深入使用 Splash 服務(wù)
上一小節(jié)我們基于 Splash 服務(wù)以及 Scrapy-Splash 插件完成了今日頭條熱點數(shù)據(jù)的抓取,今天我們來詳細(xì)地介紹 Splash Lua 腳本中支持的相關(guān)方法與 Splash 對象屬性,并解決上一小節(jié)留下的作業(yè)題。
1. Splash Lua 腳本方法與相關(guān)屬性介紹
上一節(jié)我們簡單實用了 Splash 服務(wù),并基于默認(rèn)的腳本完成了頭條熱點新聞數(shù)據(jù)的爬取。我們現(xiàn)在來深入學(xué)習(xí) Splash 中 lua 腳本的使用。
1.1 Splash對象屬性
來看默認(rèn)的 Splash lua 腳本:
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
其中這個 splash 參數(shù)非常重要,從該參數(shù)中我們可以調(diào)用 Splash 對象的一些重要屬性和方法來控制加載的過程。我們來看看 Splash 對象最常用的幾種屬性:
-
args 屬性:如
splash.args.url
是獲取請求渲染的 url; -
js_enabled 屬性:這個屬性可以用來允許或者禁止執(zhí)行 js 代碼。例如下面的 lua 腳本:
function main(splash, args) splash.js_enabled = true assert(splash:go(args.url)) assert(splash:wait(0.5)) local title = splash:evaljs("document.title") return { title=title } end
得到的結(jié)果為:
Splash Response: Object title: "今日頭條"
如果我們禁止執(zhí)行 js 代碼,即設(shè)置
splash.js_enabled = false
,則渲染頁面時會報錯:
resource_timeout 屬性:該屬性用于設(shè)置頁面加載時間,單位為秒。如果設(shè)置為0或者 nil (相當(dāng)于 Python 中的 None),表示不檢測超時;
images_enabled 屬性:用于設(shè)置是否加載圖片,默認(rèn)為 true,表示加載頁面圖片,設(shè)置為 false 后,表示禁止加載圖片,這有可能會改變頁面的布局,使用時要注意。另外,注意 Splash 使用了緩存,如果頭一次設(shè)置 true 并加載頁面,之后再設(shè)置為 false 后加載頁面仍然會有圖片顯示,這正是緩存的影響。只需要重啟 splash 服務(wù)即可顯示正常;
plugins_enabled 屬性:該屬性用于控制瀏覽器插件是否開啟,默認(rèn)情況下為 false;
scroll_position 屬性:該屬性用于控制頁面上下或者左右滾動。它是一個字典類型,key 為 x 表示頁面水平滾動位置,key 為 y 表示頁面垂直滾動的位置;我們繼續(xù)拿頭條的熱點新聞做實驗。之前默認(rèn)訪問時的頁面如下:
從抓取的網(wǎng)頁上看,一共獲取了12篇熱點新聞。接下來我們使用 scroll_position 屬性來將頁面滾動滾動,測試的 lua code 如下:
function main(splash, args)
assert(splash:go(args.url))
assert(splash:wait(2))
splash.scroll_position = {y=1000}
assert(splash:wait(2))
splash.scroll_position = {y=1500}
assert(splash:wait(5))
return {
png=splash:png(),
html=splash:html()
}
end
這里我做了2次頁面滾動,渲染的效果如下:
可以看到,頁面確實出現(xiàn)了滾動,且我們獲取的新聞數(shù)據(jù)已經(jīng)變多了,從渲染的頁面上看,我們已經(jīng)抓到了36條數(shù)據(jù)。
1.2 Splash 的常用方法
前面從默認(rèn)的 lua 腳本中我們已經(jīng)看到了 Splash 的一些常用方法,如 go()、wait()、html()、png() 等,我們來一一進(jìn)行介紹:
splash:go()
:這個方法比較熟悉了,就是跳轉(zhuǎn)去對應(yīng)的 url 地址,目前它只支持 GET 和 POST 請求。該方法支持指定 HTTP 請求頭,表單等數(shù)據(jù)。對應(yīng)的方法原型如下:
ok, reason = splash:go{url, baseurl=nil, headers=nil, http_method="GET", body=nil, formdata=nil}
函數(shù)參數(shù)以及返回結(jié)果詳情可參考:splash:go,官方已經(jīng)給出了非常詳細(xì)的說明,這里就不再進(jìn)行翻譯了。
splash:wait()
: 控制頁面等待時間,函數(shù)原型如下:
ok, reason = splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}
cancel_on_redirect 參數(shù)默認(rèn)為 false,表示如果等待中發(fā)生重定向則停止等待并返回重定向結(jié)果;cancel_on_error 默認(rèn)為 true,表示在等待渲染中出現(xiàn)了錯誤則停止等待并返回 nil, "<error string>"
,其中 error string 指的是加載錯誤的原因;
三個和執(zhí)行 js 相關(guān)的方法:
splash:jsfunc()
,該方法用于將 JavaScript 方法轉(zhuǎn)換成 Lua 中可調(diào)用的方法。注意所調(diào)用的 JavaScript 函數(shù)必須在一對雙中括號內(nèi),類似如下寫法:
function main(splash, args)
-- get_div_count 就是表示jsfunc中定義的js方法
local get_div_count = splash:jsfunc([[
function () {
var body = document.body;
var divs = body.getElementsByTagName('div');
return divs.length;
}
]])
splash:go(args.url)
return ("There are %s DIVs in %s"):format(
get_div_count(), args.url)
end
splash:evaljs()
,直接在渲染的頁面中執(zhí)行 js 腳本。來看看如下示例:
local title = splash:evaljs("document.title")
splash:runjs()
,它和 evaljs()
方法功能類似,也是執(zhí)行 JavaScript 代碼。前者它更偏向于執(zhí)行某些動作或者定義某些方法:
-- 這樣子的寫法,foo便會加入到全局上下文中,下面注釋的這樣寫法就是錯誤的
-- assert(splash:runjs("function foo(){return 'bar'}"))
-- 下面這個為正確寫法
assert(splash:runjs("foo = function (){return 'bar'}"))
local res = splash:evaljs("foo()") -- this returns 'bar'
splash:autoload()
,該方法用于設(shè)置每個頁面訪問時自動加載的 JavaScript 代碼,該方法只負(fù)責(zé)加載代碼并不執(zhí)行。我們通常會用該方法去加載一些必須的 js 庫函數(shù),如 jQuery 等,也會使用該方法加載我們自定義的 js 函數(shù)。
assert(splash:autoload("https://code.jquery.com/jquery-2.1.3.min.js"))
splash:call_later()
,該方法通過設(shè)置任務(wù)的延長時間來實現(xiàn)任務(wù)的延遲執(zhí)行。
splash:http_get()
,該方法發(fā)送 http 的 get 請求并返回響應(yīng),方法的原型如下:
response = splash:http_get{url, headers=nil, follow_redirects=true}
-
splash:http_post()
,該方法發(fā)送 http 的 post 請求并返回響應(yīng),方法的原型如下:response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}
splash:set_content()
,該方法用于設(shè)置當(dāng)前頁面的內(nèi)容并等待頁面加載;我們來看看官方給的一個簡單示例:
function main(splash)
assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
return splash:png()
end
渲染效果如下:
-
splash:html()
:獲取渲染后的網(wǎng)頁源碼; -
splash:png()
:獲取 png 格式的頁面截圖; -
splash:jpg()
:獲取 jpg 格式的頁面截圖; -
splash:url()
:獲取當(dāng)前訪問頁面的 url;
cookie 相關(guān)的方法:
-
splash:get_cookies()
:獲取 CookieJar 的內(nèi)容-腳本中所有 cookies 的列表; -
splash:add_cookie()
:添加一個 cookie; -
splash:init_cookies()
:將當(dāng)前所有 cookies 替換成傳入的 cookies -
splash:clean_cookies()
:清除所有的 cookies; -
splash:delete_cookies()
:刪除指定的 cookies; -
splash:set_viewport_full()
:設(shè)置瀏覽器全屏顯示; -
splash:on_request()
:在每個 http 請求之前注冊要調(diào)用的函數(shù)。這個方法非常有用,官方給出了6中用途示例,如記錄所有的請求、丟棄某個特殊的請求 (比如以 .css 結(jié)尾的請求) 等,這也從某方面說明了該方法的重要性;
接下來我們看看 Splash 中一些更高級的用法,包括頁面元素定位、填充輸入框以及模擬鼠標(biāo)操作等方法。
2. Splash 中元素定位與操作
Splash 中涉及到元素定位和操作的方法主要有如下幾個:
splash:select()
:從當(dāng)前網(wǎng)頁的 DOM 中選擇與指定 CSS 選擇器匹配的第一個 HTML 元素;splash:select_all()
:從當(dāng)前網(wǎng)頁的 DOM 中選擇與指定 CSS 選擇器匹配的 HTML 元素列表;splash:send_keys()
:將鍵盤事件發(fā)送到頁面上下文;splash:send_text()
:將文本作為輸入發(fā)送到頁面上下文,一個字符一個字符發(fā)送;
來看看我們對這些方法的一個簡單實例:
function main(splash)
splash:go("https://www.baidu.com")
splash:wait(2)
input = splash:select("#kw")
input:send_text("慕課網(wǎng) wiki")
splash:wait(2)
return {
png = splash:png()
}
end
來看看針對百度頁面的執(zhí)行效果:
另外一個例子,我們還是前面的頭條熱點數(shù)據(jù),我們加上滾動效果后能提取出更多的熱點新聞,那么就在這里使用 splash:select_all()
方法將這些熱點新聞的標(biāo)題提取出來。為此,我們編寫如下的 lua 代碼:
function main(splash, args)
local treat = require('treat')
assert(splash:go(args.url))
assert(splash:wait(2))
splash.scroll_position = {y=1000}
assert(splash:wait(2))
splash.scroll_position = {y=1500}
assert(splash:wait(5))
news_list = splash:select_all('div.title-box a')
local result = {}
for idx, a in ipairs(news_list) do
result[idx] = a.node.innerHTML
end
return treat.as_array(result)
end
來看看渲染后的結(jié)果,如下:
3. 模擬鼠標(biāo)操作
最后一部分我們來看看和鼠標(biāo)操作相關(guān)的方法,總共有4個方法:
-
splash:mouse_click()
:模擬鼠標(biāo)的點擊動作,該方法的原型為splash:mouse_click(x, y)
;示例1:
local button = splash:select('button') -- 對于選中的button元素執(zhí)行點擊動作 button:mouse_click()
示例2:
-- 通過(x, y)坐標(biāo)執(zhí)行鼠標(biāo)點擊動作 function main(splash) assert(splash:go(splash.args.url)) -- 定義js函數(shù) local get_dimensions = splash:jsfunc([[ function () { var rect = document.getElementById('button').getClientRects()[0]; return {"x": rect.left, "y": rect.top} } ]]) splash:set_viewport_full() splash:wait(0.1) -- 執(zhí)行js方法,獲取元素的坐標(biāo)位置 local dimensions = get_dimensions() -- FIXME: button must be inside a viewport splash:mouse_click(dimensions.x, dimensions.y) -- Wait split second to allow event to propagate. splash:wait(0.1) return splash:html() end
-
splash:mouse_hover()
:模擬鼠標(biāo)懸停事件,方法原型為splash:mouse_hover(x, y)
; -
splash:mouse_press()
:在網(wǎng)頁中觸發(fā)鼠標(biāo)按下事件,方法原型為splash:mouse_press(x, y)
; -
splash:mouse_release()
:在網(wǎng)頁中觸發(fā)鼠標(biāo)釋放事件。方法原型為splash:mouse_release(x, y)
;
4. 小結(jié)
本小節(jié)中我們深入學(xué)習(xí)了 Splash 服務(wù)的使用,并詳細(xì)介紹了 Splash 的部分屬性以及相關(guān)方法,部分屬性和方法給予詳細(xì)的實戰(zhàn)例子。接下來的兩節(jié)我們會介紹一種自動化測試工具,和 Splash 服務(wù)功能類似,但是更為強大和簡單易用,還等什么,快來學(xué)習(xí)吧!