如何管理PhantomJS实例的“池”

时间:2012-04-01 01:41:10

标签: node.js web-scraping phantomjs jsdom

我正在计划内部使用的Web服务,它接受一个参数,一个URL,并从该URL返回表示已解析 DOM的html。通过解决,我的意思是webservice将首先获取该URL的页面,然后使用PhantomJS“渲染”页面,然后在执行所有DHTML,AJAX调用等之后返回结果源。然而,基于每个请求启动幻像(我现在正在做)是方式过于缓慢。我宁愿拥有一个PhantomJS实例池,其中一个实例可以提供最新的Web服务调用。

之前有没有做过这类事情的工作?我宁愿把这个web服务建立在别人的工作之上,而不是从头开始为我自己编写一个池管理器/ http代理服务器。

更多上下文:我列出了迄今为止我见过的2个类似的项目,以及为什么我避免了每个项目,导致关于管理PhantomJS实例池的问题

jsdom - 从我所看到它在页面上执行脚本具有很强的功能,但它不会尝试复制浏览器行为,所以如果我将它用作通用的“DOM解析器”那么就有了最终会有很多额外的编码来处理各种边缘情况,事件调用等。我看到的第一个例子是必须手动调用body标签的onload()函数,用于我使用node设置的测试应用程序。这似乎是一个深深的兔子洞的开始。

Selenium - 它只有很多移动部件,因此设置一个池来管理长期存在的浏览器实例将比使用PhantomJS更复杂。我不需要它的任何宏录制/脚本功能。我只想要一个能够获得网页并解析它的DOM的网络服务,就好像我用浏览器浏览到那个URL一样(如果我可以让它忽略图像等,甚至更快)。

6 个答案:

答案 0 :(得分:62)

我设置了PhantomJs Cloud Service,它几乎可以满足您的要求。我花了大约5个星期的工作时间。

您遇到的最大问题是memory leaks in PhantomJs的已知问题。我解决这个问题的方法是每50次调用循环我的实例。

您遇到的第二大问题是每页处理非常大,因此每个CPU只能运行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

2015年1月更新:我遇到的另一个(第5个?)大问题是如何从经理/负载均衡器发送请求/响应。最初我使用的是PhantomJS的内置HTTP服务器,但仍然遇到了它的局限性,特别是在最大响应大小方面。我最终将请求/响应写入本地文件系统作为通信线路。 *实施服务所花费的总时间可能代表20个人工周问题,也许是1000个小时的工作。 * 和FYI我正在为下一个版本进行完全重写....(正在进行中)

答案 1 :(得分:17)

async JavaScript library在Node中有效,并且有一个queue函数,对于这类事情非常方便:

  

queue(worker, 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'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

答案 2 :(得分: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
    });

};

答案 3 :(得分:5)

作为@JasonS的替代方案,您可以尝试我建立的PhearJS。 PhearJS是一个用NodeJS编写的用于PhantomJS实例的管理程序,它通过HTTP提供API。它是Github开源的。

答案 4 :(得分:1)

如果您使用nodejs,为什么不使用selenium-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. 创建从数组中获取免费phantomInstance的dispather.js和

    var driver = phantomInstance.getDriver();
    

答案 5 :(得分:0)

如果您正在使用nodejs,您可以使用https://github.com/sgentle/phantomjs-node,这将允许您将任意数量的phantomjs进程连接到您的主NodeJS进程,因此,能够使用async.js和许多节点好东西