2012-04-01 83 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