Splash是一個JavaScript渲染服務 是一個帶有HTTP API的輕量級瀏覽器 同時對接了python的Twisted 和QT庫html
利用它能夠實現對動態渲染頁面的抓取node
功能介紹python
1.異步方式處理多個網頁渲染過程 2.獲取渲染後的頁面源代碼或截圖 3.經過關閉圖片渲染或使用Adblock規則加快頁面渲染速度 4.可執行特定js腳本 5.可經過Lua腳原本控制頁面渲染過程 6.獲取渲染的詳細過程並經過HAR(HTTP Archive)格式呈現
安裝準備
1.Docker的安裝 (後面講到時會詳細講 這裏先安裝)jquery
windows下安裝:
win10 64位 推薦 Docker for windwos 官網下載最新安裝包:web
https://docs.docker.com/docker-for-windows/install/
不是 64位的 下載 Docker Toolbox :docker
https://docs.docker.com/toolbox/toolbox_install_windows/
下載後雙擊安裝 安裝完成後 進入命令行 輸入docker 運行沒有報錯證實安裝成功了json
2.安裝splash 命令行執行 windows
docker run -p 8050:8050 scrapinghub/splash
顯示以下省略部分表示服務啓動了
[-] Site starting on 8050
[-] Starting factory <twisted.web.server.Site object at 0x7fb62b1957f0>api
打開瀏覽器 訪問localhost:8050 顯示web頁面瀏覽器
嘗試修改輸入框爲 https://www.baidu.com 點擊 Render me
返回結果呈現了 渲染截圖 HAR加載統計數據 網頁源代碼
經過HAR結果能夠看到 Splash 執行了整個頁面的渲染過程 包括CSS JS 加載等 和咱們在瀏覽器中獲得的結果一致
過程控制
function main(splash, args) assert(splash:go(args.url))#加載頁面 assert(splash:wait(0.5))#延時等待 return { html = splash:html(),#返回頁面源碼 png = splash:png(),#返回截圖 har = splash:har(),#返回HAR信息 } end
2.1 Splash Lua 腳本
Splash 能夠經過Lua腳本執行一系列渲染操做
2.1.1 入口及返回值
示例:
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()方法傳入js腳本 執行完畢賦值給title變量 隨後返回
注意:方法名 main() 是固定的 必須用main splash默認會調用該方法
返回值既能夠是字典也能夠是字符串 最後都會轉化爲Splash HTTP Response
示例:
function main(splash) return {hello="world"} end
返回字典
function main(splash) return 'hello' end
返回字符串
2.1.2 異步處理
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
腳本中調用wait方法 相似python中的sleep 單位秒
當splash執行到此方法會轉而處理其餘任務,而後在指定時間再回來繼續處理
字符串拼接和python不一樣 用的是 .. 操做符
更多Lua腳本語法:http://www.runoob.com/lua/lua-basic-syntax.html
2.2 splash 對象屬性
main() 方法中第一個參數 splash 這個對象很是重要 相似selenium中webdriver對象
能夠經過調用splash 的屬性和方法 控制加載過程
屬性
2.2.1 args 獲取加載時配置參數 例如URL 若是是get請求 能夠獲取get請求參數 若是是post請求 能夠獲取表單提交數據
splash也支持第二個參數直接做爲args
示例:
function main(splash,args) local url = args.url end
等價於
function main(splash) local url = spalsh.args.url end
2.2.2 js_enabled 這個屬性是splash的js執行開關 能夠配置成true或false 控制是否執行js代碼 默認爲true
例如: 禁止js執行
function main(splash,args) splash:go("http://www.baidu.com") splash.js_enabled = false local title = splash:evaljs("document.title") return {title=title} end
結果拋出異常
2.2.3 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
此屬性適合網頁加載速度比較慢的狀況設置 若是超時無響應拋出異常並忽略
2.2.4 images_enabled 設置圖片是否加載 默認加載
優勢 禁用該屬性後 能夠節省網絡流量提升網頁加載速度
缺點 可能會影響js渲染 禁用圖片後外層DOM節點高度會受影響 進而影響DOM節點位置
若是js對圖片節點有操做的話,執行就會受到影響
注意 splash 使用了緩存 若是開始加載了圖片 而後禁用圖片加載 再從新加載頁面 圖片還會顯示 重啓splash服務便可
禁用圖片加載示例:
function main(splash,args) splash.images_enabled = false assert(splash:go("https://www.jd.com")) return {png=splash:png()} end
2.2.5 plugins_enabled 控制瀏覽器插件(例如Flash)是否開啓 默認false 表示不開啓
經過 splash.plugins_enabled = true/false 控制開啓或關閉
2.2.6 scroll_position 設置此屬性能夠控制頁面上下或者左右滾動 比較經常使用的屬性
示例:
function main(splash,args) assert(splash:go("https://www.taobao.com")) splash.scroll_position = {y=400} return {png=splash:png()} end
若是讓頁面左右滾動 傳入x參數 以下:
splash.scroll_position = {x=100,y=200}
2.3 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變量中包含錯誤信息 不然表示頁面加載成功
示例:模擬POST請求 傳入表單數據 若是成功返回頁面源代碼
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
wait() 控制頁面等待時間 用法以下:
ok,reason = splash:wait{time,cancle_on_redirect=false,cancle_on_error=true}
參數說明
time 等待秒數 cancle_on_redirect 可選 默認false 表示若是發生重定向就中止等待 並返回重定向結果 cancle_on_error 可選 默認false 表示若是發生了加載錯誤就中止等待
示例:
function main(splash) splash:go("https://www.taobao.com") splash:wait(2) return {html=splash:html()} end
jsfunc() 此方法能夠直接調用js定義的方法 調用的方法須要用雙中括號包圍 至關於實現了js方法到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
更多Lua腳本的更多轉換細節 官方文檔:
https://splash.readthedocs.io/en/stable/scripting-ref.html#splash-jsfunc
evaljs() 能夠執行js代碼 並返回最後一條js語句結果 用法以下
results = splash.evaljs(js)
例如:
local title = splash.evaljs("document.title")
runjs() 執行js代碼 與 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
autoload() 設置每一個頁面訪問時自動加載的對象 用法以下
ok,reason = splash:autoload{source_or_url,source=nil,url=nil}
參數說明
source_or_url js代碼或者js庫連接 source js代碼 url js庫連接
此方法只負責加載js代碼或庫 不執行任何操做 執行操做調用 evaljs() 或 runjs()
示例:
function main(splash,args) splash:autoload([[ function get_document_title(){ return document.title } ]]) splash:go("http://www.baidu.com") return splash:evaljs("get_document_title()") end
另外也能夠加載某些方法庫 例如JQuery
示例:
function main(splash,args) assert(splash:autoload("http://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
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
第一次截圖時網頁尚未加載出來,截圖爲空,第二次網頁便加載成功了。
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() 此方法用來模擬發送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, 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()將結果返回
get_viewport_size() 此方法能夠獲取當前瀏覽器頁面的大小,即寬高
function main(splash) splash:go("https://www.baidu.com/") return splash:get_viewport_size() end
set_viewport_size() 此方法能夠設置當前瀏覽器頁面的大小,即寬高 用法以下:
splash:set_viewport_size(width, height) function main(splash) splash:set_viewport_size(400, 800) assert(splash:go("https://h5.m.taobao.com/")) return splash:png() end
set_viewport_full() 此方法能夠設置瀏覽器全屏顯示,示例以下:
function main(splash) splash:set_viewport_full() assert(splash:go("https://h5.m.taobao.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
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屬性
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()方法填寫了文本,而後返回網頁截圖
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選擇器選中了節點的正文內容,隨後遍歷了全部節點,將其中的文本獲取下來
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()方法提交查詢,而後頁面等待三秒,返回截圖
Splash的更多API操做 官方文檔
https://splash.readthedocs.io/en/stable/scripting-ref.html
針對頁面元素的API操做
https://splash.readthedocs.io/en/stable/scripting-element-object.html
2.4 Splash API調用
前面說明了Splash Lua腳本的用法,但這些腳本是在Splash頁面中測試運行的,如何才能利用Splash渲染頁面,
怎麼才能和Python程序結合使用並抓取JavaScript渲染的頁面
Splash提供了一些HTTP API接口,只須要請求這些接口並傳遞相應的參數便可
2.4.1 render.html
此接口用於獲取JavaScript渲染的頁面的HTML代碼,接口地址就是Splash的運行地址加此接口名稱,
例如http://localhost:8050/render.html
用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)
此接口還支持代理設置、圖片加載設置、Headers設置、請求方法設置,
具體的用法能夠參見官方文檔https://splash.readthedocs.io/en/stable/api.html#render-html
2.4.2 render.png
此接口能夠獲取網頁截圖,其參數比render.html多了幾個,
好比經過width和height來控制寬高,它返回的是PNG格式的圖片二進制數據。示例以下
用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('jd.png', 'wb') as f: f.write(response.content)
詳細的參數設置能夠參考官網文檔https://splash.readthedocs.io/en/stable/api.html#render-png
2.4.3 render.jpeg
此接口和render.png相似,不過它返回的是JPEG格式的圖片二進制數據。
另外,此接口比render.png多了參數quality,它用來設置圖片質量。
2.4.4 render.har
此接口用於獲取頁面加載的HAR數據,示例以下:
http://localhost:8050/render.har?url=https://www.jd.com&wait=5
是一個JSON格式的數據,其中包含頁面加載過程當中的HAR數據
2.4.5 render.json
此接口包含了前面接口的全部功能,返回結果是JSON格式,示例以下
http://localhost:8050/render.json?url=https://httpbin.org
此外還有更多參數設置,具體能夠參考官方文檔:https://splash.readthedocs.io/en/stable/api.html#render-json
2.4.6 execute
此接口可實現與Lua腳本的對接
示例1
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)
經過lua_source參數傳遞了轉碼後的Lua腳本,經過execute接口獲取了最終腳本的執行結果
示例2
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)
用urllib.parse模塊裏的 quote()方法將腳本進行URL轉碼,
隨後構造了Splash請求URL,將其做爲lua_source參數傳遞,這樣運行結果就會顯示Lua腳本執行後的結果
返回結果是JSON形式,咱們成功獲取了請求的URL、狀態碼和網頁源代碼
以前所說的Lua腳本都可以用此方式與Python進行對接,全部網頁的動態渲染、模擬點擊、表單提交、頁面滑動、延時等待後的一些結果都可以自由控制。