Как управлять "пулом" экземпляров PhantomJS
Я планирую веб-сервис для собственного использования внутри, который принимает один аргумент, URL-адрес и возвращает html, представляющий разрешенную DOM с этого URL-адреса. По разрешению я имею в виду, что webservice сначала получит страницу по этому URL-адресу, а затем использует PhantomJS для "рендеринга" страницы, а затем возвращает результирующий источник после того, как все DHTML, вызовы AJAX и т.д. Будут выполнены. Однако запуск phantom для каждого запроса (который я делаю сейчас) слишком медленный. Я бы предпочел иметь пул экземпляров PhantomJS, один из которых всегда доступен для обслуживания последнего вызова моего веб-сервиса.
Проделана ли какая-либо работа над этим видом раньше? Я предпочел бы основывать этот веб-сервис на работе других, чем писать пул-менеджер/HTTP-прокси-сервер для себя с нуля.
Больше контекста. Я перечислил 2 похожих проекта, которые я видел до сих пор ниже, и почему я избегал каждого из них, в результате чего возникает вопрос об управлении пулом экземпляров PhantomJS.
jsdom - из того, что я видел, у него отличная функциональность для выполнения скриптов на странице, но он не пытается реплицировать поведение браузера, поэтому, если бы я использовал его как универсальный "DOM resolver", в конечном итоге, есть много дополнительного кодирования для обработки всех случаев краев, вызова событий и т.д. В первом примере, который я видел, нужно вручную вызвать функцию onload() тега body для тестового приложения, которое я установил, используя node. Это казалось началом глубокой кроличьей дыры.
Selenium - у него просто намного больше движущихся частей, поэтому настройка пула для управления долговечными экземплярами браузера будет сложнее, чем использование PhantomJS. Мне не нужны какие-либо преимущества для записи макросов/сценариев. Я просто хочу, чтобы веб-сервис, который так же эффективен при получении веб-страницы и разрешал его DOM, как если бы я просматривал этот URL-адрес с помощью браузера (или даже быстрее, если я могу заставить его игнорировать изображения и т.д.).
Ответы
Ответ 1
Я устанавливаю облачную службу PhantomJs, и это в значительной степени делает то, что вы просите. Мне потребовалось около 5 недель работы.
Самая большая проблема, с которой вы столкнетесь, - это известная проблема утечек памяти в PhantomJs. Способ, которым я работал, - это цикл моих экземпляров каждые 50 вызовов.
Вторая по важности проблема, с которой вы столкнетесь, - обработка на одной странице, очень интенсивная с процессором и памятью, поэтому вы можете запускать только 4 экземпляра на каждый процессор.
Третья по важности проблема, с которой вы столкнетесь, заключается в том, что PhantomJs довольно пугает событиями и перенаправлением страницы. Вам будет сообщено, что ваша страница закончила рендеринг, прежде чем она на самом деле. Есть несколько способов борьбы с этим, но ничего "стандартного", к сожалению.
Четвертая самая большая проблема, с которой вам придется столкнуться, - это interop между nodejs и phantomjs, к счастью, есть множество пакетов npm, которые занимаются этой проблемой, чтобы выбрать из.
Итак, я знаю, что я предвзятый (поскольку я написал решение, которое я собираюсь предложить), но я предлагаю вам проверить PhantomJsCloud.com, который свободен для использования света.
Обновление в январе 2015 года: Другая (5-я) проблема, с которой я столкнулся, заключается в том, как отправить запрос/ответ от менеджера /load -balancer. Первоначально я использовал встроенный HTTP-сервер PhantomJS, но все время сталкивался с ограничениями, особенно в отношении максимального размера ответа. В итоге я написал запрос/ответ на локальную файловую систему как линии связи. * Общее время, затрачиваемое на реализацию услуги, составляет, возможно, 20 человеко-недельных вопросов, возможно, 1000 часов работы. * и FYI. Я делаю полную переписку для следующей версии.... (в процессе)
Ответ 2
асинхронная JavaScript-библиотека работает в Node и имеет функцию queue
, которая весьма удобна для такого рода вещей:
queue(worker, concurrency)
Создает объект очереди с указанным concurrency. Задачи, добавленные в очередь, будут обрабатываться параллельно (до предела concurrency). Если все работники находятся в процессе выполнения, задача ставится в очередь до тех пор, пока она не будет доступна. Как только рабочий выполнит задачу, вызывается обратный вызов задачи.
Некоторые псевдокоды:
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 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);
});
});
Просмотрите всю документацию для queue
в файле readme.
Ответ 3
Для моей магистерской диссертации я разработал библиотеку phantomjs-pool, которая делает именно это. Он позволяет предоставлять рабочие места, которые затем отображаются на рабочих PhantomJS. Библиотека обрабатывает распределение заданий, связь, обработку ошибок, протоколирование, перезапуск и некоторые другие вещи. Библиотека была успешно использована для сканирования более миллиона страниц.
Пример:
Следующий код выполняет поиск Google для чисел от 0 до 9 и сохраняет скриншот страницы как googleX.png. Четыре веб-сайта сканируются параллельно (из-за создания четырех рабочих). script запускается через 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
});
};
Ответ 4
В качестве альтернативы отличному ответу @JasonS вы можете попробовать PhearJS, который я создал. PhearJS является супервизором, написанным в NodeJS для экземпляров PhantomJS, и предоставляет API через HTTP. Он доступен с открытым исходным кодом из Github.
Ответ 5
если вы используете nodejs, почему бы не использовать selenium-webdriver
- запустить некоторый экземпляр phantomjs как webdriver
phantomjs --webdriver=port_number
-
для каждого экземпляра 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]
-
создайте dispather.js, которые получают бесплатный phantomInstance из массива и
var driver = phantomInstance.getDriver();
Ответ 6
Если вы используете nodejs, вы можете использовать https://github.com/sgentle/phantomjs-node, что позволит вам связать произвольное количество процессов phantomjs с вашим основным Таким образом, процесс NodeJS позволяет использовать async.js и множество лакомств node.