Splash的安裝中出現的問題的解決和基本使用介紹
Splash是一個JavaScript渲染服務,是一個帶有HTTP API的輕量級瀏覽器,同時它對接了Python中的Twisted和QT庫。利用它,我們同樣可以實現動態渲染頁面和抓取。
功能介紹
- 利用Splash,我們可以實現如下功能:
- 非同步方式處理多個網頁渲染過程;
- 獲取渲染後的頁面的原始碼或截圖;
- 通過關閉圖片渲染或者使用Adblock規則來加快頁面渲染速度;
- 可執行特定的JavaScript指令碼;
- 可通過Lua指令碼來控制頁面渲染過程;
- 獲取渲染的詳細過程並通過HAR(HTTP Archive)格式呈現。
準備工作
我們要進行Splash 的安裝,安裝Splash我們要先安裝Docker,我們只需從
我第一次用docker安裝splash出現這種錯誤,這是因為我們的設定的DOCKER_HOST的設定連線不上去,後來我去百度它給我的解決辦法是輸入【docker-machine env default】,然後我輸了還是不行,還是會繼續報錯,如下所示:
意思就是說Host連線不上去了,這時候我們就要重啟下default,輸入命令【docker-machine restart default】,如下所示:
然後再輸入命令【docker-machine env --shell cmd default】,如下所示:
然後把下面的內容複製下就可以了,如下所示:
這時候再輸入命令【docker version】,我們就可以顯示成功了,如下所示:
再執行命令安裝Splash即可安裝成功如下所示:
然後輸入命令【docker-machine ip default]會顯示ip地址,將下面顯示的地址複製到網頁輸出,如下所示:
這樣Splash即可安裝成功了
如上圖右側,呈現的是一個渲染示例。可以看到,上方有一個輸入框,我們點選render me按鈕開始渲染,可以看到,網頁的返回結果呈現了渲染截圖,HAR載入統計資料、網頁的原始碼。
這個過程是由一段指令碼構成,這個指令碼實際上是用Lua語言寫的指令碼,下面我們會介紹。
Splash Lua指令碼
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
返回了網頁的標題,這裡我們通過evaljs()方法傳入JavaScript指令碼,而document.title的執行結果就是返回網頁標題,執行完成後將其賦值給一個title變數,隨後將其返回。
我們在這裡定義的方法名稱叫做main()。這個名稱必須是固定的,Splash會預設呼叫這個方法。
該方法的返回值既可以是字典形式,也可以是字串形式,最後都會轉化為Splash HTTP Response。
-
非同步處理
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個站點
Splash物件屬性
類似於Selenium中的WebDriver物件,我們可以呼叫它的一些屬性和方法來控制載入過程。
-
args
該屬性可以獲取載入時配置的引數,比如URL,如果為GET請求,它還可以獲取GET請求引數;如果為POST請求,它可以獲取表單提交的資料。Splash也支援使用第二個引數直接作為args,例如:
function main(splash, args)
local url=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程式碼,此時執行結果就會丟擲異常:
HTTP Error 400 (Bad Request)
Type: ScriptError -> LUA_INIT_ERROR
Error happened while executing Lua script
[string "function main(splash, args)
..."]:3: function arguments expected near '='
{
"type": "ScriptError",
"error": 400,
"info": {
"source": "[string \"function main(splash, args)\r...\"]",
"line_number": 3,
"message": "[string \"function main(splash, args)\r...\"]:3: function arguments expected near '='",
"type": "LUA_INIT_ERROR",
"error": "function arguments expected near '='"
},
"description": "Error happened while executing Lua script"
}
-
resource_timeout
此屬性可以設定載入時間,單位是秒。如果設定為0或nil,代表不檢測超時,如下所示:
function main(splash)
splash.resource_timeout=0.1
assert(splash:go('https://www.taobao.com'))
return splash:png()
end
-
images_enabled
此屬性可以設定圖片是否載入,預設情況下是載入的。禁用該屬性後,可以節省網路流量並提高網頁載入速度。
function main(splash, args)
splash.images_enabled=false
assert(splash:go('https://www.jd.com'))
return {
png=splash:png()
}
end
這樣返回的頁面截圖就不會帶有任何圖片,載入速度也會快很多。
-
plugins_enabled
此屬性可以控制瀏覽器外掛是否開啟。預設情況下是不開啟的。
-
scroll_position
通過設定此屬性,我們可以控制頁面上下或左右滾蛋。這是一個比較常用的屬性,示例如下:
function main(splash, args)
assert(splash:go('https://www.taobao.com'))
splash.scroll_position = {y=400}
return {png=splash:png()}
end
這樣我們就可以控制頁面向下滾動400畫素值
Splash物件的方法
-
go()
該方法是用來請求某個連結的,而且它可以模擬GET和POST請求,同時支援傳入請求頭、表單等資料,用法如下:
ok,reason=splash:go{url,baseurl=nil,header=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的表單資料,如果成功,則返回頁面的原始碼。
-
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("http://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
我們聲明瞭一個JavaScript定義的辦法,然後在頁面載入成功後呼叫了從方法計算出了頁面中div節點的個數。
-
evaljs()
此方法可以執行JavaScript程式碼並返回最後一條JavaScript語句返回結果,使用方法如下:
result=splash:evaljs(js)
-
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()來呼叫得到的結果。
-
autoload()
此方法可以設定每個頁面訪問時自動載入的物件,使用方法如下:
ok,reason=splash:autoload{source_or_url,source=nill,url=nil}
- source_or_url:JavaScript程式碼或者JavaScript庫連結
- source:JavaScript程式碼
- url:JavaScript庫連結
此方法只負責載入JavaScript程式碼或庫,不執行任何操作。
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()該方法。
-
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秒時再次獲取網頁截圖,訪問的頁面是淘寶,最後將截圖結果返回。
-
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
-
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
這裡我們成功模擬提交了POST請求併發生了表單資料
-
set_content()
此方法用來設定頁面的內容,示例如下:
function main(splash)
assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
return splash:png()
end
-
html()
此方法用來獲取網頁的原始碼,它是非常簡單又常用的方法。示例如下:
function main(splash, args)
splash:go("https://httpbin.org/get")
return splash:html()
end
-
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
-
url()
此方法可以獲取當前正在訪問的URL,示例如下:
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:url()
end
-
get_cookies()
此方法可以獲取當前頁面的Cookies,示例如下:
function main(splash, args)
splash:go("https://www.baidu.com")
return splash:get_cookies()
end
-
add_cookie()
此方法可以為當前頁面新增Cookie
cookies=splash:add_cookie{name,value,path=nil,expires=nil,httpOnly=nil,secure=nil}
-
clear_cookies()
此方法可以清楚所有的cookies
-
get_viewport_size()
此方法可以獲取當前瀏覽器頁面的大小,即寬高。
-
set_viewport_size()
此方法可以設定當前瀏覽器頁面的大小,即寬高。
-
set_viewport_full()
此方法可以設定瀏覽器全屏顯示
-
set_user_agent()
此方法可以設定瀏覽器的User-Agent
-
set_custom_headers()
此方法可以設定請求頭
-
select()
該方法可以選中符合條件的第一個節點,如果有多個節點符合條件,則只會返回一個,其引數是CSS選擇器,示例如下:
function main(splash, args)
splash:go("https://www.baidu.com/")
input=splash:select("#kw")
input:send_text("Splash")
splash:wait(3)
return splash:png()
end
我們這裡首先訪問了百度,然後選中了搜尋框,隨後呼叫了send_text()方法填寫了文字,然後返回網頁截圖。
-
select_all()
此方法可以選中所有符合條件的節點,其引數是CSS選擇器。示例如下:
function main(splash, args)
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選擇器選中了節點的正文內容,隨後遍歷了所有節點,將其中的文字獲取下來。
-
mouse_click()
此方法可以模擬滑鼠點選操作,傳入的引數為座標值x和y。此外,也可以直接選中某個節點,然後呼叫此方法,示例如下:
function main(splash, args)
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()方法提交查詢,然後頁面等待三秒,返回截圖。
Splash API呼叫
-
render.html
此介面用於獲取JavaScript渲染的頁面的HTML程式碼,介面地址就是Splash的執行地址加此介面的名稱
如果用Python實現的話,如下所示:
import requests
url='http://192.168.99.100:8050/render.html?url=https://www.baidu.com'
response=requests.get(url)
print(response.text)
這樣就可以成功輸出百度頁面渲染後的原始碼,此介面還可以指定其他引數
-
render.png
此介面可以獲取網頁截圖,其引數比render.html多了幾個,比如通過width和height來控制寬高,它返回的是PNG格式的圖片二進位制資料,示例如下:
import requests
url='http://192.168.99.100:8050/render.png?url=https://www.jd.com&wait=5&wait=1000&height=700'
response=requests.get(url)
with open('taobao.png','wb') as f:
f.write(response.content)
這樣我們就成功獲取了京東首頁渲染完成後的頁面截圖
-
render.jpeg
此介面和render.png不過它返回的是JPEG格式的圖片二進位制資料
另外,此介面比render.png多了個引數quality,用來設定圖片質量。
-
render.har
此介面用於獲取頁面載入的HAR資料,
-
render.json
此介面包含了前面介面的所有功能,返回結果是JSON格式.
-
execute
此接口才是最為強大的介面。execute介面可以實現一些互動操作
import requests
from urllib.parse import quote
lua="""
function main(splash)
return 'hello'
end
"""
url='http://192.168.99.100:8050/execute?lua_source='+quote(lua)
response=requests.get(url)
print(response.text)
這裡我們用Python中的三引號將Lua指令碼包括起來,然後用urllib.parse模板裡的quote()方法將指令碼進行URL轉碼,隨後構造了Splash請求URL,將其作為lua_source引數傳遞,我們通過例項看下:
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://192.168.99.100:8050/execute?lua_source='+quote(lua)
response=requests.get(url)
print(response.text)
我們所說的Lua指令碼均可以用此方法與Python進行對接。