2012-04-01 3 views
65

Я планирую веб-сервис для собственного использования внутри, который принимает один аргумент, URL-адрес и возвращает html, представляющий DOM с этого URL-адреса. По разрешению я имею в виду, что вебсервис сначала получит страницу по этому URL-адресу, а затем использует PhantomJS для «рендеринга» страницы, а затем возвращает результирующий источник после того, как будут выполнены все DHTML, AJAX-вызовы и т. Д. Однако запуск фантома по принципу запроса (который я делаю сейчас) - это способ слишком вялый. Я бы предпочел иметь пул экземпляров PhantomJS, один из которых всегда был доступен для обслуживания последнего вызова моего веб-сервиса.Как управлять «пулом» экземпляров PhantomJS

Проделана ли какая-либо работа по этому поводу раньше? Я предпочел бы основывать этот веб-сервис на работе других, чем писать пул-менеджер/HTTP-прокси-сервер для себя с нуля.

Подробнее Контекст: Я перечислил 2 похожих проекта, которые я видел до сих пор ниже, и почему я избегал каждого из них, в результате чего возникает вопрос об управлении пулом экземпляров PhantomJS.

jsdom - из того, что я видел, у него отличная функциональность для выполнения скриптов на странице, но он не пытается реплицировать поведение браузера, поэтому, если бы я использовал его как универсальный «DOM resolver», d в конечном итоге является большим количеством дополнительного кодирования для обработки всех видов случаев краев, вызова событий и т. д. В первом примере, который я видел, нужно вручную вызвать функцию onload() тега body для тестового приложения, которое я настроил с помощью узла , Казалось, что начало глубокой кроличьей дыры.

Selenium - У этого есть намного больше движущихся частей, поэтому настройка пула для управления долговечными экземплярами браузера будет сложнее, чем использование PhantomJS. Мне не нужны какие-либо из преимуществ макросъемки/создания сценариев. Я просто хочу, чтобы веб-сервис, который так же эффективен при получении веб-страницы и разрешал его DOM, как если бы я просматривал этот URL-адрес с помощью браузера (или даже быстрее, если бы я мог игнорировать изображения и т. Д.)

ответ

17

async JavaScript library работает в Узел и имеет queue функцию, которая очень удобно для такого рода вещи:

queue(worker, concurrency)

Creates a queue object with the specified concurrency. Tasks added to the queue will be processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one is available. Once a worker has completed a task, the task's callback is called.

Некоторые псевдокод:

function getSourceViaPhantomJs(url, callback) { 
    var resultingHtml = someMagicPhantomJsStuff(url); 
    callback(null, resultingHtml); 
} 

var q = async.queue(function (task, callback) { 
    // delegate to a function that should call callback when it's done 
    // with (err, resultingHtml) as parameters 
    getSourceViaPhantomJs(task.url, callback); 
}, 5); // up to 5 PhantomJS calls at a time 

app.get('/some/url', function(req, res) { 
    q.push({url: params['url_to_scrape']}, function (err, results) { 
    res.end(results); 
    }); 
}); 

Отъезд entire documentation for queue at the project's readme.

+0

Вы знаете, как в организации очередей работы в деталях? Я думаю, что он вызывает несколько запросов XHR в очереди правильно?Я ищу решение, которое фактически поддерживает процессы phantomjs, запущенные как демон, вместо того, чтобы вращать их каждый раз, когда возникает задача. – CMCDragonkai

+0

@CMCDragonkai В этом вопросе упоминается, что «пул экземпляров PhantomJS с одним, всегда доступным для обслуживания последний звонок на мой веб-сервис », что подразумевает постоянный запуск демонов PhantomJS, но этот ответ будет работать в любом случае. Вся функция 'async.queue' делает это, чтобы в любой момент времени было не более определенного количества вызовов функции; то, что вы делаете внутри этой функции, зависит от вас. –

+2

Вы, мой друг, почти 4 года спустя, избавили меня от головной боли. – mgmcdermott

0

Если вы используете nodejs, вы можете использовать https://github.com/sgentle/phantomjs-node, что позволит вам подключить произвольное количество процессов phantomjs к вашему основному процессу NodeJS, следовательно, возможность использовать async.js и множество полезных свойств узла.

+0

Это неправда. Если вы создаете несколько экземпляров фантомной JS и запускаете их одновременно, вы получаете «Error: listen EADDRINUSE». Im в настоящее время ищет способ разместить фантомные экземпляры на разных портах или что-то другое, вызывающее EADDRINUSE. – RachelC

+1

Это, конечно же, ваша ответственность за запуск фантомных экземпляров, чтобы они прослушивали другой порт. –

61

Я устанавливаю облачную службу PhantomJs, и это в значительной степени делает то, что вы просите. Мне потребовалось около 5 недель работы.

Самая большая проблема, с которой вы столкнетесь, - это известная проблема memory leaks in PhantomJs. Способ, которым я работал, - это цикл моих экземпляров каждые 50 вызовов.

Вторая по важности проблема, с которой вы столкнетесь, - это обработка процессора и процессора, поэтому вы можете запускать только 4 экземпляра на каждый процессор.

Третья по важности проблема, с которой вы столкнетесь, заключается в том, что PhantomJs довольно дурацча с событиями и перенаправлением страницы. Вам будет сообщено, что ваша страница закончила рендеринг, прежде чем она на самом деле. There are a number of ways to deal with this, но ничего «стандартного», к сожалению.

Четвертая по значимости проблема, с которой вам придется столкнуться, - это взаимодействие между nodejs и phantomjs, к счастью, есть a lot of npm packages that deal with this issue на выбор.

Итак, я знаю, что я предвзятый (так как я написал решение, которое я собираюсь предложить), но я предлагаю вам проверить PhantomJsCloud.com, который является бесплатным для использования на свет.

Jan 2015 update: Другая (5-я) проблема, с которой я столкнулся, заключается в том, как отправить запрос/ответ от менеджера/load-balancer. Первоначально я использовал встроенный HTTP-сервер PhantomJS, но все время сталкивался с его ограничениями, особенно в отношении максимального размера ответа. В итоге я написал запрос/ответ на локальную файловую систему как линии связи. * Общее время, затрачиваемое на реализацию услуги, составляет, возможно, 20 человеко-недельных вопросов, возможно, 1000 часов работы. * и FYI Я делаю полную переписку для следующей версии .... (в процессе)

+0

Отличный ответ Джейсон. Было бы очень приятно, если бы вы могли подробнее рассказать о деталях реализации. Как вы управляете всеми экземплярами, например? Кроме того, как вы запускаете экземпляры Phantom из самого узла? Любая рекомендация модуля для этого? Или вы запускаете процессы? – Nobita

+1

Я делаю все управление с помощью приложения nodejs 'router' на сервере. он запускает несколько экземпляров phantomjs.exe через обычные команды процесса spawnsjs. ничего особенного в этом отношении. Я попробовал все различные фантомные обертки, найденные на NPM, но, откровенно говоря, они в основном сосут. Закончилось использование только встроенного http-сервера phantomjs для связи с/из приложения nodejs router. – JasonS

+0

Как создать несколько объектов веб-страницы в одном экземпляре phantomJS? что-то не так с этим? – Xsmael

5

В качестве альтернативы отличному ответу @JasonS вы можете попробовать PhearJS, который я построил. PhearJS является супервизором, написанным в NodeJS для экземпляров PhantomJS, и предоставляет API через HTTP. Он доступен с открытым исходным кодом от Github.

1

если вы используете nodejs почему бы не использовать селен WebDriver

  1. запустить некоторую phantomjs экземпляр, как WebDriver phantomjs --webdriver=port_number
  2. для каждого phantomjs экземпляра создать PhantomInstance

    function PhantomInstance(port) { 
        this.port = port; 
    } 
    
    PhantomInstance.prototype.getDriver = function() { 
        var self = this; 
        var driver = new webdriver.Builder() 
         .forBrowser('phantomjs') 
         .usingServer('http://localhost:'+self.port) 
         .build(); 
        return driver; 
    } 
    

    и поставить все их к одному массиву [phantomInstance1, phantomInstance2]

  3. создать dispather.js, которые получают бесплатно phantomInstance из массива и

    var driver = phantomInstance.getDriver(); 
    
+0

Это нехорошо. Поверьте мне ... в моей программе я использовал selenium-webdriver, но, в конце концов, я сдался! –

14

Для моей магистерской диссертации, я разработал библиотеку phantomjs-pool, которая делает именно это. Он позволяет предоставлять рабочие места, которые затем отображаются на рабочих PhantomJS. Библиотека обрабатывает распределение заданий, связь, обработку ошибок, протоколирование, перезапуск и некоторые другие вещи. Библиотека была успешно использована для сканирования более миллиона страниц.

Пример:

Следующий код выполняет поиск Google для чисел от 0 до 9 и сохраняет скриншот страницы, как googleX.png. Четыре веб-сайта сканируются параллельно (из-за создания четырех рабочих). Сценарий запускается через node master.js.

master.js (работает в среде Node.js)

var Pool = require('phantomjs-pool').Pool; 

var pool = new Pool({ // create a pool 
    numWorkers : 4, // with 4 workers 
    jobCallback : jobCallback, 
    workerFile : __dirname + '/worker.js', // location of the worker file 
    phantomjsBinary : __dirname + '/path/to/phantomjs_binary' // either provide the location of the binary or install phantomjs or phantomjs2 (via npm) 
}); 
pool.start(); 

function jobCallback(job, worker, index) { // called to create a single job 
    if (index < 10) { // index is count up for each job automatically 
     job(index, function(err) { // create the job with index as data 
      console.log('DONE: ' + index); // log that the job was done 
     }); 
    } else { 
     job(null); // no more jobs 
    } 
} 

worker.js (работает в среде PhantomJS)

var webpage = require('webpage'); 

module.exports = function(data, done, worker) { // data provided by the master 
    var page = webpage.create(); 

    // search for the given data (which contains the index number) and save a screenshot 
    page.open('https://www.google.com/search?q=' + data, function() { 
     page.render('google' + data + '.png'); 
     done(); // signal that the job was executed 
    }); 

}; 
+1

Это отличная библиотека. Мне интересно, есть ли способ обнаружить, когда не будет никаких процессов, которые будут созданы? Как в ожидании, через async или обещание, после 'pool.start()' что-то сделать после завершения серии процессов? – afithings

+0

Спасибо. В настоящее время нет никакого способа сделать это так же просто, как с помощью async. Однако вы можете использовать обратный вызов для каждого отдельного задания (которое срабатывает при выполнении одного задания) и таким образом увеличивать счетчик. Таким образом, вы все еще можете обнаружить, когда все задания будут завершены. –

 Смежные вопросы

  • Нет связанных вопросов^_^