【问题标题】:How to manage a 'pool' of PhantomJS instances如何管理 PhantomJS 实例的“池”
【发布时间】:2012-04-15 05:13:23
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: node.js web-scraping phantomjs jsdom


    【解决方案1】:

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

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

    您将遇到的第二大问题是每页处理非常消耗 CPU 和内存,因此每个 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 小时的工作。 * 仅供参考,我正在为下一个版本进行完全重写....(进行中)

    【讨论】:

    • 很好的答案杰森。如果您能继续告诉我们更多有关实施细节的信息,那就太好了。例如,您如何管理所有实例?另外,如何从 Node 本身启动 de Phantom 实例?有什么模块建议这样做吗?或者你产生进程?
    • 我从服务器上的 nodejs“路由器”应用程序进行所有管理。它通过正常的 nodejs spawn process 命令启动多个 phantomjs.exe 实例。实际上在这方面没有什么特别的。我尝试了在 NPM 上找到的所有各种 phantomjs 包装器,但坦率地说,它们大多都很糟糕。最终只使用 phantomjs 的内置 http 服务器与 nodejs 路由器应用程序进行通信。
    • 在一个 phantomJS 实例中创建多个网页对象怎么样?这有什么问题吗?
    【解决方案2】:

    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

    【讨论】:

    • 你知道排队的详细工作原理吗?我在想它是在队列中调用多个 XHR 请求吗?我正在寻找一种解决方案,它实际上让 phantomjs 进程作为守护进程运行,而不是每次有任务进入时都启动一个。
    • @CMCDragonkai 问题提到“一个 PhantomJS 实例池,其中一个始终可用于服务对我的 web 服务的最新调用”,这意味着不断运行 PhantomJS 守护程序,但这个答案适用于任何一种情况。 async.queue 函数所做的只是确保在任何给定时间不超过一定数量的函数调用;您在该函数中执行的操作取决于您。
    • 你我的朋友,差不多 4 年后,让我免于头痛。
    【解决方案3】:

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

    示例:

    以下代码对数字 0 到 9 执行 Google 搜索,并将页面截图保存为 googleX.png。四个网站并行爬取(由于创建了四个worker)。该脚本通过node master.js启动。

    ma​​ster.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
        });
    
    };
    

    【讨论】:

    • 这是一个很棒的图书馆。我想知道,有没有办法检测何时不再有进程产生?例如,通过异步或承诺,在pool.start() 之后等待一系列进程完成后做某事?
    • 谢谢。目前没有办法像使用异步一样简单。但是,您可以为每个单独的作业使用回调(当一个作业完成时触发)并以这种方式增加一个计数器。因此,您仍然能够检测到所有作业何时完成。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      如果你使用 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();
        

      【讨论】:

      • 这不是一个好办法。相信我……在我的程序中,我使用了 selenium-webdriver,但最后我放弃了!
      【解决方案6】:

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

      【讨论】:

      • 这不是真的。如果您创建多个 phantom JS 实例并同时运行它们,您会得到“错误:监听 EADDRINUSE”。我目前正在寻找一种将幻像实例放在不同端口或导致 EADDRINUSE 的任何东西的方法。
      • 当然你有责任启动幻象实例,以便它们在不同的端口上侦听。
      猜你喜欢
      • 2020-04-13
      • 2022-06-15
      • 2018-04-28
      • 2019-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-02
      • 2012-07-03
      相关资源
      最近更新 更多