python3編寫網絡爬蟲15-Splash的使用


Splash是一個JavaScript渲染服務 是一個帶有HTTP API的輕量級瀏覽器 同時對接了python的Twisted 和QT庫

利用它可以實現對動態渲染頁面的抓取

功能介紹

1.異步方式處理多個網頁渲染過程
2.獲取渲染后的頁面源代碼或截圖
3.通過關閉圖片渲染或使用Adblock規則加快頁面渲染速度
4.可執行特定js腳本
5.可通過Lua腳本來控制頁面渲染過程
6.獲取渲染的詳細過程並通過HAR(HTTP Archive)格式呈現

安裝准備
1.Docker的安裝 (后面講到時會詳細講 這里先安裝)

windows下安裝:
win10 64位 推薦 Docker for windwos 官網下載最新安裝包:

https://docs.docker.com/docker-for-windows/install/

不是 64位的 下載 Docker Toolbox :

 https://docs.docker.com/toolbox/toolbox_install_windows/

下載后雙擊安裝 安裝完成后 進入命令行 輸入docker 運行沒有報錯證明安裝成功了

2.安裝splash 命令行執行 

docker run -p 8050:8050 scrapinghub/splash

顯示如下省略部分表示服務啟動了
[-] Site starting on 8050
[-] Starting factory <twisted.web.server.Site object at 0x7fb62b1957f0>

打開瀏覽器 訪問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進行對接,
所有網頁的動態渲染、模擬點擊、表單提交、頁面滑動、延時等待后的一些結果均可以自由控制。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM