PhantomJS 基礎及示例

騰訊雲加社群發表於2017-04-18

騰訊雲技術社群-掘金主頁持續為大家呈現雲端計算技術文章,歡迎大家關注!


作者:link

概述

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.(phantomjs.org/)

PhantomJS是一個無介面的webkit核心瀏覽器,你可以把它當作一個沒有介面的Safari。

安裝

目前PhantomJS的最新版本的2.0,官方文件中有提到說:如果在使用老版本時碰到一些難解的bug,可以升級到最新版試試。

windows

直接下載phantomjs-2.0.0-windows.zip,並解壓,將bin資料夾中的可執行檔案phantomjs.exe的路徑新增到環境變數後(可能需要重啟機器才能生效),就可以在命令列環境(cmd或cygwin)中使用phantomjs命令執行js檔案了。

Linux

安裝二進位制檔案包

可以在Bitbucket下載已經編譯好的二進位制檔案安裝包,不過目前Linux提供到PhantomJS 1.9.8的安裝包,最新的PhantomJS 2.0還沒有釋出。
安裝方式:

  1. 下載phantomjs-1.9.8-linux-x86_64.tar.bz2
  2. 進入安裝目錄,解壓二進位制檔案
> cd /usr/local
> tar zxvf phantomjs-1.9.8-linux-x86_64.tar.bz2複製程式碼
  1. 建立軟連結mysql指向解壓出來的資料夾,或將解壓出來的資料夾重新命名為phantomjs:
> ln -sf phantomjs-1.9.8-linux-x86_64/bin/phantomjs phantomjs複製程式碼

編譯原始碼的方式

由於WebKit模組中有數千個檔案,因此由原始碼編譯PhantomJS會花費很長的時間,文件上說,開四個並行的程式進行編譯工作,需要超過30分鐘的時間,因此官方文件推薦直接下載和安裝二進位制檔案。
具體的安裝方法,這裡就不再贅述,大家可以到官方文件上檢視。

是否安裝成功

我們可以使用下面的命令來檢視PhantomJS是否安裝成功:

> phantomjs -v複製程式碼

命令執行phantomjs xxx.js即可執行一個PhantomJS程式。

webpage模組

webpage是PhantomJS的核心模組,你可以通過以下方式,獲得一個webpage模組的例項:

var webPage = require("webpage"),
    page = webPage.create();複製程式碼

open()

開啟一個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...
});複製程式碼

evaluate()

在開啟一個網頁後,我們往往有對其進行操作的需求,例如模擬點選登陸按鈕、獲取某個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()

可以使用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()可以將開啟的網頁截圖並儲存成本地圖片,可以將指定的圖片檔名作為引數傳入,render方法可以根據檔名的字尾將圖片儲存成對應的格式。目前支援PNGGIFJPEGPDF四種圖片格式。

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

當頁面去請求一個資源時,會觸發onResourceRequested()方法的回撥函式。回撥函式接受兩個引數,第一個引數requestData是這個HTTP請求的後設資料物件,包括以下屬性:

  • id: 所請求資源的id號,這個應該是phantomjs給標識的。
  • method: 所使用的HTTP方法(GET/POST/PUT/DELETE等)。
  • url: 所請求資源的URL
  • time: 包含請求該資源時間的一個Date物件。
  • headers: 該請求的http請求頭中的資訊陣列。

第二個引數networkRequest包含以下方法:

  • abort(): 終止當前的網路請求,這會導致呼叫onResourceError回撥函式。
  • changeUrl(newUrl):改變當前網路請求的URL。
  • setHeader(key, value):設定HTTP頭資訊。
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

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動態生成的,故爬蟲也分抓靜及抓動之分。
因為淘寶商品詳情頁的交易量是非同步拉取的,在非同步資料還沒有返回時,頁面上交易量那一欄只是一個無意義的“-”,如圖:

PhantomJS 基礎及示例

當非同步資料返回後,才會顯示出真正的交易量:

PhantomJS 基礎及示例

因此,

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"引數)

PhantomJS不能做什麼

  • PhantomJS是一個閹割版的webkit,不支援flash、webGL、video/audio、css 3-d,phontomjs不想揹負作業系統強相關的特性,跨平臺比較困難。
  • 如果使用Page模組的onResourceReceived()方法監聽頁面收到的請求資源,是無法得到該資源的response.body的,這也是目前PhantomJS最受開發者吐槽的點之一。

原文連結:ivweb.io/topic/560b4…

相關推薦

騰訊雲上PhantomJS用法示例
騰訊雲上Selenium用法示例
包學會之淺入淺出Vue.js:開學篇


此文已由作者授權騰訊雲技術社群釋出,轉載請註明文章出處
原文連結:www.qcloud.com/community/a…
獲取更多騰訊海量技術實踐乾貨,歡迎大家前往騰訊雲技術社群

相關文章