66

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

以前有没有在这种事情上做过任何工作?我宁愿这个 web 服务基于其他人的工作,也不愿从头开始为自己编写一个池管理器/http 代理服务器。

更多上下文:我在下面列出了到目前为止我看到的 2 个类似项目,以及为什么我避开了每个项目,从而导致了这个关于管理 PhantomJS 实例池的问题。

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

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

4

6 回答 6

62

我设置了一个 PhantomJs 云服务,它几乎可以满足您的要求。我花了大约 5 周的时间实施工作。

您将遇到的最大问题是PhantomJs 中已知的内存泄漏问题。我解决这个问题的方法是每 50 次调用循环我的实例。

您将遇到的第二大问题是每页处理非常占用 cpu 和内存,因此每个 CPU 只能运行 4 个左右的实例。

您将遇到的第三个最大问题是 PhantomJs 对页面完成事件和重定向非常古怪。您将被告知您的页面在实际渲染之前已完成渲染。 有很多方法可以解决这个问题,但不幸的是没有“标准”。

您必须处理的第四个最大问题是 nodejs 和 phantomjs 之间的互操作,幸好有很多 npm 包可以处理这个问题可供选择。

所以我知道我有偏见(因为我写了我要建议的解决方案),但我建议你查看PhantomJsCloud.com,它可以免费使用。

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

于 2013-10-28T04:30:11.630 回答
17

异步 JavaScript 库在 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);
  });
});

在项目的自述文件中查看整个文档queue

于 2012-04-18T05:21:39.980 回答
14

对于我的硕士论文,我开发了phantomjs-pool 库,它正是这样做的。它允许提供工作,然后映射到 PhantomJS 工作人员。该库处理作业分配、通信、错误处理、日志记录、重新启动等。该库已成功用于爬取超过一百万页。

例子:

以下代码对数字 0 到 9 执行 Google 搜索,并将页面的屏幕截图保存为googleX.png。四个网站并行爬取(由于创建了四个worker)。该脚本通过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
    });

};
于 2015-12-02T19:07:51.333 回答
5

作为@JasonS 很好的答案的替代方案,您可以尝试我构建的PhearJS。PhearJS 是用 NodeJS 为 PhantomJS 实例编写的主管,并通过 HTTP 提供 API。它可以从Github开源。

于 2015-03-25T11:22:20.550 回答
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. 创建 dispather.js 从数组中获取免费的 phantomInstance 和

    var driver = phantomInstance.getDriver();
    
于 2015-11-10T09:13:17.143 回答
0

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

于 2012-10-28T21:45:13.873 回答