Splash 是一個 JavaScript 渲染服務,是一個帶有 HTTP API 的輕量級瀏覽器,同時它對接了 Python 中的 Twisted和 QT 庫,利用它咱們一樣能夠實現動態渲染頁面的抓取。html
利用 Splash 咱們能夠實現以下功能:node
接下來咱們來了解一下它的具體用法。jquery
在本節開始以前請確保已經正確安裝好了 Splash 並能夠正常運行服務,如沒有安裝能夠參考第一章的安裝說明。nginx
首先咱們能夠經過 Splash 提供的 Web 頁面來測試 Splash的渲染過程,例如咱們在本機 8050 端口運行了 Splash 服務,打開:http://localhost:8050/ 便可看到其 Web 頁面,如圖 7-6 所示:web
圖 7-6 Web 頁面
在右側呈現的是一個渲染示例,咱們能夠看到在上方有一個輸入框,默認是:http://google.com,咱們在這裏換成百度測試一下,將內容更改成:https://www.baidu.com,而後點擊按鈕,開始渲染,結果如圖 7-7 所示:編程
[外鏈圖片轉存失敗(img-wYIGP9Th-1565082895571)(https://upload-images.jianshu.io/upload_images/17885815-a053c5ffb0f8ea03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]json
圖 7-7 運行結果
能夠看到網頁的返回結果呈現了渲染截圖、HAR 加載統計數據、網頁的源代碼。
經過 HAR 的結果咱們能夠看到 Splash 執行了整個網頁的渲染過程,包括 CSS、JavaScript 的加載等過程,呈現的頁面和咱們在瀏覽器獲得的結果徹底一致。
那麼這個過程是由什麼來控制的呢?咱們從新返回首頁能夠看到其實是有一段腳本,內容以下:api
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 Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
這個腳本是實際是 Lua 語言寫的腳本,Lua也是一門編程語言,簡潔易用。
即便咱們不懂這個語言的語法,但經過腳本表面意思咱們也能夠大體瞭解到它是首先調用 go() 方法去加載頁面,而後調用 wait() 方法等待了必定的時間,最後返回了頁面的源碼、截圖和 HAR 信息。
因此到這裏咱們能夠大致瞭解到 Splash 是經過 Lua 腳原本控制了頁面的加載過程,加載過程徹底模擬瀏覽器,最後可返回各類格式的結果,如網頁源碼、截圖等。
因此接下來咱們要學會用 Splash 的話,一是須要了解其中 Lua 腳本的寫法,二是須要了解相關 API 的用法,那麼接下來咱們就來了解一下這兩部份內容。瀏覽器
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/ 的代碼編輯區域,而後點擊按鈕來測試一下。
這樣咱們就會看到其返回了網頁的標題,這裏咱們是經過 evaljs() 方法傳入 JavaScript 腳本,而 document.title 的執行結果就是返回網頁標題,執行完畢後賦值給一個 title 變量,隨後將其返回,這樣就能夠看到其返回結果就是網頁標題了,如圖 7-8 所示:
[外鏈圖片轉存失敗(img-Ynaq32CQ-1565082895572)(https://upload-images.jianshu.io/upload_images/17885815-37d5a25df0739240.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-8 運行結果
注意到咱們在這裏定義的方法名稱叫作 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
運行後的返回結果是三個站點的截圖,如圖 7-9 所示:
圖 7-9 運行結果
在腳本內調用了 wait() 方法,這相似於 Python 中的 sleep(),參數爲等待的秒數,當 Splash 執行到此方法時,它會轉而去處理其餘的任務,而後在指定的時間事後再回來繼續處理。
在這裏值得注意的是 Lua 腳本中的字符串拼接,和 Python不一樣,這裏的字符串拼接使用的是 .. 操做符,而不是 +,若有必要能夠簡單瞭解一下Lua腳本的語法,連接:http://www.runoob.com/lua/lua...。
另外這裏咱們作了加載時的異常檢測,go() 方法會返回加載頁面的結果狀態,若是頁面出現 4XX 或 5XX 狀態碼,ok 變量就會爲空,就不會返回加載後的圖片。
咱們注意到在前面的例子中 main() 方法的第一個參數是 splash,這個對象很是重要,相似於在 Selenium 中的WebDriver 對象:
from selenium import webdriver browser = webdriver.Chrome()
如上所示,如今的 splash 對象就如同此處 Selenium 中的 browser 對象,咱們能夠調用它的一些屬性和方法來控制加載過程,接下來咱們首先看下它的屬性。
splash 對象的 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
這個屬性是 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" }
不過通常來講咱們不用設置此屬性開關,默認開啓便可。
此屬性能夠設置加載的超時時間,單位是秒,若是設置爲 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" }
此屬性適合在網頁加載速度較慢的狀況下設置,若是超過了某個時間無響應則直接拋出異常並忽略便可。
此屬性能夠設置圖片是否加載,默認狀況下是加載的,可是禁用以後能夠節省網絡流量並提升網頁加載速度,可是值得注意的是禁用圖片加載以後可能會影響 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
這樣返回的頁面截圖就不會帶有任何圖片,加載速度也會快不少。
此屬性能夠控制瀏覽器插件是否開啓,如 Flash 插件。默認此屬性是 False 不開啓,可使用以下代碼控制其開啓和關閉:
splash.plugins_enabled = true/false
此屬性能夠控制頁面的滾動偏移,經過設置此屬性咱們能夠控制頁面上下或左右滾動,仍是比較經常使用的一個屬性,咱們用一個實例感覺一下:
function main(splash, args) assert(splash:go('https://www.taobao.com')) splash.scroll_position = {y=400} return {png=splash:png()} end
這樣咱們就能夠控制頁面向下滾動 400 像素值,結果如圖 7-10 所示:
[外鏈圖片轉存失敗(img-Nb3jZnGZ-1565082895575)(https://upload-images.jianshu.io/upload_images/17885815-4bde1b0c3e5329f3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-10 運行結果
若是要控制左右滾動能夠傳入 x 參數,代碼以下:
splash.scroll_position = {x=100, y=200}
go() 方法就是用來請求某個連接的方法,並且它能夠模擬 GET 和 POST 請求,同時支持傳入 Headers、Form Data 等數據,用法以下:
ok, reason = splash:go{url, baseurl=nil, headers=nil, http_method="GET", body=nil, formdata=nil}
參數說明以下:
返回的結果是結果 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 Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
在這裏咱們模擬了一個 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 請求併發送了表單數據。
此方法能夠控制頁面等待時間,使用方法以下:
ok, reason = splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}
參數說明以下:
返回結果一樣是結果 ok 和緣由 reason 的組合。
咱們用一個實例感覺一下:
function main(splash) splash:go("https://www.taobao.com") splash:wait(2) return {html=splash:html()} end
如上代碼能夠實現訪問淘寶並等待 2 秒,隨後返回頁面源代碼的功能。
此方法能夠直接調用 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
首選咱們聲明瞭一個方法,而後在頁面加載成功後調用了此方法計算出了頁面中的 div 節點的個數。
但這只是 Splash 提供的 Web 頁面功能,更多的功能咱們可使用它提供的 HTTP API 來完成 JavaScript 渲染過程。
關於更多 JavaScript 到 Lua 腳本的轉換細節能夠參考官方文檔介紹:https://splash.readthedocs.io...。
此方法能夠執行 JavaScript 代碼並返回最後一條語句的返回結果,使用方法以下:
result = splash:evaljs(js)
好比咱們能夠用下面的代碼來獲取頁面的標題:
local title = splash:evaljs("document.title") runjs()
此方法能夠執行 JavaScript 代碼,和 evaljs() 功能相似,可是此方法更偏向於執行某些動做或聲明某些方法,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 Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
在這裏咱們用 runjs() 先聲明瞭一個 JavaScript 定義的方法,而後經過 evaljs() 來調用獲得結果。
運行結果以下:
bar
此方法能夠設置在每一個頁面訪問時自動加載的對象,使用方法以下:
ok, reason = splash:autoload{source_or_url, source=nil, url=nil}
參數說明以下:
可是此方法只負責加載 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() 調用了此方法執行。
運行結果:
百度一下,你就知道
另外咱們也能夠加載某些方法庫,如 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
此方法能夠經過設置定時任務和延遲時間實現任務延時執行,而且能夠在執行前經過 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 秒時再次獲取網頁截圖,訪問的頁面是淘寶,最後將截圖結果返回。
運行結果如圖 7-11 所示:
[外鏈圖片轉存失敗(img-CR8NPo3D-1565082895576)(https://upload-images.jianshu.io/upload_images/17885815-6ae7ea423aed649c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-11 運行結果
咱們能夠發現第一次截圖網頁尚未加載出來,截圖爲空,第二次網頁便加載成功了。
此方法能夠模擬發送 HTTP 的 GET 請求,使用方法以下:
response = splash:http_get{url, headers=nil, 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_get() 方法相似,此方法是模擬發送一個 POST 請求,不過多了一個參數 body,使用方法以下:
response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}
參數說明以下:
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 請求併發送了表單數據。
此方法能夠用來設置頁面的內容,示例以下:
function main(splash) assert(splash:set_content("<html><body><h1>hello</h1></body></html>")) return splash:png() end
運行結果如圖 7-12 所示:
[外鏈圖片轉存失敗(img-IvzYSj6W-1565082895577)(https://upload-images.jianshu.io/upload_images/17885815-a8a9045645a4ffae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-12 運行結果
此方法能夠用來獲取網頁的源代碼,很是簡單又經常使用的方法,示例以下:
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 格式的網頁截圖,示例以下:
function main(splash, args) splash:go("https://www.taobao.com") return splash:png() end
此方法能夠用來獲取 JPEG 格式的網頁截圖,示例以下:
function main(splash, args) splash:go("https://www.taobao.com") return splash:jpeg() end
此方法能夠用來獲取頁面加載過程描述,示例以下:
function main(splash, args) splash:go("https://www.baidu.com") return splash:har() end
運行結果如圖 7-13 所示:
[外鏈圖片轉存失敗(img-Qo58xLDp-1565082895578)(https://upload-images.jianshu.io/upload_images/17885815-56c5ddd9f221c90c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-13 運行結果
在這裏顯示了頁面加載過程當中的每一個請求記錄詳情。
此方法能夠獲取當前正在訪問的 URL,示例以下:
function main(splash, args) splash:go("https://www.baidu.com") return splash:url() end
運行結果以下:
https://www.baidu.com/
此方法能夠獲取當前頁面的 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"
此方法能夠爲當前頁面添加 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被所有清空,沒有任何結果。
此方法能夠獲取當前瀏覽器頁面的大小,即寬高,示例以下:
function main(splash) splash:go("https://www.baidu.com/") return splash:get_viewport_size() end
運行結果:
Splash Response: Array[2] 0: 1024 1: 768
此方法能夠設置當前瀏覽器頁面的大小,即寬高,用法以下:
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 Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
運行結果如圖 7-14 所示:
[外鏈圖片轉存失敗(img-IlYRAAgO-1565082895578)(https://upload-images.jianshu.io/upload_images/17885815-abf0bd4551469ca2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-14 運行結果
此方法能夠設置瀏覽器全屏顯示,示例以下:
function main(splash) splash:set_viewport_full() assert(splash:go("http://cuiqingcai.com")) return splash:png() end
此方法能夠設置瀏覽器的 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 被成功設置。
此方法能夠設置請求的 Headers,示例以下:
function main(splash) splash:set_custom_headers({ ["User-Agent"] = "Splash", ["Site"] = "Splash", }) splash:go("http://httpbin.org/get") return splash:html() end
在這裏咱們設置了 Headers 中的 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>
能夠看到結果的 Headers 中兩個字段被成功設置。
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() 方法填寫了文本,而後返回網頁截圖。
結果如圖 7-15 所示:
圖 7-15 運行結果
能夠看到咱們成功填寫了輸入框。
此方法能夠選中全部的符合條件的節點,其參數是 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 個節點的正文內容獲取了下來。
此方法能夠模擬鼠標點擊操做,傳入的參數爲座標值 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() 方法提交查詢,而後頁面等待三秒,返回截圖,結果如圖 7-16 所示:
[外鏈圖片轉存失敗(img-ZTQ37gRw-1565082895581)(https://upload-images.jianshu.io/upload_images/17885815-7bea97e6c46ee282.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-16 運行結果
能夠看到在這裏咱們成功獲取了查詢後的頁面內容,模擬了百度搜索操做。
以上咱們介紹了 Splash 的經常使用 API 操做,還有一些 API 在這再也不一一介紹,更加詳細和權威的說明能夠參見官方文檔:https://splash.readthedocs.io...,此頁面介紹了 splash 對象的全部 API 操做,另外還有針對於頁面元素的 API 操做,連接爲:https://splash.readthedocs.io...。
在上文中咱們說明了 Splash Lua 腳本的用法,但這些腳本是在 Splash 頁面裏面測試運行的,咱們如何才能利用Splash 來渲染頁面呢?怎樣才能和 Python 程序結合使用並抓取 JavaScript 渲染的頁面呢?
其實 Splash 給咱們提供了一些 HTTP API 接口,咱們只須要請求這些接口並傳遞相應的參數便可獲取頁面渲染後的結果,下面咱們對這些接口進行介紹:
此接口用於獲取 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 秒多鍾便可獲取 JavaScript 渲染後的淘寶頁面源代碼。
另外此接口還支持代理設置、圖片加載設置、Headers設置、請求方法設置,具體的用法能夠參見官方文檔:https://splash.readthedocs.io...。
此接口能夠獲取網頁截圖,參數相比 render.html 又多了幾個,如 width、height 來控制寬高,返回的是 PNG 格式的圖片二進制數據。
示例以下:
curl http://localhost:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700
在這裏咱們還傳入了 width 和 height 來放縮頁面大小爲 1000x700 像素。
若是用 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)
獲得的圖片如圖 7-17 所示:
[外鏈圖片轉存失敗(img-asiKVNfa-1565082895582)(https://upload-images.jianshu.io/upload_images/17885815-ccc3343c91574c4c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-17 運行結果
這樣咱們就成功獲取了京東首頁渲染完成後的頁面截圖,詳細的參數設置能夠參考官網文檔:https://splash.readthedocs.io...。
此接口和 render.png 相似,不過它返回的是 JPEG 格式的圖片二進制數據。
另外此接口相比 render.png 還多了一個參數 quality,能夠用來設置圖片質量。
此接口用於獲取頁面加載的 HAR 數據,示例以下:
curl http://localhost:8050/render.har?url=https://www.jd.com&wait=5
返回結果很是多,是一個 Json 格式的數據,裏面包含了頁面加載過程當中的 HAR 數據。
結果如圖 7-18 所示:
[外鏈圖片轉存失敗(img-1ZejYGQA-1565082895583)(https://upload-images.jianshu.io/upload_images/17885815-232f53982c604542.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圖 7-18 運行結果
此接口包含了前面接口的全部功能,返回結果是 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...。
此接口才是最爲強大的接口,咱們在前面說了不少 Splash Lua 腳本的操做,用此接口即可以實現和 Lua 腳本的對接。
前面的 render.html、render.png 等接口對於通常的 JavaScript 渲染頁面是足夠了,可是若是要實現一些交互操做的話仍是無能爲力的,因此這裏就須要使用此 execute 接口來對接 Lua 腳本和網頁進行交互了。
咱們先實現一個最簡單的腳本,直接返回數據:
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 進行對接,這樣的話,全部網頁的動態渲染、模擬點擊、表單提交、頁面滑動、延時等待後的一些結果都可以自由控制,獲取頁面源碼和截圖都不在話下。
若是咱們用 Splash 來作 JavaScript 動態渲染的頁面的抓取的話,若是爬取的量很是大,任務很是多,若是咱們用一個 Splash 服務來處理的話未免壓力太大了,因此咱們能夠考慮搭建一個負載均衡器來把壓力分散到各個服務器上,這樣至關於多臺機器多個服務共同參與任務的處理,能夠減少單個 Splash 服務的壓力。
要搭建 Splash 負載均衡首先咱們須要有多個 Splash 服務,假如在這裏我在四臺遠程主機的 8050 端口上都開啓了 Splash 服務,它們的服務地址分別爲:41.159.27.223:8050、41.159.27.221:8050、41.159.27.9:8050、41.159.117.119:8050,四個服務徹底一致,都是經過 Docker 的 Splash 鏡像開啓的,訪問任何一個服務均可以使用 Splash 服務。
接下來咱們能夠選用任意一臺帶有公網 IP 的主機來配置負載均衡,首先須要在這臺主機上裝好 Nginx,而後修改 Nginx 的配置文件 nginx.conf,添加以下內容:
http { upstream splash { least_conn; server 41.159.27.223:8050; server 41.159.27.221:8050; server 41.159.27.9:8050; server 41.159.117.119:8050; } server { listen 8050; location / { proxy_pass http://splash; } } }
這樣咱們經過 upstream 字段定義了一個名字叫作 splash 的服務集羣配置,least_conn 表明最少連接負載均衡,它適合處理請求處理時間長短不一形成服務器過載的狀況。
或者咱們也能夠不指定配置,配置以下:
upstream splash { server 41.159.27.223:8050; server 41.159.27.221:8050; server 41.159.27.9:8050; server 41.159.117.119:8050; }
這樣默認以輪詢策略實現負載均衡,每一個服務器的壓力相同,此策略適合服務器配置至關,無狀態且短平快的服務使用。
另外咱們還能夠指定權重,配置以下:
upstream splash { server 41.159.27.223:8050 weight=4; server 41.159.27.221:8050 weight=2; server 41.159.27.9:8050 weight=2; server 41.159.117.119:8050 weight=1; }
咱們經過 weight 指定了各個服務的權重,權重越高分配處處理的請求越多,假如不一樣的服務器配置差異比較大的話,就可使用此種配置。
最後還有一種 IP 哈希負載均衡,配置以下:
upstream splash { ip_hash; server 41.159.27.223:8050; server 41.159.27.221:8050; server 41.159.27.9:8050; server 41.159.117.119:8050; }
服務器根據請求客戶端的 IP 地址進行哈希計算,確保使用同一個服務器響應請求,這種策略適合有狀態的服務,如用戶登陸後訪問某個頁面的情形。不過對於 Splash 來講不須要。
咱們能夠根據不一樣的情形選用不一樣的配置,配置完成後重啓一下 Nginx 服務:
sudo nginx -s reload
這樣直接訪問 Nginx 所在服務器的 8050 端口便可實現負載均衡了。
如今 Splash 是公開訪問的,若是咱們不想讓其被公開訪問還能夠配置認證,仍然藉助於 Nginx 便可,能夠在 server 的 location 字段中添加一個 auth_basic 和 auth_basic_user_file 字段,配置以下:
http { upstream splash { least_conn; server 41.159.27.223:8050; server 41.159.27.221:8050; server 41.159.27.9:8050; server 41.159.117.119:8050; } server { listen 8050; location / { proxy_pass http://splash; auth_basic "Restricted"; auth_basic_user_file /etc/nginx/conf.d/.htpasswd; } } }
在這裏使用的用戶名密碼配置放置在 /etc/nginx/conf.d 目錄,咱們須要使用 htpasswd 命令建立,例如建立一個用戶名爲 admin 的文件,命令以下:
htpasswd -c .htpasswd admin
接下就會提示咱們輸入密碼,輸入兩次以後,就會生成密碼文件,查看一下內容:
cat .htpasswd admin:5ZBxQr0rCqwbc
配置完成以後咱們重啓一下 Nginx 服務,運行以下命令:
sudo nginx -s reload
這樣訪問認證就成功配置好了。
最後咱們能夠用代碼來測試一下負載均衡的配置,看看究竟是不是每次請求會切換IP,利用 http://httpbin.org/get 測試便可,代碼實現以下:
import requests from urllib.parse import quote import re lua = ''' function main(splash, args) local treat = require("treat") local response = splash:http_get("http://httpbin.org/get") return treat.as_string(response.body) end ''' url = 'http://splash:8050/execute?lua_source=' + quote(lua) response = requests.get(url, auth=('admin', 'admin')) ip = re.search('(\d+\.\d+\.\d+\.\d+)', response.text).group(1) print(ip)
這裏的 URL 中的 splash 請自行替換成本身的 Nginx 服務器 IP,在這裏我修改了 Hosts 添加了 splash 別名。
屢次運行代碼以後能夠發現每次請求的 IP 都會變化:
如第一次的結果:
41.159.27.223
第二次的結果:
41.159.27.9 Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎
這就說明負載均衡已經成功實現了。
到如今爲止,咱們就能夠用 Python 和 Splash 實現JavaScript 渲染的頁面的抓取了,除了 Selenium,本節所說的 Splash 一樣能夠作到很是強大的渲染功能,同時它也不須要瀏覽器便可渲染,使用很是方便。
本節咱們還成功實現了負載均衡的配置,配置了負載均衡以後能夠多個 Splash 服務共同合做,減輕單個服務的負載,仍是比較有用的。