[Python3網絡爬蟲開發實戰] 7-動態渲染頁面爬取-2-Splash的使用

Splash是一個JavaScript渲染服務,是一個帶有HTTP API的輕量級瀏覽器,同時它對接了Python中的Twisted和QT庫。利用它,咱們一樣能夠實現動態渲染頁面的抓取。html

1. 功能介紹

利用Splash,咱們能夠實現以下功能:node

  • 異步方式處理多個網頁渲染過程;
  • 獲取渲染後的頁面的源代碼或截圖;
  • 經過關閉圖片渲染或者使用Adblock規則來加快頁面渲染速度;
  • 可執行特定的JavaScript腳本;
  • 可經過Lua腳原本控制頁面渲染過程;
  • 獲取渲染的詳細過程並經過HAR(HTTP Archive)格式呈現。

接下來,咱們來了解一下它的具體用法。jquery

2. 準備工做

在開始以前,請確保已經正確安裝好了Splash並能夠正常運行服務。若是沒有安裝,能夠參考第1章。json

3. 實例引入

首先,經過Splash提供的Web頁面來測試其渲染過程。例如,咱們在本機8050端口上運行了Splash服務,打開http://localhost:8050/便可看到其Web頁面,如圖7-6所示。api

圖7-6 Web頁面瀏覽器

在圖7-6右側,呈現的是一個渲染示例。能夠看到,上方有一個輸入框,默認是google.com,這裏換成百度測試一下,將內容更改成www.baidu.com,而後點擊Render me按鈕開始渲染,結果如圖7-7所示。緩存

圖7-7 運行結果bash

能夠看到,網頁的返回結果呈現了渲染截圖、HAR加載統計數據、網頁的源代碼。微信

經過HAR的結果能夠看到,Splash執行了整個網頁的渲染過程,包括CSS、JavaScript的加載等過程,呈現的頁面和咱們在瀏覽器中獲得的結果徹底一致。cookie

那麼,這個過程由什麼來控制呢?從新返回首頁,能夠看到其實是有一段腳本,內容以下:

123456789function main(splash, args)  assert(splash:go(args.url))  assert(splash:wait(0.5))  return {    html = splash:html(),    png = splash:png(),    har = splash:har(),  }end複製代碼

這個腳本其實是用Lua語言寫的腳本。即便不懂這個語言的語法,但從腳本的表面意思,咱們也能夠大體瞭解到它首先調用go()方法去加載頁面,而後調用wait()方法等待了必定時間,最後返回了頁面的源碼、截圖和HAR信息。

到這裏,咱們大致瞭解了Splash是經過Lua腳原本控制了頁面的加載過程的,加載過程徹底模擬瀏覽器,最後可返回各類格式的結果,如網頁源碼和截圖等。

接下來,咱們就來了解Lua腳本的寫法以及相關API的用法。

4. Splash Lua腳本

Splash能夠經過Lua腳本執行一系列渲染操做,這樣咱們就能夠用Splash來模擬相似Chrome、PhantomJS的操做了。

首先,咱們來了解一下Splash Lua腳本的入口和執行方式。

入口及返回值

首先,來看一個基本實例:

123456function main(splash, args)  splash:go("http://www.baidu.com")  splash:wait(0.5)  local title = splash:evaljs("document.title")  return {title=title}end複製代碼

咱們將代碼粘貼到剛纔打開的http://localhost:8050/的代碼編輯區域,而後點擊Render me!按鈕來測試一下。

咱們看到它返回了網頁的標題,如圖7-8所示。這裏咱們經過evaljs()方法傳入JavaScript腳本,而document.title的執行結果就是返回網頁標題,執行完畢後將其賦值給一個title變量,隨後將其返回。

圖7-8 運行結果

注意,咱們在這裏定義的方法名稱叫做main()。這個名稱必須是固定的,Splash會默認調用這個方法。

該方法的返回值既能夠是字典形式,也能夠是字符串形式,最後都會轉化爲Splash HTTP Response,例如:

123function main(splash)    return {hello="world!"}end複製代碼

返回了一個字典形式的內容。例如:

123function main(splash)    return 'hello'end複製代碼

返回了一個字符串形式的內容。

異步處理

Splash支持異步處理,可是這裏並無顯式指明回調方法,其回調的跳轉是在Splash內部完成的。示例以下:

function main(splash, args)
  local example_urls = {"www.baidu.com", "www.taobao.com", "www.zhihu.com"}
  local urls = args.urls or example_urls
  local results = {}
  for index, url in ipairs(urls) do
    local ok, reason = splash:go("http://" .. url)
    if ok then
      splash:wait(2)
      results[url] = splash:png()
    end
  end
  return results
end

複製代碼

運行結果是3個站點的截圖,如圖7-9所示。

圖7-9 運行結果

在腳本內調用的wait()方法相似於Python中的sleep(),其參數爲等待的秒數。當Splash執行到此方法時,它會轉而去處理其餘任務,而後在指定的時間事後再回來繼續處理。

這裏值得注意的是,Lua腳本中的字符串拼接和Python不一樣,它使用的是..操做符,而不是+。若是有必要,能夠簡單瞭解一下Lua腳本的語法,詳見www.runoob.com/lua/lua-bas…

另外,這裏作了加載時的異常檢測。go()方法會返回加載頁面的結果狀態,若是頁面出現4xx或5xx狀態碼,ok變量就爲空,就不會返回加載後的圖片。

5. Splash對象屬性

咱們注意到,前面例子中main()方法的第一個參數是splash,這個對象很是重要,它相似於Selenium中的WebDriver對象,咱們能夠調用它的一些屬性和方法來控制加載過程。接下來,先看下它的屬性。

args

該屬性能夠獲取加載時配置的參數,好比URL,若是爲GET請求,它還能夠獲取GET請求參數;若是爲POST請求,它能夠獲取表單提交的數據。Splash也支持使用第二個參數直接做爲args,例如:

123function main(splash, args)    local url = args.urlend複製代碼

這裏第二個參數args就至關於splash.args屬性,以上代碼等價於:

123function main(splash)    local url = splash.args.urlend複製代碼

js_enabled複製代碼

這個屬性是Splash的JavaScript執行開關,能夠將其配置爲truefalse來控制是否執行JavaScript代碼,默認爲true。例如,這裏禁止執行JavaScript代碼:

123456function main(splash, args)  splash:go("https://www.baidu.com")  splash.js_enabled = false  local title = splash:evaljs("document.title")  return {title=title}end複製代碼

接着咱們從新調用了evaljs()方法執行JavaScript代碼,此時運行結果就會拋出異常:

1234567891011121314{    "error": 400,    "type": "ScriptError",    "info": {        "type": "JS_ERROR",        "js_error_message": null,        "source": "[string \"function main(splash, args)\r...\"]",        "message": "[string \"function main(splash, args)\r...\"]:4: unknown JS error: None",        "line_number": 4,        "error": "unknown JS error: None",        "splash_method": "evaljs"    },    "description": "Error happened while executing Lua script"}複製代碼

不過通常來講,不用設置此屬性,默認開啓便可。

resource_timeout

此屬性能夠設置加載的超時時間,單位是秒。若是設置爲0或nil(相似Python中的None),表明不檢測超時。示例以下:

function main(splash)    splash.resource_timeout = 0.1    assert(splash:go('https://www.taobao.com'))    return splash:png()end複製代碼

例如,這裏將超時時間設置爲0.1秒。若是在0.1秒以內沒有獲得響應,就會拋出異常,錯誤以下:

123456789101112{    "error": 400,    "type": "ScriptError",    "info": {        "error": "network5",        "type": "LUA_ERROR",        "line_number": 3,        "source": "[string \"function main(splash)\r...\"]",        "message": "Lua error: [string \"function main(splash)\r...\"]:3: network5"    },    "description": "Error happened while executing Lua script"}複製代碼

此屬性適合在網頁加載速度較慢的狀況下設置。若是超過了某個時間無響應,則直接拋出異常並忽略便可。

images_enabled

此屬性能夠設置圖片是否加載,默認狀況下是加載的。禁用該屬性後,能夠節省網絡流量並提升網頁加載速度。可是須要注意的是,禁用圖片加載可能會影響JavaScript渲染。由於禁用圖片以後,它的外層DOM節點的高度會受影響,進而影響DOM節點的位置。所以,若是JavaScript對圖片節點有操做的話,其執行就會受到影響。

另外值得注意的是,Splash使用了緩存。若是一開始加載出來了網頁圖片,而後禁用了圖片加載,再從新加載頁面,以前加載好的圖片可能還會顯示出來,這時直接重啓Splash便可。

禁用圖片加載的示例以下:

1
2
3
4
5
function main(splash, args)
splash.images_enabled = false
assert(splash:go('https://www.jd.com'))
return {png=splash:png()}
end

這樣返回的頁面截圖就不會帶有任何圖片,加載速度也會快不少。

plugins_enabled

此屬性能夠控制瀏覽器插件(如Flash插件)是否開啓。默認狀況下,此屬性是false,表示不開啓。可使用以下代碼控制其開啓和關閉:

1
splash.plugins_enabled = true/false

scroll_position

經過設置此屬性,咱們能夠控制頁面上下或左右滾動。這是一個比較經常使用的屬性,示例以下:

1
2
3
4
5
function main(splash, args)
assert(splash:go('https://www.taobao.com'))
splash.scroll_position = {y=400}
return {png=splash:png()}
end

這樣咱們就能夠控制頁面向下滾動400像素值,結果如圖7-10所示。

圖7-10 運行結果

若是要讓頁面左右滾動,能夠傳入x參數,代碼以下:

1
splash.scroll_position = {x=100, y=200}

6. Splash對象的方法

除了前面介紹的屬性外,Splash對象還有以下方法。

go()

該方法用來請求某個連接,並且它能夠模擬GET和POST請求,同時支持傳入請求頭、表單等數據,其用法以下:

1
ok, reason = splash:go{url, baseurl=nil, headers=nil, http_method="GET", body=nil, formdata=nil}

其參數說明以下。

  • url:請求的URL。
  • baseurl:可選參數,默認爲空,表示資源加載相對路徑。
  • headers:可選參數,默認爲空,表示請求頭。
  • http_method:可選參數,默認爲GET,同時支持POST
  • body:可選參數,默認爲空,發POST請求時的表單數據,使用的Content-typeapplication/json
  • formdata:可選參數,默認爲空,POST的時候的表單數據,使用的Content-typeapplication/x-www-form-urlencoded

該方法的返回結果是結果ok和緣由reason的組合,若是ok爲空,表明網頁加載出現了錯誤,此時reason變量中包含了錯誤的緣由,不然證實頁面加載成功。示例以下:

123456function main(splash, args)  local ok, reason = splash:go{"http://httpbin.org/post", http_method="POST", body="name=Germey"}  if ok then        return splash:html()  endend複製代碼

這裏咱們模擬了一個POST請求,並傳入了POST的表單數據,若是成功,則返回頁面的源代碼。

運行結果以下:

1234567891011121314151617181920212223<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{  "args": {},   "data": "",   "files": {},   "form": {    "name": "Germey"  },   "headers": {    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",     "Accept-Encoding": "gzip, deflate",     "Accept-Language": "en,*",     "Connection": "close",     "Content-Length": "11",     "Content-Type": "application/x-www-form-urlencoded",     "Host": "httpbin.org",     "Origin": "null",     "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/9.0 Safari/602.1"  },   "json": null,   "origin": "60.207.237.85",   "url": "http://httpbin.org/post"}</pre></body></html>複製代碼

能夠看到,咱們成功實現了POST請求併發送了表單數據。

wait()

此方法能夠控制頁面的等待時間,使用方法以下:

1
ok, reason = splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}

參數說明以下。

  • time:等待的秒數。
  • cancel_on_redirect:可選參數,默認爲false,表示若是發生了重定向就中止等待,並返回重定向結果。
  • cancel_on_error:可選參數,默認爲false,表示若是發生了加載錯誤,就中止等待。

返回結果一樣是結果ok和緣由reason的組合。

咱們用一個實例感覺一下:

12345function main(splash)    splash:go("https://www.taobao.com")    splash:wait(2)    return {html=splash:html()}end複製代碼

這能夠實現訪問淘寶並等待2秒,隨後返回頁面源代碼的功能。

jsfunc()

此方法能夠直接調用JavaScript定義的方法,可是所調用的方法須要用雙中括號包圍,這至關於實現了JavaScript方法到Lua腳本的轉換。示例以下:

123456789101112function main(splash, args)  local get_div_count = splash:jsfunc([[  function () {    var body = document.body;    var divs = body.getElementsByTagName('div');    return divs.length;  }  ]])  splash:go("https://www.baidu.com")  return ("There are %s DIVs"):format(    get_div_count())end複製代碼

運行結果以下:

1
There are 21 DIVs

首先,咱們聲明瞭一個JavaScript定義的方法,而後在頁面加載成功後調用了此方法計算出了頁面中div節點的個數。

關於JavaScript到Lua腳本的更多轉換細節,能夠參考官方文檔:splash.readthedocs.io/en/stable/s…

evaljs()

此方法能夠執行JavaScript代碼並返回最後一條JavaScript語句的返回結果,使用方法以下:

1
result = splash:evaljs(js)

好比,能夠用下面的代碼來獲取頁面標題:

1
local title = splash:evaljs("document.title")

runjs()

此方法能夠執行JavaScript代碼,它與evaljs()的功能相似,可是更偏向於執行某些動做或聲明某些方法。例如:

1
2
3
4
5
6
function main(splash, args)
splash:go("https://www.baidu.com")
splash:runjs("foo = function() { return 'bar' }")
local result = splash:evaljs("foo()")
return result
end

這裏咱們用runjs()先聲明瞭一個JavaScript定義的方法,而後經過evaljs()來調用獲得的結果。

運行結果以下:

1
bar

autoload()

此方法能夠設置每一個頁面訪問時自動加載的對象,使用方法以下:

1
ok, reason = splash:autoload{source_or_url, source=nil, url=nil}

參數說明以下。

  • source_or_url:JavaScript代碼或者JavaScript庫連接。
  • source:JavaScript代碼。
  • url:JavaScript庫連接

可是此方法只負責加載JavaScript代碼或庫,不執行任何操做。若是要執行操做,能夠調用evaljs()runjs()方法。示例以下:

1
2
3
4
5
6
7
8
9
function main(splash, args)
splash:autoload([[
function get_document_title(){
return document.title;
}
]])
splash:go("https://www.baidu.com")
return splash:evaljs("get_document_title()")
end

這裏咱們調用autoload()方法聲明瞭一個JavaScript方法,而後經過evaljs()方法來執行此JavaScript方法。

運行結果以下:

1
百度一下,你就知道

另外,咱們也可使用autoload()方法加載某些方法庫,如jQuery,示例以下:

1
2
3
4
5
6
function main(splash, args)
assert(splash:autoload("https://code.jquery.com/jquery-2.1.3.min.js"))
assert(splash:go("https://www.taobao.com"))
local version = splash:evaljs("$.fn.jquery")
return 'JQuery version: ' .. version
end

運行結果以下:

1
JQuery version: 2.1.3

call_later()

此方法能夠經過設置定時任務和延遲時間來實現任務延時執行,而且能夠在執行前經過cancel()方法從新執行定時任務。示例以下:

1
2
3
4
5
6
7
8
9
10
11
function main(splash, args)
local snapshots = {}
local timer = splash:call_later(function()
snapshots["a"] = splash:png()
splash:wait(1.0)
snapshots["b"] = splash:png()
end, 0.2)
splash:go("https://www.taobao.com")
splash:wait(3.0)
return snapshots
end

這裏咱們設置了一個定時任務,0.2秒的時候獲取網頁截圖,而後等待1秒,1.2秒時再次獲取網頁截圖,訪問的頁面是淘寶,最後將截圖結果返回。運行結果如圖7-11所示。

圖7-11 運行結果

能夠發現,第一次截圖時網頁尚未加載出來,截圖爲空,第二次網頁便加載成功了。

http_get()

此方法能夠模擬發送HTTP的GET請求,使用方法以下:

1
response = splash:http_get{url, headers=nil, follow_redirects=true}

參數說明以下。

  • url:請求URL。
  • headers:可選參數,默認爲空,請求頭。
  • follow_redirects:可選參數,表示是否啓動自動重定向,默認爲true

示例以下:

1
2
3
4
5
6
7
8
9
function main(splash, args)
local treat = require("treat")
local response = splash:http_get("http://httpbin.org/get")
return {
html=treat.as_string(response.body),
url=response.url,
status=response.status
}
end

運行結果以下:

12345678910111213141516Splash Response: Objecthtml: String (length 355){  "args": {},   "headers": {    "Accept-Encoding": "gzip, deflate",     "Accept-Language": "en,*",     "Connection": "close",     "Host": "httpbin.org",     "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/9.0 Safari/602.1"  },   "origin": "60.207.237.85",   "url": "http://httpbin.org/get"}status: 200url: "http://httpbin.org/get"複製代碼

http_post()

http_get()方法相似,此方法用來模擬發送POST請求,不過多了一個參數body,使用方法以下:

1
response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}

參數說明以下。

  • url:請求URL。
  • headers:可選參數,默認爲空,請求頭。
  • follow_redirects:可選參數,表示是否啓動自動重定向,默認爲true
  • body:可選參數,即表單數據,默認爲空。

咱們用實例感覺一下:

function main(splash, args)  local treat = require("treat")  local json = require("json")  local response = splash:http_post{"http://httpbin.org/post",           body=json.encode({name="Germey"}),      headers={["content-type"]="application/json"}    }    return {    html=treat.as_string(response.body),    url=response.url,    status=response.status    }end複製代碼

運行結果以下:

123456789101112131415161718192021222324Splash Response: Objecthtml: String (length 533){  "args": {},   "data": "{\"name\": \"Germey\"}",   "files": {},   "form": {},   "headers": {    "Accept-Encoding": "gzip, deflate",     "Accept-Language": "en,*",     "Connection": "close",     "Content-Length": "18",     "Content-Type": "application/json",     "Host": "httpbin.org",     "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/9.0 Safari/602.1"  },   "json": {    "name": "Germey"  },   "origin": "60.207.237.85",   "url": "http://httpbin.org/post"}status: 200url: "http://httpbin.org/post"複製代碼

能夠看到,這裏咱們成功模擬提交了POST請求併發送了表單數據。

set_content()

此方法用來設置頁面的內容,示例以下:

1
2
3
4
function main(splash)
assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
return splash:png()
end

運行結果如圖7-12所示。

圖7-12 運行結果

html()

此方法用來獲取網頁的源代碼,它是很是簡單又經常使用的方法。示例以下:

1
2
3
4
function main(splash, args)
splash:go("https://httpbin.org/get")
return splash:html()
end

運行結果以下:

1234567891011121314<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{  "args": {},   "headers": {    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",     "Accept-Encoding": "gzip, deflate",     "Accept-Language": "en,*",     "Connection": "close",     "Host": "httpbin.org",     "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/9.0 Safari/602.1"  },   "origin": "60.207.237.85",   "url": "https://httpbin.org/get"}</pre></body></html>複製代碼

png()

此方法用來獲取PNG格式的網頁截圖,示例以下:

1
2
3
4
function main(splash, args)
splash:go("https://www.taobao.com")
return splash:png()
end

jpeg()

此方法用來獲取JPEG格式的網頁截圖,示例以下:

1
2
3
4
function main(splash, args)
splash:go("https://www.taobao.com")
return splash:jpeg()
end

har()

此方法用來獲取頁面加載過程描述,示例以下:

1
2
3
4
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:har()
end

運行結果如圖7-13所示,其中顯示了頁面加載過程當中每一個請求記錄的詳情。

圖7-13 運行結果

url()

此方法能夠獲取當前正在訪問的URL,示例以下:

1
2
3
4
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:url()
end

運行結果以下:

1
https://www.baidu.com/

get_cookies()

此方法能夠獲取當前頁面的Cookies,示例以下:

1
2
3
4
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:get_cookies()
end

運行結果以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Splash Response: Array[2]
0: Object
domain: ".baidu.com"
expires: "2085-08-21T20:13:23Z"
httpOnly: false
name: "BAIDUID"
path: "/"
secure: false
value: "C1263A470B02DEF45593B062451C9722:FG=1"
1: Object
domain: ".baidu.com"
expires: "2085-08-21T20:13:23Z"
httpOnly: false
name: "BIDUPSID"
path: "/"
secure: false
value: "C1263A470B02DEF45593B062451C9722"

add_cookie()

此方法能夠爲當前頁面添加Cookie,用法以下:

1
cookies = splash:add_cookie{name, value, path=nil, domain=nil, expires=nil, httpOnly=nil, secure=nil}

該方法的各個參數表明Cookie的各個屬性。

示例以下:

1
2
3
4
5
function main(splash)
splash:add_cookie{"sessionid", "237465ghgfsd", "/", domain="http://example.com"}
splash:go("http://example.com/")
return splash:html()
end

clear_cookies()

此方法能夠清除全部的Cookies,示例以下:

1
2
3
4
5
function main(splash)
splash:go("https://www.baidu.com/")
splash:clear_cookies()
return splash:get_cookies()
end

這裏咱們清除了全部的Cookies,而後調用get_cookies()將結果返回。

運行結果以下:

1
Splash Response: Array[0]

能夠看到,Cookies被所有清空,沒有任何結果。

get_viewport_size()

此方法能夠獲取當前瀏覽器頁面的大小,即寬高,示例以下:

1
2
3
4
function main(splash)
splash:go("https://www.baidu.com/")
return splash:get_viewport_size()
end

運行結果以下:

1
2
3
Splash Response: Array[2]
0: 1024
1: 768

set_viewport_size()

此方法能夠設置當前瀏覽器頁面的大小,即寬高,用法以下:

1
splash:set_viewport_size(width, height)

例如,這裏訪問一個寬度自適應的頁面:

1
2
3
4
5
function main(splash)
splash:set_viewport_size(400, 700)
assert(splash:go("http://cuiqingcai.com"))
return splash:png()
end

運行結果如圖7-14所示。

圖7-14 運行結果

set_viewport_full()

此方法能夠設置瀏覽器全屏顯示,示例以下:

1
2
3
4
5
function main(splash)
splash:set_viewport_full()
assert(splash:go("http://cuiqingcai.com"))
return splash:png()
end

set_user_agent()

此方法能夠設置瀏覽器的User-Agent,示例以下:

1
2
3
4
5
function main(splash)
splash:set_user_agent('Splash')
splash:go("http://httpbin.org/get")
return splash:html()
end

這裏咱們將瀏覽器的User-Agent設置爲Splash,運行結果以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en,*",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "Splash"
},
"origin": "60.207.237.85",
"url": "http://httpbin.org/get"
}
</pre></body></html>

能夠看到,此處User-Agent被成功設置。

set_custom_headers()

此方法能夠設置請求頭,示例以下:

1
2
3
4
5
6
7
8
function main(splash)
splash:set_custom_headers({
["User-Agent"] = "Splash",
["Site"] = "Splash",
})
splash:go("http://httpbin.org/get")
return splash:html()
end

這裏咱們設置了請求頭中的User-AgentSite屬性,運行結果以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en,*",
"Connection": "close",
"Host": "httpbin.org",
"Site": "Splash",
"User-Agent": "Splash"
},
"origin": "60.207.237.85",
"url": "http://httpbin.org/get"
}
</pre></body></html>

select()

該方法能夠選中符合條件的第一個節點,若是有多個節點符合條件,則只會返回一個,其參數是CSS選擇器。示例以下:

1
2
3
4
5
6
7
function main(splash)
splash:go("https://www.baidu.com/")
input = splash:select("#kw")
input:send_text('Splash')
splash:wait(3)
return splash:png()
end

這裏咱們首先訪問了百度,而後選中了搜索框,隨後調用了send_text()方法填寫了文本,而後返回網頁截圖。

結果如圖7-15所示,能夠看到,咱們成功填寫了輸入框。

圖7-15 運行結果

select_all()

此方法能夠選中全部符合條件的節點,其參數是CSS選擇器。示例以下:

1
2
3
4
5
6
7
8
9
10
11
function main(splash)
local treat = require('treat')
assert(splash:go("http://quotes.toscrape.com/"))
assert(splash:wait(0.5))
local texts = splash:select_all('.quote .text')
local results = {}
for index, text in ipairs(texts) do
results[index] = text.node.innerHTML
end
return treat.as_array(results)
end

這裏咱們經過CSS選擇器選中了節點的正文內容,隨後遍歷了全部節點,將其中的文本獲取下來。

運行結果以下:

1
2
3
4
5
6
7
8
9
10
11
Splash Response: Array[10]
0: "「The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.」"
1: "「It is our choices, Harry, that show what we truly are, far more than our abilities.」"
2: 「There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.」
3: "「The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.」"
4: "「Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.」"
5: "「Try not to become a man of success. Rather become a man of value.」"
6: "「It is better to be hated for what you are than to be loved for what you are not.」"
7: "「I have not failed. I've just found 10,000 ways that won't work.」"
8: "「A woman is like a tea bag; you never know how strong it is until it's in hot water.」"
9: "「A day without sunshine is like, you know, night.」"

能夠發現,咱們成功地將10個節點的正文內容獲取了下來。

mouse_click()

此方法能夠模擬鼠標點擊操做,傳入的參數爲座標值xy。此外,也能夠直接選中某個節點,而後調用此方法,示例以下:

1
2
3
4
5
6
7
8
9
function main(splash)
splash:go("https://www.baidu.com/")
input = splash:select("#kw")
input:send_text('Splash')
submit = splash:select('#su')
submit:mouse_click()
splash:wait(3)
return splash:png()
end

這裏咱們首先選中頁面的輸入框,輸入了文本,而後選中「提交」按鈕,調用了mouse_click()方法提交查詢,而後頁面等待三秒,返回截圖,結果如圖7-16所示。

圖7-16 運行結果

能夠看到,這裏咱們成功獲取了查詢後的頁面內容,模擬了百度搜索操做。

前面介紹了Splash的經常使用API操做,還有一些API在這再也不一一介紹,更加詳細和權威的說明能夠參見官方文檔splash.readthedocs.io/en/stable/s…,此頁面介紹了Splash對象的全部API操做。另外,還有針對頁面元素的API操做,連接爲splash.readthedocs.io/en/stable/s…

7. Splash API調用

前面說明了Splash Lua腳本的用法,但這些腳本是在Splash頁面中測試運行的,如何才能利用Splash渲染頁面呢?怎樣才能和Python程序結合使用並抓取JavaScript渲染的頁面呢?

其實Splash給咱們提供了一些HTTP API接口,咱們只須要請求這些接口並傳遞相應的參數便可,下面簡要介紹這些接口。

render.html

此接口用於獲取JavaScript渲染的頁面的HTML代碼,接口地址就是Splash的運行地址加此接口名稱,例如http://localhost:8050/render.html。能夠用curl來測試一下:

1
curl http://localhost:8050/render.html?url=https://www.baidu.com

咱們給此接口傳遞了一個url參數來指定渲染的URL,返回結果即頁面渲染後的源代碼。

若是用Python實現的話,代碼以下:

1
2
3
4
import requests
url = 'http://localhost:8050/render.html?url=https://www.baidu.com'
response = requests.get(url)
print(response.text)

這樣就能夠成功輸出百度頁面渲染後的源代碼了。

另外,此接口還能夠指定其餘參數,好比經過wait指定等待秒數。若是要確保頁面徹底加載出來,能夠增長等待時間,例如:

1
2
3
4
import requests
url = 'http://localhost:8050/render.html?url=https://www.taobao.com&wait=5'
response = requests.get(url)
print(response.text)

此時獲得響應的時間就會相應變長,好比這裏會等待5秒多鍾才能獲取淘寶頁面的源代碼。

另外,此接口還支持代理設置、圖片加載設置、Headers設置、請求方法設置,具體的用法能夠參見官方文檔splash.readthedocs.io/en/stable/a…

render.png

此接口能夠獲取網頁截圖,其參數比render.html多了幾個,好比經過widthheight來控制寬高,它返回的是PNG格式的圖片二進制數據。示例以下:

1
curl http://localhost:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700

這裏咱們傳入了widthheight來設置頁面大小爲1000×700像素。

若是用Python實現,能夠將返回的二進制數據保存爲PNG格式的圖片,具體以下:

1
2
3
4
5
6
import requests
url = 'http://localhost:8050/render.png?url=https://www.jd.com&wait=5&width=1000&height=700'
response = requests.get(url)
with open('taobao.png', 'wb') as f:
f.write(response.content)

獲得的圖片如圖7-17所示。

圖7-17 運行結果

這樣咱們就成功獲取了京東首頁渲染完成後的頁面截圖,詳細的參數設置能夠參考官網文檔splash.readthedocs.io/en/stable/a…

render.jpeg

此接口和render.png相似,不過它返回的是JPEG格式的圖片二進制數據。

另外,此接口比render.png多了參數quality,它用來設置圖片質量。

render.har

此接口用於獲取頁面加載的HAR數據,示例以下:

1
curl http://localhost:8050/render.har?url=https://www.jd.com&wait=5

它的返回結果(如圖7-18所示)很是多,是一個JSON格式的數據,其中包含頁面加載過程當中的HAR數據。

圖7-18 運行結果

render.json

此接口包含了前面接口的全部功能,返回結果是JSON格式,示例以下:

1
curl http://localhost:8050/render.json?url=https://httpbin.org

結果以下:

1
{"title": "httpbin(1): HTTP Client Testing Service", "url": "https://httpbin.org/", "requestedUrl": "https://httpbin.org/", "geometry": [0, 0, 1024, 768]}

能夠看到,這裏以JSON形式返回了相應的請求數據。

咱們能夠經過傳入不一樣參數控制其返回結果。好比,傳入html=1,返回結果即會增長源代碼數據;傳入png=1,返回結果即會增長頁面PNG截圖數據;傳入har=1,則會得到頁面HAR數據。例如:

1
curl http://localhost:8050/render.json?url=https://httpbin.org&html=1&har=1

這樣返回的JSON結果會包含網頁源代碼和HAR數據。

此外還有更多參數設置,具體能夠參考官方文檔:splash.readthedocs.io/en/stable/a…

execute

此接口才是最爲強大的接口。前面說了不少Splash Lua腳本的操做,用此接口即可實現與Lua腳本的對接。

前面的render.html和render.png等接口對於通常的JavaScript渲染頁面是足夠了,可是若是要實現一些交互操做的話,它們仍是無能爲力,這裏就須要使用execute接口了。

咱們先實現一個最簡單的腳本,直接返回數據:

1
2
3
function main(splash)
return 'hello'
end

而後將此腳本轉化爲URL編碼後的字符串,拼接到execute接口後面,示例以下:

1
curl http://localhost:8050/execute?lua_source=function+main%28splash%29%0D%0A++return+%27hello%27%0D%0Aend

運行結果以下:

1
hello

這裏咱們經過lua_source參數傳遞了轉碼後的Lua腳本,經過execute接口獲取了最終腳本的執行結果。

這裏咱們更加關心的確定是如何用Python來實現,上例用Python實現的話,代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
import requests
from urllib.parse import quote
lua = '''
function main(splash)
return 'hello'
end
'''
url = 'http://localhost:8050/execute?lua_source=' + quote(lua)
response = requests.get(url)
print(response.text)

運行結果以下:

1
hello

這裏咱們用Python中的三引號將Lua腳本包括起來,而後用urllib.parse模塊裏的quote()方法將腳本進行URL轉碼,隨後構造了Splash請求URL,將其做爲lua_source參數傳遞,這樣運行結果就會顯示Lua腳本執行後的結果。

咱們再經過實例看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
from urllib.parse import quote
lua = '''
function main(splash, args)
local treat = require("treat")
local response = splash:http_get("http://httpbin.org/get")
return {
html=treat.as_string(response.body),
url=response.url,
status=response.status
}
end
'''
url = 'http://localhost:8050/execute?lua_source=' + quote(lua)
response = requests.get(url)
print(response.text)

運行結果以下:

1
{"url": "http://httpbin.org/get", "status": 200, "html": "{\n \"args\": {}, \n \"headers\": {\n \"Accept-Encoding\": \"gzip, deflate\", \n \"Accept-Language\": \"en,*\", \n \"Connection\": \"close\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/9.0 Safari/602.1\"\n }, \n \"origin\": \"60.207.237.85\", \n \"url\": \"http://httpbin.org/get\"\n}\n"}

能夠看到,返回結果是JSON形式,咱們成功獲取了請求的URL、狀態碼和網頁源代碼。

如此一來,咱們以前所說的Lua腳本都可以用此方式與Python進行對接,全部網頁的動態渲染、模擬點擊、表單提交、頁面滑動、延時等待後的一些結果都可以自由控制,獲取頁面源碼和截圖也都不在話下。

到如今爲止,咱們能夠用Python和Splash實現JavaScript渲染的頁面的抓取了。除了Selenium,本節所說的Splash一樣能夠作到很是強大的渲染功能,同時它也不須要瀏覽器便可渲染,使用很是方便。


本資源首發於崔慶才的我的博客靜覓: Python3網絡爬蟲開發實戰教程 | 靜覓

如想了解更多爬蟲資訊,請關注個人我的微信公衆號:進擊的Coder

weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)

相關文章
相關標籤/搜索