2012-04-01 84 views
65

我正在计划一个内部使用的web服务,它接受一个参数,一个URL,并从该URL返回代表解析的 DOM的html。通过解析,我的意思是web服务首先会在该URL上获取页面,然后使用PhantomJS来“呈现”该页面,然后在执行完所有DHTML,AJAX调用等之后返回生成的源代码。然而,在每个请求的基础上启动幻像(我现在正在做)是方式太慢了。我宁愿有一个PhantomJS实例池,其中一个始终可用来为我的web服务提供最新的调用。如何管理PhantomJS实例的“池”

之前有没有做过这方面的工作?我宁愿将这个web服务基于其他人的工作,而不是从头开始编写自己的池管理器/ http代理服务器。

更多上下文:我已经列出了两个类似的项目,我已经在下面看到了,并且为什么我避免了每个项目,结果导致这个关于管理PhantomJS实例池的问题。我从中可以看到它对于在页面上执行脚本具有很好的功能,但它不会尝试复制浏览器行为,所以如果我将它用作通用“DOM解析器”,那么“ d最终需要大量额外的编码来处理各种边界情况,事件调用等。我看到的第一个例子是,我必须手动调用我使用节点设置的测试应用程序的body标记的onload()函数。这似乎是一个深刻的兔子洞的开始。

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

回答

17

async JavaScript library适用于节点,具有queue功能是这种东西非常方便:

queue(worker, concurrency)

Creates a queue object with the specified concurrency. Tasks added to the queue will be processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one is available. Once a worker has completed a task, the task's callback is called.

一些伪代码:

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

+0

你知道怎么排队作品详细?我在想这是在队列中调用多个XHR请求吗?我正在寻找一种解决方案,它实际上保持phantomjs进程作为守护进程运行,而不是在每次任务进入时进行一次启动。 – CMCDragonkai 2013-10-01 03:37:27

+0

@CMCDragonkai该问题提到“一个PhantomJS实例池始终可用于服务我的web服务的最新调用“,这意味着不断运行PhantomJS守护进程,但是这个答案适用于任何一种情况。所有的'async.queue'函数都确保在任何给定的时间不超过一定数量的函数调用未完成;你在这个功能里面做什么取决于你。 – 2013-10-01 03:41:52

+2

你我的朋友,差不多4年后,让我非常头痛。 – mgmcdermott 2016-02-19 22:43:24

0

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

+0

这是不正确的。如果您创建幻影JS的多个实例并同时运行它们,则会出现'错误:侦听EADDRINUSE'。我目前正在寻找一种方法来将幻影实例放在不同的端口或任何引起EADDRINUSE的东西。 – RachelC 2013-09-12 18:41:14

+1

当然,您有责任启动幻像实例,以便在不同的端口上侦听。 – 2015-03-19 09:51:17

61

我设置了一个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小时的工作时间。 *和FYI我正在为下一个版本做一个完整的重写....(进行中)

+0

伟大的答案杰森。如果你能继续告诉我们更多关于实施细节的信息,那将是非常好的。你如何管理所有的实例?另外,你如何从节点本身启动de Phantom实例?任何模块建议这样做?或者你产生了过程? – Nobita 2014-06-15 18:52:49

+1

我从服务器上的nodejs'路由器'应用程序执行所有管理。它通过正常的nodejs spawn处理命令启动多个phantomjs.exe实例。实际上在这方面没什么特别的。我尝试了NPM上发现的所有各种幻影包装,但坦率地说,他们大多是吸吮。最后只需使用phantomjs内置的http服务器与nodejs路由器应用进行通信。 – JasonS 2014-06-19 14:41:14

+0

如何在一个phantomJS实例中创建多个网页对象?那有什么不对吗? – Xsmael 2016-06-26 21:12:56

5

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

1

如果你正在使用的NodeJS为什么不使用硒的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和

    var driver = phantomInstance.getDriver(); 
    
+0

这不是一个好方法。相信我......在我的程序中我使用了selenium-webdriver,但最后我放弃了! – 2017-06-02 07:01:59

14

对于我的硕士论文dispather.js,我开发出不正是这个库phantomjs-pool。它允许提供映射到PhantomJS工作人员的工作。该库处理作业分布,通信,错误处理,日志记录,重新启动等等。该图书馆已成功用于抓取超过一百万页。

实施例:

下面的代码执行谷歌搜索数字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 
    }); 

}; 
+1

这是一个很棒的图书馆。我想知道,有没有一种方法可以检测何时不会产生更多进程?像in一样,等待,通过异步或承诺,在'pool.start()'之后等待一系列进程完成后再执行某些操作? – afithings 2016-09-07 15:31:51

+0

谢谢。目前没有办法像使用异步一样简单。但是,您可以对每个单个作业使用回调(当一个作业完成时触发)并以此方式增加计数器。所以你仍然能够检测到所有工作完成的时间。 – 2016-09-15 09:19:17