Python3爬蟲中Splash的知識(shí)總結(jié)
Splash是一個(gè)JavaScript渲染服務(wù),是一個(gè)帶有HTTP API的輕量級(jí)瀏覽器,同時(shí)它對(duì)接了Python中的Twisted和QT庫。利用它,我們同樣可以實(shí)現(xiàn)動(dòng)態(tài)渲染頁面的抓取。
1. 功能介紹
·利用Splash,我們可以實(shí)現(xiàn)如下功能:
·異步方式處理多個(gè)網(wǎng)頁渲染過程;
·獲取渲染后的頁面的源代碼或截圖;
·通過關(guān)閉圖片渲染或者使用Adblock規(guī)則來加快頁面渲染速度;
·可執(zhí)行特定的JavaScript腳本;
·可通過Lua腳本來控制頁面渲染過程;
·獲取渲染的詳細(xì)過程并通過HAR(HTTP Archive)格式呈現(xiàn)。
接下來,我們來了解一下它的具體用法。
2. 準(zhǔn)備工作
在開始之前,請(qǐng)確保已經(jīng)正確安裝好了Splash并可以正常運(yùn)行服務(wù)。如果沒有安裝,可以參考第1章。
3. 實(shí)例引入
首先,通過Splash提供的Web頁面來測(cè)試其渲染過程。例如,我們?cè)诒緳C(jī)8050端口上運(yùn)行了Splash服務(wù),打開http://localhost:8050/即可看到其Web頁面,如圖7-6所示。
圖7-6 Web頁面
在圖7-6右側(cè),呈現(xiàn)的是一個(gè)渲染示例??梢钥吹剑戏接幸粋€(gè)輸入框,默認(rèn)是http://google.com,這里換成百度測(cè)試一下,將內(nèi)容更改為https://www.baidu.com,然后點(diǎn)擊Render me按鈕開始渲染,結(jié)果如圖7-7所示。
圖7-7 運(yùn)行結(jié)果
可以看到,網(wǎng)頁的返回結(jié)果呈現(xiàn)了渲染截圖、HAR加載統(tǒng)計(jì)數(shù)據(jù)、網(wǎng)頁的源代碼。
通過HAR的結(jié)果可以看到,Splash執(zhí)行了整個(gè)網(wǎng)頁的渲染過程,包括CSS、JavaScript的加載等過程,呈現(xiàn)的頁面和我們?cè)跒g覽器中得到的結(jié)果完全一致。
那么,這個(gè)過程由什么來控制呢?重新返回首頁,可以看到實(shí)際上是有一段腳本,內(nèi)容如下:
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
這個(gè)腳本實(shí)際上是用Lua語言寫的腳本。即使不懂這個(gè)語言的語法,但從腳本的表面意思,我們也可以大致了解到它首先調(diào)用go()方法去加載頁面,然后調(diào)用wait()方法等待了一定時(shí)間,最后返回了頁面的源碼、截圖和HAR信息。
到這里,我們大體了解了Splash是通過Lua腳本來控制了頁面的加載過程的,加載過程完全模擬瀏覽器,最后可返回各種格式的結(jié)果,如網(wǎng)頁源碼和截圖等。
接下來,我們就來了解Lua腳本的寫法以及相關(guān)API的用法。
4. Splash Lua腳本
Splash可以通過Lua腳本執(zhí)行一系列渲染操作,這樣我們就可以用Splash來模擬類似Chrome、PhantomJS的操作了。
首先,我們來了解一下Splash Lua腳本的入口和執(zhí)行方式。
入口及返回值
首先,來看一個(gè)基本實(shí)例:
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/的代碼編輯區(qū)域,然后點(diǎn)擊Render me!按鈕來測(cè)試一下。
我們看到它返回了網(wǎng)頁的標(biāo)題,如圖7-8所示。這里我們通過evaljs()方法傳入JavaScript腳本,而document.title的執(zhí)行結(jié)果就是返回網(wǎng)頁標(biāo)題,執(zhí)行完畢后將其賦值給一個(gè)title變量,隨后將其返回。
圖7-8 運(yùn)行結(jié)果
注意,我們?cè)谶@里定義的方法名稱叫作main()。這個(gè)名稱必須是固定的,Splash會(huì)默認(rèn)調(diào)用這個(gè)方法。
該方法的返回值既可以是字典形式,也可以是字符串形式,最后都會(huì)轉(zhuǎn)化為Splash HTTP Response,例如:
function main(splash) return {hello="world!"} end
返回了一個(gè)字典形式的內(nèi)容。例如:
function main(splash) return 'hello' end
返回了一個(gè)字符串形式的內(nèi)容。
異步處理
Splash支持異步處理,但是這里并沒有顯式指明回調(diào)方法,其回調(diào)的跳轉(zhuǎn)是在Splash內(nèi)部完成的。示例如下:
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
運(yùn)行結(jié)果是3個(gè)站點(diǎn)的截圖,如圖7-9所示。
圖7-9 運(yùn)行結(jié)果
在腳本內(nèi)調(diào)用的wait()方法類似于Python中的sleep(),其參數(shù)為等待的秒數(shù)。當(dāng)Splash執(zhí)行到此方法時(shí),它會(huì)轉(zhuǎn)而去處理其他任務(wù),然后在指定的時(shí)間過后再回來繼續(xù)處理。
這里值得注意的是,Lua腳本中的字符串拼接和Python不同,它使用的是..操作符,而不是+。如果有必要,可以簡(jiǎn)單了解一下Lua腳本的語法,詳見http://www.runoob.com/lua/lua-basic-syntax.html。
另外,這里做了加載時(shí)的異常檢測(cè)。go()方法會(huì)返回加載頁面的結(jié)果狀態(tài),如果頁面出現(xiàn)4xx或5xx狀態(tài)碼,ok變量就為空,就不會(huì)返回加載后的圖片。
5. Splash對(duì)象屬性
我們注意到,前面例子中main()方法的第一個(gè)參數(shù)是splash,這個(gè)對(duì)象非常重要,它類似于Selenium中的WebDriver對(duì)象,我們可以調(diào)用它的一些屬性和方法來控制加載過程。接下來,先看下它的屬性。
args
該屬性可以獲取加載時(shí)配置的參數(shù),比如URL,如果為GET請(qǐng)求,它還可以獲取GET請(qǐng)求參數(shù);如果為POST請(qǐng)求,它可以獲取表單提交的數(shù)據(jù)。Splash也支持使用第二個(gè)參數(shù)直接作為args,例如:
function main(splash, args) local url = args.url end
這里第二個(gè)參數(shù)args就相當(dāng)于splash.args屬性,以上代碼等價(jià)于:
function main(splash) local url = splash.args.url end
js_enabled
這個(gè)屬性是Splash的JavaScript執(zhí)行開關(guān),可以將其配置為true或false來控制是否執(zhí)行JavaScript代碼,默認(rèn)為true。例如,這里禁止執(zhí)行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
接著我們重新調(diào)用了evaljs()方法執(zhí)行JavaScript代碼,此時(shí)運(yùn)行結(jié)果就會(huì)拋出異常:
{ "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" }
不過一般來說,不用設(shè)置此屬性,默認(rèn)開啟即可。
resource_timeout
此屬性可以設(shè)置加載的超時(shí)時(shí)間,單位是秒。如果設(shè)置為0或nil(類似Python中的None),代表不檢測(cè)超時(shí)。示例如下:
function main(splash) splash.resource_timeout = 0.1 assert(splash:go('https://www.taobao.com')) return splash:png() end
例如,這里將超時(shí)時(shí)間設(shè)置為0.1秒。如果在0.1秒之內(nèi)沒有得到響應(yīng),就會(huì)拋出異常,錯(cuò)誤如下:
{ "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" }
此屬性適合在網(wǎng)頁加載速度較慢的情況下設(shè)置。如果超過了某個(gè)時(shí)間無響應(yīng),則直接拋出異常并忽略即可。
images_enabled
此屬性可以設(shè)置圖片是否加載,默認(rèn)情況下是加載的。禁用該屬性后,可以節(jié)省網(wǎng)絡(luò)流量并提高網(wǎng)頁加載速度。但是需要注意的是,禁用圖片加載可能會(huì)影響JavaScript渲染。因?yàn)榻脠D片之后,它的外層DOM節(jié)點(diǎn)的高度會(huì)受影響,進(jìn)而影響DOM節(jié)點(diǎn)的位置。因此,如果JavaScript對(duì)圖片節(jié)點(diǎn)有操作的話,其執(zhí)行就會(huì)受到影響。
另外值得注意的是,Splash使用了緩存。如果一開始加載出來了網(wǎng)頁圖片,然后禁用了圖片加載,再重新加載頁面,之前加載好的圖片可能還會(huì)顯示出來,這時(shí)直接重啟Splash即可。
禁用圖片加載的示例如下:
function main(splash, args) splash.images_enabled = false assert(splash:go('https://www.jd.com')) return {png=splash:png()} end
這樣返回的頁面截圖就不會(huì)帶有任何圖片,加載速度也會(huì)快很多。
plugins_enabled
此屬性可以控制瀏覽器插件(如Flash插件)是否開啟。默認(rèn)情況下,此屬性是false,表示不開啟。可以使用如下代碼控制其開啟和關(guān)閉:
splash.plugins_enabled = true/false
scroll_position
通過設(shè)置此屬性,我們可以控制頁面上下或左右滾動(dòng)。這是一個(gè)比較常用的屬性,示例如下:
function main(splash, args) assert(splash:go('https://www.taobao.com')) splash.scroll_position = {y=400} return {png=splash:png()} end
這樣我們就可以控制頁面向下滾動(dòng)400像素值,結(jié)果如圖7-10所示。
圖7-10 運(yùn)行結(jié)果
如果要讓頁面左右滾動(dòng),可以傳入x參數(shù),代碼如下:
splash.scroll_position = {x=100, y=200}
6. Splash對(duì)象的方法
除了前面介紹的屬性外,Splash對(duì)象還有如下方法。
go()
該方法用來請(qǐng)求某個(gè)鏈接,而且它可以模擬GET和POST請(qǐng)求,同時(shí)支持傳入請(qǐng)求頭、表單等數(shù)據(jù),其用法如下:
ok, reason = splash:go{url, baseurl=nil, headers=nil, http_method="GET", body=nil, formdata=nil}
其參數(shù)說明如下。
url:請(qǐng)求的URL。
baseurl:可選參數(shù),默認(rèn)為空,表示資源加載相對(duì)路徑。
headers:可選參數(shù),默認(rèn)為空,表示請(qǐng)求頭。
http_method:可選參數(shù),默認(rèn)為GET,同時(shí)支持POST。
body:可選參數(shù),默認(rèn)為空,發(fā)POST請(qǐng)求時(shí)的表單數(shù)據(jù),使用的Content-type為application/json。
formdata:可選參數(shù),默認(rèn)為空,POST的時(shí)候的表單數(shù)據(jù),使用的Content-type為application/x-www-form-urlencoded。
該方法的返回結(jié)果是結(jié)果ok和原因reason的組合,如果ok為空,代表網(wǎng)頁加載出現(xiàn)了錯(cuò)誤,此時(shí)reason變量中包含了錯(cuò)誤的原因,否則證明頁面加載成功。示例如下:
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
這里我們模擬了一個(gè)POST請(qǐng)求,并傳入了POST的表單數(shù)據(jù),如果成功,則返回頁面的源代碼。
運(yùn)行結(jié)果如下:
<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>
可以看到,我們成功實(shí)現(xiàn)了POST請(qǐng)求并發(fā)送了表單數(shù)據(jù)。
wait()
此方法可以控制頁面的等待時(shí)間,使用方法如下:
ok, reason = splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}
參數(shù)說明如下。
time:等待的秒數(shù)。
cancel_on_redirect:可選參數(shù),默認(rèn)為false,表示如果發(fā)生了重定向就停止等待,并返回重定向結(jié)果。
cancel_on_error:可選參數(shù),默認(rèn)為false,表示如果發(fā)生了加載錯(cuò)誤,就停止等待。
返回結(jié)果同樣是結(jié)果ok和原因reason的組合。
我們用一個(gè)實(shí)例感受一下:
function main(splash) splash:go("https://www.taobao.com") splash:wait(2) return {html=splash:html()} end
這可以實(shí)現(xiàn)訪問淘寶并等待2秒,隨后返回頁面源代碼的功能。
jsfunc()
此方法可以直接調(diào)用JavaScript定義的方法,但是所調(diào)用的方法需要用雙中括號(hào)包圍,這相當(dāng)于實(shí)現(xiàn)了JavaScript方法到Lua腳本的轉(zhuǎn)換。示例如下:
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
運(yùn)行結(jié)果如下:
There are 21 DIVs
首先,我們聲明了一個(gè)JavaScript定義的方法,然后在頁面加載成功后調(diào)用了此方法計(jì)算出了頁面中div節(jié)點(diǎn)的個(gè)數(shù)。
關(guān)于JavaScript到Lua腳本的更多轉(zhuǎn)換細(xì)節(jié),可以參考官方文檔:https://splash.readthedocs.io/en/stable/scripting-ref.html#splash-jsfunc。
evaljs()
此方法可以執(zhí)行JavaScript代碼并返回最后一條JavaScript語句的返回結(jié)果,使用方法如下:
result = splash:evaljs(js)
比如,可以用下面的代碼來獲取頁面標(biāo)題:
local title = splash:evaljs("document.title")
runjs()
此方法可以執(zhí)行JavaScript代碼,它與evaljs()的功能類似,但是更偏向于執(zhí)行某些動(dòng)作或聲明某些方法。例如:
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()先聲明了一個(gè)JavaScript定義的方法,然后通過evaljs()來調(diào)用得到的結(jié)果。
運(yùn)行結(jié)果如下:
bar
autoload()
此方法可以設(shè)置每個(gè)頁面訪問時(shí)自動(dòng)加載的對(duì)象,使用方法如下:
ok, reason = splash:autoload{source_or_url, source=nil, url=nil}
參數(shù)說明如下。
source_or_url:JavaScript代碼或者JavaScript庫鏈接。
source:JavaScript代碼。
url:JavaScript庫鏈接
但是此方法只負(fù)責(zé)加載JavaScript代碼或庫,不執(zhí)行任何操作。如果要執(zhí)行操作,可以調(diào)用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
這里我們調(diào)用autoload()方法聲明了一個(gè)JavaScript方法,然后通過evaljs()方法來執(zhí)行此JavaScript方法。
運(yùn)行結(jié)果如下:
百度一下,你就知道
另外,我們也可以使用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
運(yùn)行結(jié)果如下:
JQuery version: 2.1.3
call_later()
此方法可以通過設(shè)置定時(shí)任務(wù)和延遲時(shí)間來實(shí)現(xiàn)任務(wù)延時(shí)執(zhí)行,并且可以在執(zhí)行前通過cancel()方法重新執(zhí)行定時(shí)任務(wù)。示例如下:
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
這里我們?cè)O(shè)置了一個(gè)定時(shí)任務(wù),0.2秒的時(shí)候獲取網(wǎng)頁截圖,然后等待1秒,1.2秒時(shí)再次獲取網(wǎng)頁截圖,訪問的頁面是淘寶,最后將截圖結(jié)果返回。運(yùn)行結(jié)果如圖7-11所示。
圖7-11 運(yùn)行結(jié)果
可以發(fā)現(xiàn),第一次截圖時(shí)網(wǎng)頁還沒有加載出來,截圖為空,第二次網(wǎng)頁便加載成功了。
http_get()
此方法可以模擬發(fā)送HTTP的GET請(qǐng)求,使用方法如下:
response = splash:http_get{url, headers=nil, follow_redirects=true}
參數(shù)說明如下。
url:請(qǐng)求URL。
headers:可選參數(shù),默認(rèn)為空,請(qǐng)求頭。
follow_redirects:可選參數(shù),表示是否啟動(dòng)自動(dòng)重定向,默認(rèn)為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
運(yùn)行結(jié)果如下:
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_get()方法類似,此方法用來模擬發(fā)送POST請(qǐng)求,不過多了一個(gè)參數(shù)body,使用方法如下:
response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}
參數(shù)說明如下。
url:請(qǐng)求URL。
headers:可選參數(shù),默認(rèn)為空,請(qǐng)求頭。
follow_redirects:可選參數(shù),表示是否啟動(dòng)自動(dòng)重定向,默認(rèn)為true。
body:可選參數(shù),即表單數(shù)據(jù),默認(rèn)為空。
我們用實(shí)例感受一下:
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
運(yùn)行結(jié)果如下:
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請(qǐng)求并發(fā)送了表單數(shù)據(jù)。
set_content()
此方法用來設(shè)置頁面的內(nèi)容,示例如下:
function main(splash) assert(splash:set_content("<html><body><h1>hello</h1></body></html>")) return splash:png() end
運(yùn)行結(jié)果如圖7-12所示。
圖7-12 運(yùn)行結(jié)果
html()
此方法用來獲取網(wǎng)頁的源代碼,它是非常簡(jiǎn)單又常用的方法。示例如下:
function main(splash, args) splash:go("https://httpbin.org/get") return splash:html() end
運(yùn)行結(jié)果如下:
<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格式的網(wǎng)頁截圖,示例如下:
function main(splash, args) splash:go("https://www.taobao.com") return splash:png() end
jpeg()
此方法用來獲取JPEG格式的網(wǎng)頁截圖,示例如下:
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
運(yùn)行結(jié)果如圖7-13所示,其中顯示了頁面加載過程中每個(gè)請(qǐng)求記錄的詳情。
圖7-13 運(yùn)行結(jié)果
url()
此方法可以獲取當(dāng)前正在訪問的URL,示例如下:
function main(splash, args) splash:go("https://www.baidu.com") return splash:url() end
運(yùn)行結(jié)果如下:
https://www.baidu.com/
get_cookies()
此方法可以獲取當(dāng)前頁面的Cookies,示例如下:
function main(splash, args) splash:go("https://www.baidu.com") return splash:get_cookies() end
運(yùn)行結(jié)果如下:
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"
此方法可以為當(dāng)前頁面添加Cookie,用法如下:
cookies = splash:add_cookie{name, value, path=nil, domain=nil, expires=nil, httpOnly=nil, secure=nil}
該方法的各個(gè)參數(shù)代表Cookie的各個(gè)屬性。
示例如下:
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,然后調(diào)用get_cookies()將結(jié)果返回。
運(yùn)行結(jié)果如下:
Splash Response: Array[0]
可以看到,Cookies被全部清空,沒有任何結(jié)果。
get_viewport_size()
此方法可以獲取當(dāng)前瀏覽器頁面的大小,即寬高,示例如下:
function main(splash) splash:go("https://www.baidu.com/") return splash:get_viewport_size() end
運(yùn)行結(jié)果如下:
Splash Response: Array[2] 0: 1024 1: 768
set_viewport_size()
此方法可以設(shè)置當(dāng)前瀏覽器頁面的大小,即寬高,用法如下:
splash:set_viewport_size(width, height)
例如,這里訪問一個(gè)寬度自適應(yīng)的頁面:
function main(splash) splash:set_viewport_size(400, 700) assert(splash:go("http://cuiqingcai.com")) return splash:png() end
運(yùn)行結(jié)果如圖7-14所示。
圖7-14 運(yùn)行結(jié)果
set_viewport_full()
此方法可以設(shè)置瀏覽器全屏顯示,示例如下:
function main(splash) splash:set_viewport_full() assert(splash:go("http://cuiqingcai.com")) return splash:png() end
此方法可以設(shè)置瀏覽器的User-Agent,示例如下:
function main(splash) splash:set_user_agent('Splash') splash:go("http://httpbin.org/get") return splash:html() end
這里我們將瀏覽器的User-Agent設(shè)置為Splash,運(yùn)行結(jié)果如下:
<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被成功設(shè)置。
set_custom_headers()
此方法可以設(shè)置請(qǐng)求頭,示例如下:
function main(splash) splash:set_custom_headers({ ["User-Agent"] = "Splash", ["Site"] = "Splash", }) splash:go("http://httpbin.org/get") return splash:html() end
這里我們?cè)O(shè)置了請(qǐng)求頭中的User-Agent和Site屬性,運(yùn)行結(jié)果如下:
<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>
該方法可以選中符合條件的第一個(gè)節(jié)點(diǎn),如果有多個(gè)節(jié)點(diǎn)符合條件,則只會(huì)返回一個(gè),其參數(shù)是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
這里我們首先訪問了百度,然后選中了搜索框,隨后調(diào)用了send_text()方法填寫了文本,然后返回網(wǎng)頁截圖。
結(jié)果如圖7-15所示,可以看到,我們成功填寫了輸入框。
圖7-15 運(yùn)行結(jié)果
select_all()
此方法可以選中所有符合條件的節(jié)點(diǎn),其參數(shù)是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選擇器選中了節(jié)點(diǎn)的正文內(nèi)容,隨后遍歷了所有節(jié)點(diǎn),將其中的文本獲取下來。
運(yùn)行結(jié)果如下:
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.”"
可以發(fā)現(xiàn),我們成功地將10個(gè)節(jié)點(diǎn)的正文內(nèi)容獲取了下來。
mouse_click()
此方法可以模擬鼠標(biāo)點(diǎn)擊操作,傳入的參數(shù)為坐標(biāo)值x和y。此外,也可以直接選中某個(gè)節(jié)點(diǎn),然后調(diào)用此方法,示例如下:
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
這里我們首先選中頁面的輸入框,輸入了文本,然后選中“提交”按鈕,調(diào)用了mouse_click()方法提交查詢,然后頁面等待三秒,返回截圖,結(jié)果如圖7-16所示。
圖7-16 運(yùn)行結(jié)果
可以看到,這里我們成功獲取了查詢后的頁面內(nèi)容,模擬了百度搜索操作。
前面介紹了Splash的常用API操作,還有一些API在這不再一一介紹,更加詳細(xì)和權(quán)威的說明可以參見官方文檔https://splash.readthedocs.io/en/stable/scripting-ref.html,此頁面介紹了Splash對(duì)象的所有API操作。另外,還有針對(duì)頁面元素的API操作,鏈接為https://splash.readthedocs.io/en/stable/scripting-element-object.html。
7. Splash API調(diào)用
前面說明了Splash Lua腳本的用法,但這些腳本是在Splash頁面中測(cè)試運(yùn)行的,如何才能利用Splash渲染頁面呢?怎樣才能和Python程序結(jié)合使用并抓取JavaScript渲染的頁面呢?
其實(shí)Splash給我們提供了一些HTTP API接口,我們只需要請(qǐng)求這些接口并傳遞相應(yīng)的參數(shù)即可,下面簡(jiǎn)要介紹這些接口。
render.html
此接口用于獲取JavaScript渲染的頁面的HTML代碼,接口地址就是Splash的運(yùn)行地址加此接口名稱,例如http://localhost:8050/render.html??梢杂胏url來測(cè)試一下:
curl http://localhost:8050/render.html?url=https://www.baidu.com
我們給此接口傳遞了一個(gè)url參數(shù)來指定渲染的URL,返回結(jié)果即頁面渲染后的源代碼。
如果用Python實(shí)現(xiàn)的話,代碼如下:
import requests url = 'http://localhost:8050/render.html?url=https://www.baidu.com' response = requests.get(url) print(response.text)
這樣就可以成功輸出百度頁面渲染后的源代碼了。
另外,此接口還可以指定其他參數(shù),比如通過wait指定等待秒數(shù)。如果要確保頁面完全加載出來,可以增加等待時(shí)間,例如:
import requests url = 'http://localhost:8050/render.html?url=https://www.taobao.com&wait=5' response = requests.get(url) print(response.text)
此時(shí)得到響應(yīng)的時(shí)間就會(huì)相應(yīng)變長,比如這里會(huì)等待5秒多鐘才能獲取淘寶頁面的源代碼。
另外,此接口還支持代理設(shè)置、圖片加載設(shè)置、Headers設(shè)置、請(qǐng)求方法設(shè)置,具體的用法可以參見官方文檔https://splash.readthedocs.io/en/stable/api.html#render-html。
render.png
此接口可以獲取網(wǎng)頁截圖,其參數(shù)比render.html多了幾個(gè),比如通過width和height來控制寬高,它返回的是PNG格式的圖片二進(jìn)制數(shù)據(jù)。示例如下:
curl http://localhost:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700
這里我們傳入了width和height來設(shè)置頁面大小為1000×700像素。
如果用Python實(shí)現(xiàn),可以將返回的二進(jìn)制數(shù)據(jù)保存為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)
得到的圖片如圖7-17所示。
圖7-17 運(yùn)行結(jié)果
這樣我們就成功獲取了京東首頁渲染完成后的頁面截圖,詳細(xì)的參數(shù)設(shè)置可以參考官網(wǎng)文檔https://splash.readthedocs.io/en/stable/api.html#render-png。
render.jpeg
此接口和render.png類似,不過它返回的是JPEG格式的圖片二進(jìn)制數(shù)據(jù)。
另外,此接口比render.png多了參數(shù)quality,它用來設(shè)置圖片質(zhì)量。
render.har
此接口用于獲取頁面加載的HAR數(shù)據(jù),示例如下:
curl http://localhost:8050/render.har?url=https://www.jd.com&wait=5
它的返回結(jié)果(如圖7-18所示)非常多,是一個(gè)JSON格式的數(shù)據(jù),其中包含頁面加載過程中的HAR數(shù)據(jù)。
圖7-18 運(yùn)行結(jié)果
render.json
此接口包含了前面接口的所有功能,返回結(jié)果是JSON格式,示例如下:
curl http://localhost:8050/render.json?url=https://httpbin.org
結(jié)果如下:
{"title": "httpbin(1): HTTP Client Testing Service", "url": "https://httpbin.org/", "requestedUrl": "https: //httpbin.org/", "geometry": [0, 0, 1024, 768]}
可以看到,這里以JSON形式返回了相應(yīng)的請(qǐng)求數(shù)據(jù)。
我們可以通過傳入不同參數(shù)控制其返回結(jié)果。比如,傳入html=1,返回結(jié)果即會(huì)增加源代碼數(shù)據(jù);傳入png=1,返回結(jié)果即會(huì)增加頁面PNG截圖數(shù)據(jù);傳入har=1,則會(huì)獲得頁面HAR數(shù)據(jù)。例如:
curl http://localhost:8050/render.json?url=https://httpbin.org&html=1&har=1
這樣返回的JSON結(jié)果會(huì)包含網(wǎng)頁源代碼和HAR數(shù)據(jù)。
此外還有更多參數(shù)設(shè)置,具體可以參考官方文檔:https://splash.readthedocs.io/en/stable/api.html#render-json。
execute
此接口才是最為強(qiáng)大的接口。前面說了很多Splash Lua腳本的操作,用此接口便可實(shí)現(xiàn)與Lua腳本的對(duì)接。
前面的render.html和render.png等接口對(duì)于一般的JavaScript渲染頁面是足夠了,但是如果要實(shí)現(xiàn)一些交互操作的話,它們還是無能為力,這里就需要使用execute接口了。
我們先實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的腳本,直接返回?cái)?shù)據(jù):
function main(splash) return 'hello' end
然后將此腳本轉(zhuǎn)化為URL編碼后的字符串,拼接到execute接口后面,示例如下:
curl http://localhost:8050/execute?lua_source=function+main%28splash%29%0D%0A++return+%27hello%27%0D%0Aend
運(yùn)行結(jié)果如下:
hello
這里我們通過lua_source參數(shù)傳遞了轉(zhuǎn)碼后的Lua腳本,通過execute接口獲取了最終腳本的執(zhí)行結(jié)果。
這里我們更加關(guān)心的肯定是如何用Python來實(shí)現(xiàn),上例用Python實(shí)現(xiàn)的話,代碼如下:
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)
運(yùn)行結(jié)果如下:
hello
這里我們用Python中的三引號(hào)將Lua腳本包括起來,然后用urllib.parse模塊里的quote()方法將腳本進(jìn)行URL轉(zhuǎn)碼,隨后構(gòu)造了Splash請(qǐng)求URL,將其作為lua_source參數(shù)傳遞,這樣運(yùn)行結(jié)果就會(huì)顯示Lua腳本執(zhí)行后的結(jié)果。
我們?cè)偻ㄟ^實(shí)例看一下:
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)
運(yùn)行結(jié)果如下:
{"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"}
可以看到,返回結(jié)果是JSON形式,我們成功獲取了請(qǐng)求的URL、狀態(tài)碼和網(wǎng)頁源代碼。
如此一來,我們之前所說的Lua腳本均可以用此方式與Python進(jìn)行對(duì)接,所有網(wǎng)頁的動(dòng)態(tài)渲染、模擬點(diǎn)擊、表單提交、頁面滑動(dòng)、延時(shí)等待后的一些結(jié)果均可以自由控制,獲取頁面源碼和截圖也都不在話下。
到現(xiàn)在為止,我們可以用Python和Splash實(shí)現(xiàn)JavaScript渲染的頁面的抓取了。除了Selenium,本節(jié)所說的Splash同樣可以做到非常強(qiáng)大的渲染功能,同時(shí)它也不需要瀏覽器即可渲染,使用非常方便。
到此這篇關(guān)于Python3爬蟲中Splash的知識(shí)總結(jié)的文章就介紹到這了,更多相關(guān)Python3中Splash的基礎(chǔ)知識(shí)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 還原梯度下降算法實(shí)現(xiàn)一維線性回歸
這篇文章主要介紹了python 還原梯度下降算法實(shí)現(xiàn)一維線性回歸,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10Python3實(shí)現(xiàn)監(jiān)控新型冠狀病毒肺炎疫情的示例代碼
這篇文章主要介紹了Python3實(shí)現(xiàn)監(jiān)控新型冠狀病毒肺炎疫情的示例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02python Pexpect 實(shí)現(xiàn)輸密碼 scp 拷貝的方法
今天小編就為大家分享一篇python Pexpect 實(shí)現(xiàn)輸密碼 scp 拷貝的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-01-01如何在Flask中實(shí)現(xiàn)數(shù)據(jù)分組流程詳解
在Flask中,數(shù)據(jù)分組是指將一組數(shù)據(jù)按照某種方式進(jìn)行分類,以便更好地對(duì)數(shù)據(jù)進(jìn)行處理和展示,可以使用Python內(nèi)置的itertools模塊中的groupby方法,或者使用SQL語句中的GROUP?BY子句來實(shí)現(xiàn)數(shù)據(jù)分組,這篇文章介紹了在Flask中實(shí)現(xiàn)數(shù)據(jù)分組,感興趣的同學(xué)可以參考下文2023-05-05使用Python的Supervisor進(jìn)行進(jìn)程監(jiān)控以及自動(dòng)啟動(dòng)
這篇文章主要介紹了使用Python的Supervisor進(jìn)行進(jìn)程監(jiān)控以及自動(dòng)啟動(dòng),使用python supervisor實(shí)現(xiàn),需要的朋友可以參考下2014-05-05Python爬蟲 scrapy框架爬取某招聘網(wǎng)存入mongodb解析
這篇文章主要介紹了Python爬蟲 scrapy框架爬取某招聘網(wǎng)存入mongodb解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07python函數(shù)與方法的區(qū)別總結(jié)
在本篇文章里小編給大家整理了關(guān)于python函數(shù)與方法的區(qū)別的相關(guān)知識(shí)點(diǎn)代碼內(nèi)容,需要的朋友們學(xué)習(xí)下。2019-06-06