Как управлять "пулом" экземпляров 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.