[Python3網路爬蟲開發實戰] --Splash的使用
Splash是一個JavaScript渲染服務,是一個帶有HTTP API的輕量級瀏覽器,同時它對接了Python中的Twisted和QT庫。利用它同樣可以實現動態渲染頁面的抓取。
1. 功能介紹
利用Splash可以實現如下功能:
- 非同步方式處理多個網頁渲染過程;
- 獲取渲染後的頁面的原始碼或截圖;
- 通過關閉圖片渲染或者使用Adblock規則來加快頁面渲染速度;
- 可執行特定的JavaScript指令碼;
- 可通過Lua指令碼來控制頁面渲染過程;
- 獲取渲染的詳細過程並通過HAR(HTTP Archive)格式呈現。
2. 準備工作
在開始之前,請確保已經正確安裝好了Splash並可以正常執行服務(pip install Splash)。
3. 例項引入
首先,通過Splash提供的Web頁面來測試其渲染過程。例如,在本機8050埠上執行了Splash服務,開啟http://localhost:8050/即可看到其Web頁面,如圖1所示。
在圖1右側,呈現的是一個渲染示例。可以看到,上方有一個輸入框,預設是http://google.com,這裡換成百度測試一下,將內容更改為https://www.baidu.com,然後點選Render me按鈕開始渲染,結果如圖2所示。
可以看到,網頁的返回結果呈現了渲染截圖、HAR載入統計資料、網頁的原始碼。
通過HAR的結果可以看到,Splash執行了整個網頁的渲染過程,包括CSS、JavaScript的載入等過程,呈現的頁面和在瀏覽器中得到的結果完全一致。
那麼,這個過程由什麼來控制呢?重新返回首頁,可以看到實際上是有一段指令碼,內容如下:
function 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指令碼的入口和執行方式。
入口及返回值
首先,來看一個基本例項:
function 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變數,隨後將其返回。
注意,在這裡定義的方法名稱叫作main()。這個名稱必須是固定的,Splash會預設呼叫這個方法。
該方法的返回值既可以是字典形式,也可以是字串形式,最後都會轉化為Splash HTTP Response,例如:
function main(splash)
return {hello="world!"}
end
返回了一個字典形式的內容。例如:
function 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個站點的截圖,如圖3所示。
在指令碼內呼叫的wait()方法類似於Python中的sleep(),其引數為等待的秒數。當Splash執行到此方法時,它會轉而去處理其他任務,然後在指定的時間過後再回來繼續處理。
這裡值得注意的是,Lua指令碼中的字串拼接和Python不同,它使用的是…操作符,而不是+。如果有必要,可以簡單瞭解一下Lua指令碼的語法,詳見http://www.runoob.com/lua/lua-basic-syntax.html。
另外,這裡做了載入時的異常檢測。go()方法會返回載入頁面的結果狀態,如果頁面出現4xx或5xx狀態碼,ok變數就為空,就不會返回載入後的圖片。
5. Splash物件屬性
注意到,前面例子中main()方法的第一個引數是splash,這個物件非常重要,它類似於Selenium中的WebDriver物件,可以呼叫它的一些屬性和方法來控制載入過程。接下來,先看下它的屬性。
args
該屬性可以獲取載入時配置的引數,比如URL,如果為GET請求,它還可以獲取GET請求引數;如果為POST請求,它可以獲取表單提交的資料。Splash也支援使用第二個引數直接作為args,例如:
function main(splash, args)
local url = args.url
end
這裡第二個引數args就相當於splash.args屬性,以上程式碼等價於:
function main(splash)
local url = splash.args.url
end
js_enabled
這個屬性是Splash的JavaScript執行開關,可以將其配置為true或false來控制是否執行JavaScript程式碼,預設為true。例如,這裡禁止執行JavaScript程式碼:
function 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程式碼,此時執行結果就會丟擲異常:
{
"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秒之內沒有得到響應,就會丟擲異常,錯誤如下:
{
"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即可。
禁用圖片載入的示例如下:
function main(splash, args)
splash.images_enabled = false
assert(splash:go('https://www.jd.com'))
return {png=splash:png()}
end
這樣返回的頁面截圖就不會帶有任何圖片,載入速度也會快很多。
plugins_enabled
此屬性可以控制瀏覽器外掛(如Flash外掛)是否開啟。預設情況下,此屬性是false,表示不開啟。可以使用如下程式碼控制其開啟和關閉:
splash.plugins_enabled = true/false
scroll_position
通過設定此屬性,可以控制頁面上下或左右滾動。這是一個比較常用的屬性,示例如下:
function main(splash, args)
assert(splash:go('https://www.taobao.com'))
splash.scroll_position = {y=400}
return {png=splash:png()}
end
這樣就可以控制頁面向下滾動400畫素值,結果如圖4所示。
如果要讓頁面左右滾動,可以傳入x引數,程式碼如下:
splash.scroll_position = {x=100, y=200}
6. Splash物件的方法
除了前面介紹的屬性外,Splash物件還有如下方法。
go()
該方法用來請求某個連結,而且它可以模擬GET和POST請求,同時支援傳入請求頭、表單等資料,其用法如下:
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-type為application/json。
- formdata:可選引數,預設為空,POST的時候的表單資料,使用的Content-type為application/x-www-form-urlencoded。
該方法的返回結果是結果ok和原因reason的組合,如果ok為空,代表網頁載入出現了錯誤,此時reason變數中包含了錯誤的原因,否則證明頁面載入成功。示例如下:
function main(splash, args)
local ok, reason = splash:go{"http://httpbin.org/post", http_method="POST", body="name=Germey"}
if ok then
return splash:html()
end
end
這裡模擬了一個POST請求,並傳入了POST的表單資料,如果成功,則返回頁面的原始碼。
執行結果如下:
<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()
此方法可以控制頁面的等待時間,使用方法如下:
ok, reason = splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}
引數說明如下。
- time:等待的秒數。
- cancel_on_redirect:可選引數,預設為false,表示如果發生了重定向就停止等待,並返回重定向結果。
- cancel_on_error:可選引數,預設為false,表示如果發生了載入錯誤,就停止等待。
返回結果同樣是結果ok和原因reason的組合。
用一個例項感受一下:
function main(splash)
splash:go("https://www.taobao.com")
splash:wait(2)
return {html=splash:html()}
end
這可以實現訪問淘寶並等待2秒,隨後返回頁面原始碼的功能。
jsfunc()
此方法可以直接呼叫JavaScript定義的方法,但是所呼叫的方法需要用雙中括號包圍,這相當於實現了JavaScript方法到Lua指令碼的轉換。示例如下:
function 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
執行結果如下:
There are 21 DIVs
首先,宣告瞭一個JavaScript定義的方法,然後在頁面載入成功後呼叫了此方法計算出了頁面中div節點的個數。
關於JavaScript到Lua指令碼的更多轉換細節,可以參考官方文件:https://splash.readthedocs.io/en/stable/scripting-ref.html#splash-jsfunc。
evaljs()
此方法可以執行JavaScript程式碼並返回最後一條JavaScript語句的返回結果,使用方法如下:
result = splash:evaljs(js)
比如,可以用下面的程式碼來獲取頁面標題:
local title = splash:evaljs("document.title")
runjs()
此方法可以執行JavaScript程式碼,它與evaljs()的功能類似,但是更偏向於執行某些動作或宣告某些方法。例如:
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()來呼叫得到的結果。
執行結果如下:
bar
autoload()
此方法可以設定每個頁面訪問時自動載入的物件,使用方法如下:
ok, reason = splash:autoload{source_or_url, source=nil, url=nil}
引數說明如下。
- source_or_url:JavaScript程式碼或者JavaScript庫連結。
- source:JavaScript程式碼。
- url:JavaScript庫連結
但是此方法只負責載入JavaScript程式碼或庫,不執行任何操作。如果要執行操作,可以呼叫evaljs()或runjs()方法。示例如下:
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方法。
執行結果如下:
百度一下,你就知道
另外,也可以使用autoload()方法載入某些方法庫,如jQuery,示例如下:
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
執行結果如下:
JQuery version: 2.1.3
call_later()
此方法可以通過設定定時任務和延遲時間來實現任務延時執行,並且可以在執行前通過cancel()方法重新執行定時任務。示例如下:
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秒時再次獲取網頁截圖,訪問的頁面是淘寶,最後將截圖結果返回。執行結果如圖5所示。
可以發現,第一次截圖時網頁還沒有載入出來,截圖為空,第二次網頁便載入成功了。
http_get()
此方法可以模擬傳送HTTP的GET請求,使用方法如下:
response = splash:http_get{url, headers=nil, follow_redirects=true}
引數說明如下。
- url:請求URL。
- headers:可選引數,預設為空,請求頭。
- follow_redirects:可選引數,表示是否啟動自動重定向,預設為true。
示例如下:
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
執行結果如下:
Splash Response: Object
html: 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: 200
url: "http://httpbin.org/get"
http_post()
和http_get()方法類似,此方法用來模擬傳送POST請求,不過多了一個引數body,使用方法如下:
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
執行結果如下:
Splash Response: Object
html: 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: 200
url: "http://httpbin.org/post"
可以看到,這裡成功模擬提交了POST請求併傳送了表單資料。
set_content()
此方法用來設定頁面的內容,示例如下:
function main(splash)
assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
return splash:png()
end
執行結果如圖6所示。
html()
此方法用來獲取網頁的原始碼,它是非常簡單又常用的方法。示例如下:
function main(splash, args)
splash:go("https://httpbin.org/get")
return splash:html()
end
執行結果如下:
<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格式的網頁截圖,示例如下:
function main(splash, args)
splash:go("https://www.taobao.com")
return splash:png()
end
jpeg()
此方法用來獲取JPEG格式的網頁截圖,示例如下:
function main(splash, args)
splash:go("https://www.taobao.com")
return splash:jpeg()
end
har()
此方法用來獲取頁面載入過程描述,示例如下:
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:har()
end
執行結果如圖7所示,其中顯示了頁面載入過程中每個請求記錄的詳情。
url()
此方法可以獲取當前正在訪問的URL,示例如下:
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:url()
end
執行結果如下:
https://www.baidu.com/
get_cookies()
此方法可以獲取當前頁面的Cookies,示例如下:
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:get_cookies()
end
執行結果如下:
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,用法如下:
cookies = splash:add_cookie{name, value, path=nil, domain=nil, expires=nil, httpOnly=nil, secure=nil}
該方法的各個引數代表Cookie的各個屬性。
示例如下:
function main(splash)
splash:add_cookie{"sessionid", "237465ghgfsd", "/", domain="http://example.com"}
splash:go("http://example.com/")
return splash:html()
end
clear_cookies()
此方法可以清除所有的Cookies,示例如下:
function main(splash)
splash:go("https://www.baidu.com/")
splash:clear_cookies()
return splash:get_cookies()
end
這裡清除了所有的Cookies,然後呼叫get_cookies()將結果返回。
執行結果如下:
Splash Response: Array[0]
可以看到,Cookies被全部清空,沒有任何結果。
get_viewport_size()
此方法可以獲取當前瀏覽器頁面的大小,即寬高,示例如下:
function main(splash)
splash:go("https://www.baidu.com/")
return splash:get_viewport_size()
end
執行結果如下:
Splash Response: Array[2]
0: 1024
1: 768
set_viewport_size()
此方法可以設定當前瀏覽器頁面的大小,即寬高,用法如下:
splash:set_viewport_size(width, height)
例如,這裡訪問一個寬度自適應的頁面:
function main(splash)
splash:set_viewport_size(400, 700)
assert(splash:go("http://cuiqingcai.com"))
return splash:png()
end
執行結果如圖8所示。
set_viewport_full()
此方法可以設定瀏覽器全屏顯示,示例如下:
function main(splash)
splash:set_viewport_full()
assert(splash:go("http://cuiqingcai.com"))
return splash:png()
end
set_user_agent()
此方法可以設定瀏覽器的User-Agent,示例如下:
function main(splash)
splash:set_user_agent('Splash')
splash:go("http://httpbin.org/get")
return splash:html()
end
這裡將瀏覽器的User-Agent設定為Splash,執行結果如下:
<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()
此方法可以設定請求頭,示例如下:
function main(splash)
splash:set_custom_headers({
["User-Agent"] = "Splash",
["Site"] = "Splash",
})
splash:go("http://httpbin.org/get")
return splash:html()
end
這裡設定了請求頭中的User-Agent和Site屬性,執行結果如下:
<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選擇器。示例如下:
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()方法填寫了文字,然後返回網頁截圖。
結果如圖9所示,可以看到成功填寫了輸入框。
select_all()
此方法可以選中所有符合條件的節點,其引數是CSS選擇器。示例如下:
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選擇器選中了節點的正文內容,隨後遍歷了所有節點,將其中的文字獲取下來。
執行結果如下:
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()
此方法可以模擬滑鼠點選操作,傳入的引數為座標值x和y。此外,也可以直接選中某個節點,然後呼叫此方法,示例如下:
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()方法提交查詢,然後頁面等待三秒,返回截圖,結果如圖10所示。
可以看到,這裡成功獲取了查詢後的頁面內容,模擬了百度搜尋操作。
前面介紹了Splash的常用API操作,還有一些API在這不再一一介紹,更加詳細和權威的說明可以參見官方文件https://splash.readthedocs.io/en/stable/scripting-ref.html,此頁面介紹了Splash物件的所有API操作。另外,還有針對頁面元素的API操作,連結為https://splash.readthedocs.io/en/stable/scripting-element-object.html。
7. Splash API呼叫
前面說明了Splash Lua指令碼的用法,但這些指令碼是在Splash頁面中測試執行的,如何才能利用Splash渲染頁面呢?怎樣才能和Python程式結合使用並抓取JavaScript渲染的頁面呢?
其實Splash提供了一些HTTP API介面,只需要請求這些介面並傳遞相應的引數即可,下面簡要介紹這些介面。
render.html
此介面用於獲取JavaScript渲染的頁面的HTML程式碼,介面地址就是Splash的執行地址加此介面名稱,例如http://localhost:8050/render.html。可以用curl來測試一下:
curl http://localhost:8050/render.html?url=https://www.baidu.com
給此介面傳遞了一個url引數來指定渲染的URL,返回結果即頁面渲染後的原始碼。
如果用Python實現的話,程式碼如下:
import requests
url = 'http://localhost:8050/render.html?url=https://www.baidu.com'
response = requests.get(url)
print(response.text)
這樣就可以成功輸出百度頁面渲染後的原始碼了。
另外,此介面還可以指定其他引數,比如通過wait指定等待秒數。如果要確保頁面完全載入出來,可以增加等待時間,例如:
import requests
url = 'http://localhost:8050/render.html?url=https://www.taobao.com&wait=5'
response = requests.get(url)
print(response.text)
此時得到響應的時間就會相應變長,比如這裡會等待5秒多鍾才能獲取淘寶頁面的原始碼。
另外,此介面還支援代理設定、圖片載入設定、Headers設定、請求方法設定,具體的用法可以參見官方文件https://splash.readthedocs.io/en/stable/api.html#render-html。
render.png
此介面可以獲取網頁截圖,其引數比render.html多了幾個,比如通過width和height來控制寬高,它返回的是PNG格式的圖片二進位制資料。示例如下:
curl http://localhost:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700
這裡傳入了width和height來設定頁面大小為1000×700畫素。
如果用Python實現,可以將返回的二進位制資料儲存為PNG格式的圖片,具體如下:
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)
得到的圖片如圖11所示。
這樣就成功獲取了京東首頁渲染完成後的頁面截圖,詳細的引數設定可以參考官網文件https://splash.readthedocs.io/en/stable/api.html#render-png。
render.jpeg
此介面和render.png類似,不過它返回的是JPEG格式的圖片二進位制資料。
另外,此介面比render.png多了引數quality,它用來設定圖片質量。
render.har
此介面用於獲取頁面載入的HAR資料,示例如下:
curl http://localhost:8050/render.har?url=https://www.jd.com&wait=5
它的返回結果(如圖12所示)非常多,是一個JSON格式的資料,其中包含頁面載入過程中的HAR資料。
render.json
此介面包含了前面介面的所有功能,返回結果是JSON格式,示例如下:
curl http://localhost:8050/render.json?url=https://httpbin.org
結果如下:
{"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資料。例如:
curl http://localhost:8050/render.json?url=https://httpbin.org&html=1&har=1
這樣返回的JSON結果會包含網頁原始碼和HAR資料。
此外還有更多引數設定,具體可以參考官方文件:https://splash.readthedocs.io/en/stable/api.html#render-json。
execute
此介面才是最為強大的介面。前面說了很多Splash Lua指令碼的操作,用此介面便可實現與Lua指令碼的對接。
前面的render.html和render.png等介面對於一般的JavaScript渲染頁面是足夠了,但是如果要實現一些互動操作的話,它們還是無能為力,這裡就需要使用execute介面了。
先實現一個最簡單的指令碼,直接返回資料:
function main(splash)
return 'hello'
end
然後將此指令碼轉化為URL編碼後的字串,拼接到execute介面後面,示例如下:
curl http://localhost:8050/execute?lua_source=function+main%28splash%29%0D%0A++return+%27hello%27%0D%0Aend
執行結果如下:
hello
這裡通過lua_source引數傳遞了轉碼後的Lua指令碼,通過execute介面獲取了最終指令碼的執行結果。
這裡更加關心的肯定是如何用Python來實現,上例用Python實現的話,程式碼如下:
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)
執行結果如下:
hello
這裡用Python中的三引號將Lua指令碼包括起來,然後用urllib.parse模組裡的quote()方法將指令碼進行URL轉碼,隨後構造了Splash請求URL,將其作為lua_source引數傳遞,這樣執行結果就會顯示Lua指令碼執行後的結果。
再通過例項看一下:
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)
執行結果如下:
{"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網路爬蟲開發實戰_Python3 爬蟲實戰Python爬蟲
- [Python3網路爬蟲開發實戰] Charles 的使用Python爬蟲
- Python3網路爬蟲開發實戰Python爬蟲
- 《Python3網路爬蟲開發實戰》教程||爬蟲教程Python爬蟲
- python3網路爬蟲開發實戰_Python 3開發網路爬蟲(一)Python爬蟲
- [Python3網路爬蟲開發實戰] 分散式爬蟲原理Python爬蟲分散式
- python3網路爬蟲開發實戰pdfPython爬蟲
- 《python3網路爬蟲開發實戰》--pyspiderPython爬蟲IDE
- 《Python3網路爬蟲開發實戰》開源啦!Python爬蟲
- 《Python3網路爬蟲開發實戰程式碼》基本庫使用Python爬蟲
- Python3網路爬蟲開發實戰——第3章 基本庫的使用Python爬蟲
- [Python3網路爬蟲開發實戰] 7-動態渲染頁面爬取-3-Splash負載均衡配置Python爬蟲負載
- 《Python3 網路爬蟲開發實戰》—學習筆記Python爬蟲筆記
- Python3網路爬蟲開發實戰(第二版)Python爬蟲
- 【Python3網路爬蟲開發實戰】4-解析庫的使用-1 使用XPathPython爬蟲
- 【Python3網路爬蟲開發實戰】4-解析庫的使用-3 使用pyqueryPython爬蟲
- Python3網路爬蟲開發實戰——第1章 開發環境Python爬蟲開發環境
- 我的爬蟲入門書 —— 《Python3網路爬蟲開發實戰(第二版)》爬蟲Python
- 【Python3網路爬蟲開發實戰】4-解析庫的使用-2 使用Beautiful SoupPython爬蟲
- 面前最全《崔慶才python3網路爬蟲開發實戰》Python爬蟲
- 《Python3網路爬蟲開發實戰》抽獎贈書活動Python爬蟲
- [Python3網路爬蟲開發實戰] 2-爬蟲基礎 2-網頁基礎Python爬蟲網頁
- 讀書筆記:《Python3網路爬蟲開發實戰》——第2章:爬蟲基礎筆記Python爬蟲
- python3 網路爬蟲開發實戰 貓眼top100Python爬蟲
- 【閱讀筆記】《Python3網路爬蟲開發實戰》PDF文件筆記Python爬蟲
- python3網路爬蟲開發實戰【高清掃描帶目錄】Python爬蟲
- Python3 大型網路爬蟲實戰 001 --- 搭建開發環境Python爬蟲開發環境
- 《網路爬蟲開發實戰案例》筆記爬蟲筆記
- Python 3網路爬蟲開發實戰Python爬蟲
- 網路爬蟲——爬蟲實戰(一)爬蟲
- 【Python3網路爬蟲開發實戰】3-基本庫的使用-3正規表示式Python爬蟲
- [Python3網路爬蟲開發實戰] 2-爬蟲基礎 5-代理的基本原理Python爬蟲
- python3網路爬蟲開發實戰-線上版-可複製貼上Python爬蟲
- [Python3網路爬蟲開發實戰] 2-爬蟲基礎 1-HTTP基本原理Python爬蟲HTTP
- 【Python3網路爬蟲開發實戰】3-基本庫的使用 1.1-傳送請求Python爬蟲
- Python3網路爬蟲快速入門實戰解析Python爬蟲
- [Python3網路爬蟲開發實戰] 7-動態渲染頁面爬取-1-Selenium的使用Python爬蟲
- 【Python3網路爬蟲開發實戰】3.4-抓取貓眼電影排行Python爬蟲