PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.(http://phantomjs.org/)javascript
PhantomJS 是一個無界面的webkit內核瀏覽器,你能夠把它看成一個沒有界面的 Safari。css
目前 PhantomJS 的最新版本的2.0,官方文檔中有提到說:若是在使用老版本時碰到一些難解的 bug ,能夠升級到最新版試試。html
直接下載 phantomjs-2.0.0-windows.zip ,並解壓,將 bin 文件夾中的可執行文件phantomjs.exe的路徑添加到環境變量後(可能須要重啓機器才能生效),就能夠在命令行環境(cmd 或 cygwin )中使用 phantomjs 命令執行 js 文件了。java
能夠在 Bitbucket 下載已經編譯好的二進制文件安裝包,不過目前 Linux 提供到 PhantomJS 1.9.8的安裝包,最新的 PhantomJS 2.0尚未發佈。
安裝方式:mysql
> cd /usr/local > tar zxvf phantomjs-1.9.8-linux-x86_64.tar.bz2
> ln -sf phantomjs-1.9.8-linux-x86_64/bin/phantomjs phantomjs
因爲 WebKit 模塊中有數千個文件,所以由源碼編譯 PhantomJS 會花費很長的時間,文檔上說,開四個並行的進程進行編譯工做,須要超過30分鐘的時間,所以官方文檔推薦直接下載和安裝二進制文件。jquery
具體的安裝方法,這裏就再也不贅述,你們能夠到官方文檔上查看。linux
咱們可使用下面的命令來查看 PhantomJS 是否安裝成功:web
> phantomjs -v
命令運行 phantomjs xxx.js便可執行一個 PhantomJS 程序。ajax
webpage 是 PhantomJS 的核心模塊,你能夠經過如下方式,得到一個 webpage 模塊的實例:正則表達式
var webPage = require("webpage"), page = webPage.create();
打開一個 url 連接,並加載對應的頁面,一旦頁面加載完成,就會觸發回調,你也可使用page.onLoadFinished
方法來監聽頁面是否加載完成。下面,咱們來用 open() 方法打開騰訊課堂:
var page = require("webpage").create; page.open("http://ke.qq.com", function(status) { if(status !== "success") { console.log("open fail!"); } phantom.exit(); });
上面的代碼中,open() 方法接受了兩個參數。第一個參數是要打開網頁的 url(要記得加協議頭哦!)
,默認使用 GET 方法打開,第二個參數是回調參數,網頁加載完成後該函數將會執行,它的參數status
表示網頁是否打開成功,打開成功就是success
,不然就是fail
。要注意的是,只要收到服務器返回的結果,status
參數就是success
,即便服務器返回的是404或500錯誤。
咱們也可使用其餘的http方法打開頁面。
var webPage = require("webpage"); var page = webPage.create(); var postBody = "user=username&password=password"; page.open("http://www.google.com/", "POST", postBody, function(status) { console.log("Status: " + status); // Do other things here... });
上面的代碼是官方文檔的事例,使用POST方法向服務器發送數據。open方法的第二個參數用來指定HTTP方法,第三個參數用來指定該方法所要使用的數據。
從PhantomJS 1.9開始,咱們還可使用json對象來對http請求進行更詳細的配置。
var webPage = require('webpage'); var page = webPage.create(); var settings = { operation: "POST", encoding: "utf8", headers: { "Content-Type": "application/json" }, data: JSON.stringify({ some: "data", another: ["custom", "data"] }) }; page.open('http://your.custom.api', settings, function(status) { console.log('Status: ' + status); // Do other things here... });
在打開一個網頁後,咱們每每有對其進行操做的需求,例如模擬點擊登錄按鈕、獲取某個DOM元素等等,也就是須要在頁面中執行javascript代碼,這時候咱們就須要使用到evaluate()方法。
// 獲取打開頁面的title var page = require('webpage').create(); page.open(url, function(status) { var title = page.evaluate(function() { return document.title; }); console.log('Page title is ' + title); phantom.exit(); });
因爲由於evaluate()方法至關於一個沙盒,在其中是沒法訪問evaluate()以外的變量的。那如何將我想要獲取的dom元素的id傳進evaluate呢?
從PhantomJS 1.6開始,咱們能夠將外部變量以以下的方式傳給evaluate內部,須要注意的是,能傳入evaluate方法內部的參數只能是簡單的基本類型,例如數值、字符串、json對象等能被JSON序列化的類型,而沒法接受更復雜的對象,它的返回值也一樣如此。
page.open('https://item.taobao.com/item.htm?id=520115087331', function(status) { var domId = "J_SellCounter" var sellCounter = page.evaluate(function(id) { return document.getElementById(id).innerText; }, domId); console.log(sellCounter); phantom.exit(); });
因爲open()方法打開的網頁內部的 console 語句,和 evaluate() 方法中的 console 語句都不會執行,給咱們開發調試帶來了不便。這時能夠採用 onConsoleMessage 回調函數,來打印出上面兩種狀況中的 console 語句中的信息:
var webPage = require('webpage'); var page = webPage.create(); page.onConsoleMessage = function(msg, lineNum, sourceId) { console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")'); };
其中 msg 是須要打印的信息,lineNum 和 sourceId 是 console.log 在文件中的行號以及這個文件對應的標識 id。
可使用 includeJs()方法加載外部腳本,例如 jquery。
var webPage = require('webpage'); var page = webPage.create(); page.open('http://www.example.com', function(status) { if(status !== "success") { console.log("open fail!"); } page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js', function() { page.evaluate(function() { // jQuery is loaded, now manipulate the DOM var $loginForm = $('form#login'); $loginForm.find('input[name="username"]').value('phantomjs'); $loginForm.find('input[name="password"]').value('c45p3r'); $('#loginBtn').click(); }); phantom.exit(); }); })
注意,因爲includeJs
是異步加載腳本,因此phantom.exit()
須要放在page.includeJs()
的回調函數中,不然phantomjs進程會過早退出。
render() 能夠將打開的網頁截圖並保存成本地圖片,能夠將指定的圖片文件名做爲參數傳入,render 方法能夠根據文件名的後綴將圖片保存成對應的格式。目前支持PNG
、GIF
、JPEG
、PDF
四種圖片格式。
var webPage = require('webpage'); var page = webPage.create(); page.viewportSize = { width: 1920, height: 1080 }; page.open("http://www.google.com", function start(status) { page.render('google_home.jpeg', {format: 'jpeg', quality: '100'}); phantom.exit(); });
該方法的第一個參數是保存的文件名,第二個可選參數是一個 JSON 對象,format 指定圖片格式, quality 指定0-100區間內的圖片質量,必須是整數。
當頁面去請求一個資源時,會觸發 onResourceRequested() 方法的回調函數。回調函數接受兩個參數,第一個參數requestData
是這個HTTP請求的元數據對象,包括如下屬性:
id: 所請求資源的id號,這個應該是phantomjs給標識的。
method: 所使用的HTTP方法(GET/POST/PUT/DELETE等)。
url: 所請求資源的URL
time: 包含請求該資源時間的一個Date對象。
headers: 該請求的http請求頭中的信息數組。
第二個參數networkRequest
包含如下方法:
var webPage = require('webpage'); var page = webPage.create(); page.onResourceRequested = function(requestData, networkRequest) { console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData)); }; page.open("http://ke.qq.com", function(status) { if(status) { console.log("fail!"); } phantom.exit(); });
onResourceReceived屬性用於指定一個回調函數,當網頁收到所請求的資源時,就會執行該回調函數。回調函數只有一個參數,就是所請求資源的服務器發來的HTTP response的元數據對象,包括如下字段。
id:所請求的資源編號,此編號phantomjs標識。
url:所請求的資源的URL
time:包含HTTP迴應時間的Date對象
headers:響應的HTTP頭信息數組
bodySize:解壓縮後的收到的內容大小
contentType:接到的內容種類
redirectURL:重定向URL(若是有的話)
stage:對於多數據塊的HTTP迴應,頭一個數據塊爲start,最後一個數據塊爲end。
status:HTTP狀態碼,成功時爲200。
statusText:HTTP狀態信息,好比OK。
須要注意的是,該方法收到的response對象是沒有response.body的具體內容的。
能夠利用正則表達式,來篩選出咱們想要操做的一些響應資源。好比我想從淘寶教育的課程詳情頁跳轉到購買頁(在淘寶網中),能夠從淘寶同窗請求的資源url中篩選出帶淘寶網商品詳情頁的商品id,而後用這個淘寶網商品id拼接成一個淘寶網的商品詳情頁url,再次使用open()方法打開這個url,就能夠跳轉到該課程的購買頁中。
var page = require('webpage').create(), url1 = "http://i.xue.taobao.com/detail.htm?courseId=32679", url2 = "https://item.taobao.com/item.htm?id=", itemId = 0, mItem = "", siteType = "taobao"; page.onConsoleMessage = function(msg) { console.log('console: ' + msg); }; page.onResourceReceived = function(response) { /*if(mItem = response.url.match(/^http\:\/\/(?:.*)[?|&]item=(\d*)/)) { itemId = mItem[1]; console.log(itemId); phantom.exit(); }*/ // 獲取課程對應的淘寶網商品id if(mItem = response.url.match(/itemId=(\d*)/)) { itemId = parseInt(mItem[1]); } } page.open(url1, function(status) { if(status !== "success") { console.log("tongxue fail!"); phantom.exit(); } page.render("tongxue.png"); // 打開課程對應的淘寶商品詳情頁。 page.open(url2 + itemId, function(status) { if(status !== "success") { console.log("tongxue fail!"); phantom.exit(); } // 因爲頁面中的資源是動態加載的,須要setTimeout 10s 等待資源加載完,再操做頁面。 setTimeout(function() { var apply = page.evaluate(function() { // 獲取課程交易量 return document.getElementById("J_SellCounter").innerText; //return document.getElementById("bd").innerHTML; }); console.log("apply:", apply); //fs.write("body.html", apply, "w"); phantom.exit(); }, 10000); }); });
相信你們都知道爬蟲的基本方式無非是抓取頁面中的 url,而後分析;可是頁面中的 url 也些是靜態的,有些事經過js動態生成的,故爬蟲也分抓靜及抓動之分。
由於淘寶商品詳情頁的交易量是異步拉取的,在異步數據尚未返回時,頁面上交易量那一欄只是一個無心義的「-」,如圖:
當異步數據返回後,纔會顯示出真正的交易量:
所以,
var webPage = require('webpage'); var page = webPage.create(); var pageTb = webPage.create(); var tbUrl = "https://item.taobao.com/item.htm?id=520115087331"; page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36"; pageTb.open(tbUrl, function(status) { // 因爲是拉取異步數據,咱們打開頁面後,等待12s再去操做dom,獲取交易量 setTimeout(function() { var result = pageTb.evaluate(function() { return document.getElementById("J_SellCounter").innerText; }); console.log(result); //生成當前頁面截圖 pageTb.render("xuqintb2.png"); phantom.exit(); }, 12000); });
win7上執行命令:
$ phantomjs.exe --ssl-protocol=any xuqinTb.js
1379
win7上獲得了交易量(因爲是打開https協議頭的網頁,因此執行js文件時,須要添加"--ssl-protocol=any"參數)