爬蟲動態渲染頁面爬取之Splash的介紹和使用


 

Splash是一個JavaScript渲染服務,是一個帶有HTTP API的輕量級瀏覽器,同時它對接了Python中的Twisted和QT庫。
利用它,我們同樣可以實現動態渲染頁面的抓取。

 

 

1. 功能介紹和基本實例

### Splash的使用
'''
Splash是一個JavaScript渲染服務,是一個帶有HTTP API的輕量級瀏覽器,同時它對接了Python中的Twisted和QT庫。
利用它,我們同樣可以實現動態渲染頁面的抓取。
'''

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

## 基本實例
function main(splash, args)
  splash:go("http://www.baidu.com")
  splash:wait(0.5)
  local title = splash:evaljs("document.title")
  return {
    title = title
  }
end

 

2. Splash用lua腳本爬取網頁的基本使用介紹

  2.1 異步處理

## 異步處理
# ipairs,為集合元素進行編號(編號從1開始),類似於python的enumerate
# lua腳本語言中字符串拼接用 ..
# splash:wait()類似於python中的time.sleep()
# 當Splash執行wait方法時,它會轉而去處理其他任務,等到指定時間結束后再回來進行繼續處理

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

 

  2.2 Splash的對象屬性

    2.2.1 args,main方法中的第二個args屬性即加載到splash中,即 args.url == splash.args.url

 

    2.2.2 js_enabled,頁面JavaScript的執行開關,默認為true

## js_enabled,頁面JavaScript的執行開關,默認為True
function main(splash, args)
  splash:go("http://www.baidu.com")
  splash.js_enabled = False
  local title = splash:evaljs("document.title")
  return {
    title = title
  }
end

'''
執行結果:
{
    "error": 400,
    "type": "ScriptError",
    "description": "Error happened while executing Lua script",
    "info": {
        "type": "SPLASH_LUA_ERROR",
        "message": "[string \"function main(splash, args)\r...\"]:3: setAttribute(self, QWebSettings.WebAttribute, bool): argument 2 has unexpected type 'NoneType'",
        "source": "[string \"function main(splash, args)\r...\"]",
        "line_number": 3,
        "error": "setAttribute(self, QWebSettings.WebAttribute, bool): argument 2 has unexpected type 'NoneType'"
    }
}
'''

 

    2.2.3 resource_timeout,頁面加載超時時間,單位是秒

## resource_timeout,頁面加載超時時間,單位是秒
function main(splash, args)
  splash.resource_timeout = 0.01
  assert(splash:go("http://www.baidu.com"))
  return splash:png()
end

'''
{
    "error": 400,
    "type": "ScriptError",
    "description": "Error happened while executing Lua script",
    "info": {
        "source": "[string \"function main(splash, args)\r...\"]",
        "line_number": 3,
        "error": "network5",
        "type": "LUA_ERROR",
        "message": "Lua error: [string \"function main(splash, args)\r...\"]:3: network5"
    }
}
'''

 

    2.2.4 images_enabled,頁面圖片是否加載,默認為true

## images_enabled,頁面圖片是否加載,默認為True
## 禁用圖片可以節省網絡流量以及加快網頁加載速度,當加載頁面可能會影響JavaScript的渲染
## Splash使用了緩存,即訪問過一次頁面的圖片,即使禁用頁面圖片加載后,依舊可以訪問到緩存中的圖片
## 如下面示例,返回的phg頁面就不會帶有圖片

function main(splash, args)
  splash.images_enabled = false
  assert(splash:go("https://www.bilibili.com/"))
  return {
    png = splash:png()
  }
end

 

    2.2.4 plugins_enabled,控制瀏覽器插件(如flash等)是否開啟,默認為false

 

    2.2.5 scroll_position,控制頁面的上下左右滾動,用x定位左右,y定位上下;如下示例,定位到y=800的位置

## scroll_position,控制頁面的上下左右滾動,用x定位左右,y定位上下;如下示例,定位到y=800的位置

function main(splash, args)
  assert(splash:go("https://www.mi.com"))
  splash.scroll_position = {y = 800}
  return {png = splash:png()}
end

 

  2.3 Splash對象的方法

    2.3.1 go方法,用來請求某個鏈接,可以模擬get和post請求,同時傳入請求頭、表單等數據,賦值傳入變量時用{}

## go方法,用來請求某個鏈接,可以模擬get和post請求,同時傳入請求頭、表單等數據,賦值傳入變量時用{}
## go方法有2個返回值,ok和reason,ok為空代表網頁加載出現錯誤,此時reason變量中包含錯誤原因
# url:請求的URL
# baseurl:可選參數,默認為空,表示自願加載的相對路徑
# headers:可選參數,默認為空,請求頭
# http_method:可選參數,默認為get,可以支持post
# body:可選參數,默認為空,發post請求時的表單數據,傳入的數據內容類型為json
# formdata:可選參數,默認為空,發post請求時的表單數據,傳入的數據內容類型為x-www-form-urlencoded

function main(splash, args)
  local ok, reason = splash:go{url="http://httpbin.org/post", http_method="POST", body="name=dmr"}
  if ok then
    return splash:html()
  end
end

 

    2.3.2 wait方法,控制頁面的等待時間,可以傳入多值,賦值傳入變量時用{}

## wait方法,控制頁面的等待時間,可以傳入多值,賦值傳入變量時用{}
## 同樣有2個返回值,ok和reason,ok為空代表網頁加載出現錯誤,此時reason變量中包含錯誤原因
## ok, reason = splash:go{time, cancel_on_redirect=false, cancel_on_error=false}
# time:等待的秒數
# cancel_on_redirect:可選參數,默認為false,表示如果發生了重定向則停止等待並返回重定向結果
# cancel_on_error:可選參數,默認為false,表示如果發生了錯誤就停止等待

function main(splash, args)
  local ok, reason = splash:go("http://www.mi.com")
  splash:wait(2)
  if ok then
    return splash:html()
  end
end

 

    2.3.3 JavaScript等的操作方法

## jsfunc方法,可以直接調用JavaScript定義的方法,所調用的方法要用雙括號包圍
## 如下示例,通過構造JavaScript方法來獲取訪問頁面的title和div數量並返回
function main(splash, args)
  local get_div_count = splash:jsfunc([[
    function(){
    var title = document.title;
    var body = document.body;
    var divs = body.getElementsByTagName('div');
    var div_count = divs.length;
    return {div_count, title};
      }
    ]])
  ok, reason = splash:go("https://www.mi.com")
  result = get_div_count()
  return ("This page'title is %s, there are %s divs"):format(result.title, result.div_count)
end


## evaljs方法,可以執行JavaScript代碼並返回最后一條JavaScript的返回結果
## 如下示例,只返回最后一條JavaScript語句的執行結果
function main(splash, args)
  splash:go("https://www.mi.com")
  result = splash:evaljs("document.title;document.body.getElementsByTagName('div').length;")
  return result
end


## runjs方法,可以執行JavaScript代碼,與evaljs類似,但是更偏向於執行某些動作或聲明某些方法
## 如下示例,用runjs方法構造了一個JavaScript定義的方法,然后用evaljs執行此方法獲取返回結果
function main(splash, args)
  splash:go("https://www.mi.com")
  splash:runjs("fofo = function(){return 'dmr'}")
  result = splash:evaljs('fofo()')
  return result
end


## autoload方法,可以設置每個頁面訪問時自動加載的對象,在Lua語言中nil相當於python的None
## ok, reason = splash:autoload{source_or_url, source=nil, url=nil}
# source_or_url:JavaScript代碼或JavaScript庫鏈接
# source:JavaScript代碼
# url:JavaScript庫鏈接
# 示例1,通過構造一個get_path_title對象方法,用evaljs調用執行獲取返回結果,在這里與runjs類似,不過構造方法需要用[]中括號
function main(splash, args)
  splash:autoload([[
    function get_path_title(){
    return document.title;
  }
    ]])
  splash:go("https://www.mi.com")
  result = splash:evaljs('get_path_title()')
  return result
end

# 示例2,用autoload加載jquery方法庫
function main(splash, args)
  assert(splash:autoload("https://code.jquery.com/jquery-2.2.4.min.js"))
  assert(splash:go("https://www.baidu.com"))
  local version = splash:evaljs("$.fn.jquery")
  return 'Version is '..version
end

 

    2.3.4 call_later方法,設置定時任務進行延時執行,並且可以在執行前通過cancel()方法重新執行定時任務

## call_later方法,設置定時任務進行延時執行,並且可以在執行前通過cancel()方法重新執行定時任務
# 如下示例,構造一個timer定時任務,當訪問頁面時,等待0.2秒獲取頁面的截圖,再等待1秒后獲取頁面的截圖
# 第一次獲取頁面的截圖頁面還沒加載出來,所以獲取到的是空白頁

function main(splash, args)
  local pngs = {}
  local timer = splash:call_later(function()
    pngs['a'] = splash:png()
    splash:wait(1)
    pngs['b'] = splash:png()
    end, 0.2)
  splash:go("https://www.mi.com")
  return pngs
end

 

    2.3.5 http_get方法,模擬發送http的get請求

## http_get方法,模擬發送http的get請求
## response = splash:http_get{url, headers=nil, follow_redirects=true}
# url:請求URL
# headers:可選參數,默認為空,請求頭
# follwo_redirects:可選參數,表示是否啟動自動重定向,默認為true

function main(splash, args)
  local t = require("treat")
  local response = splash:http_get("https://www.taobao.com")
  if response.status == 200 then
    return {
      b_html = response.body,
      html = t.as_string(response.body),
      url = response.url,
      status = response.status,
    }
  end  
end

 

    2.3.6 http_post方法,模擬發送http的post請求,與http_get方法類似,不過多個body表單參數

## http_post方法,模擬發送http的post請求,與http_get方法類似,不過多個body表單參數
## response = splash:http_get{url, headers=nil, follow_redirects=true, body=nil}
# url:請求URL
# headers:可選參數,默認為空,請求頭
# follwo_redirects:可選參數,表示是否啟動自動重定向,默認為true
# body:可選參數,默認為空,表單數據
# 如下示例,將表單數據提交到了json中

function main(splash, args)
  local t = require("treat")
  local json = require("json")
  local response = splash:http_post{
    "http://httpbin.org/post", 
    body=json.encode({name="dmr"}),
    headers={["content-type"]="application/json"}
  }
  if response.status == 200 then
    return {
      'response',
      b_html = response.body,
      html = t.as_string(response.body),
      url = response.url,
      status = response.status, 
    }
  end  
end

 

    2.3.7 set_content方法,用來設置頁面的內容

## set_content方法,用來設置頁面的內容
## 如下示例,可以看到頁面中有dmr的內容

function main(splash, args)
  assert(splash:set_content("<h1>dmr</h1>"))
  return splash:png()
end

 

    2.3.8 html方法,用來獲取網頁的源代碼

## html方法,用來獲取網頁的源代碼

function main(splash, args)
  assert(splash:go("https://www.baidu.com"))
  return splash:html()
end

 

    2.3.9 png方法和jpeg方法,用來獲取網頁頁面png格式或jpeg格式的截圖

## png方法和jpeg方法,用來獲取網頁頁面png格式或jpeg格式的截圖

function main(splash, args)
  assert(splash:go("https://www.baidu.com"))
  return {
    png = splash:png(),
    jpeg = splash:jpeg()
    }
end

 

    2.3.10 har方法,用來獲取頁面加載過程描述

## har方法,用來獲取頁面加載過程描述

function main(splash, args)
  assert(splash:go("https://www.baidu.com"))
  return {
    har = splash:har()
    }
end

 

    2.3.11 url方法,用來獲取當前正在訪問頁面的url 

## url方法,用來獲取當前正在訪問頁面的url

function main(splash, args)
  assert(splash:go("https://www.baidu.com"))
  return {url = splash:url()}
end

 

    2.3.12 Cookies操作

## get_cookies方法,用來獲取當前頁面的cookies
function main(splash, args)
  assert(splash:go("https://www.baidu.com"))
  return {cookies = splash:get_cookies()}
end


## add_cookie方法,為當前頁面添加cookie
## splash:add_cookie{name, value, path=nil, domain=nil, expires=nil, httpOnly=nil, secure=nil}
function main(splash, args)
  splash:add_cookie{'name', 'dmr'}
  assert(splash:go("https://www.baidu.com"))
  return {cookies = splash:get_cookies()}
end


## clear_cookies方法,清楚所有的cookies
function main(splash, args)
  splash:add_cookie{'name', 'dmr'}
  assert(splash:go("https://www.baidu.com"))
  splash:clear_cookies()
  return {cookies = splash:get_cookies()}
end

 

    2.3.13 瀏覽器視圖操作

## get_viewport_size方法,用來獲取當前頁面的大小,即寬高
function main(splash, args)
  assert(splash:go("https://www.taobao.com"))
  return splash:get_viewport_size()
end


## set_viewport_size方法,設置當前瀏覽器頁面的大小,即寬高
function main(splash, args)
  splash:set_viewport_size(400, 400)
  assert(splash:go("https://www.taobao.com"))
  return splash:png()
end


## set_viewport_full方法,用來設置瀏覽器全屏顯示
function main(splash, args)
  splash:set_viewport_full()
  assert(splash:go("https://www.taobao.com"))
  return splash:png()
end

 

    2.3.14 set_user_agent方法,用來設置瀏覽器的User-Agent

## set_user_agent方法,用來設置瀏覽器的User-Agent

function main(splash, args)
  splash:set_user_agent('Splash')
  assert(splash:go("https://httpbin.org/get"))
  return splash:html()
end

 

    2.3.15 set_custom_headers方法,可以用來設置請求頭

## set_custom_headers方法,可以用來設置請求頭

function main(splash, args)
  splash:set_custom_headers({
      ["User-Agent"] = "Splash",
      ["Host"] = "Splash.org"
    })
  assert(splash:go("https://httpbin.org/get"))
  return splash:html()
end

 

    2.3.16 select方法,查找符合條件的第一個節點,用的是CSS選擇器

## select方法,查找符合條件的第一個節點,用的是CSS選擇器

function main(splash, args)
  assert(splash:go("https://www.taobao.com"))
  input = splash:select("#q")
  input:send_text("數碼")
  splash:wait(2)
  return splash:jpeg()
end

 

    2.3.17 select_all方法,查找符合條件的所有節點,用的是CSS選擇器

## select_all方法,查找符合條件的所有節點,用的是CSS選擇器

function main(splash, args)
  local treat = require("treat")
  assert(splash:go("https://movie.douban.com/top250"))
    assert(splash:wait(1))
  local items = splash:select_all(".inq")
  local sum = {}
  for index, item in ipairs(items) do
    sum[index] = item.node.innerHTML
  end
  return {
    obj1 = sum,
    obj2 = treat.as_array(sum)
  }
end

 

    2.3.18 mouse_click方法,模擬鼠標點擊操作

## mouse_click方法,模擬鼠標點擊操作
## 1.傳入x和y進行點擊操作
## 2.查找到相關節點,調用此方法進行點擊操作

function main(splash, args)
  splash:go("https://www.baidu.com")
  input = splash:select("#kw")
  input:send_text("Python")
  splash:wait(1)
  search = splash:select('#su')
  search:mouse_click()
  splash:wait(2)
  return splash:png()
end

 

3. Splash API的調用,以Python為例

    官方API文檔:https://splash.readthedocs.io/en/stable/api.html

 

  3.1 render.html頁面,此接口用於獲取JavaScript渲染的頁面的HTML代碼

## render.html頁面,此接口用於獲取JavaScript渲染的頁面的HTML代碼
## 獲取百度頁面源代碼url示例:http://localhost:8050/render.html?url=https://www.baidu.com
# 示例,通過調用render.html頁面獲取百度的源代碼並且設置等待時間為4秒
import requests

url = 'http://10.0.0.100:8050/render.html?url=https://www.baidu.com&wait=4'
response = requests.get(url)
print(response.text)

 

  3.2 render.png和render.jpeg,此接口獲取網頁截圖, 返回的是二進制數據

## render.png和render.jpeg,此接口獲取網頁截圖,返回的是二進制數據
## 配置參數:url,wait,width,height
## render.jpeg多了個參數quality,用來調整圖片的質量,取值1-100,默認值為75,應盡量避免取95以上的數值
## url示例:http://localhost:8050/render.png?url=https://www.baidu.com&wait=2&width=400&height=400
# 示例,通過render.png接口獲取頁面寬400高400的頁面截圖並保存到文件中
import requests

url = 'http://10.0.0.100:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700'
url2 = 'http://10.0.0.100:8050/render.jpeg?url=https://www.taobao.com&wait=5&width=1000&height=700&quality=90'
response = requests.get(url)
with open('taobao.png', 'wb') as f:
    f.write(response.content)
response2 = requests.get(url2)
with open('taobao.jpeg', 'wb') as f:
    f.write(response2.content)

 

  3.3 render.har,此接口用來獲取頁面加載的HAR數據,返回的是json格式的數據

## render.har,此接口用來獲取頁面加載的HAR數據,返回的是json格式的數據
## url示例:http://localhost:8050/render.har?url=https://www.baidu.com&wait=2
import requests, json

url = 'http://10.0.0.100:8050/render.har?url=https://www.baidu.com&wait=2'
response = requests.get(url)
print(response.content)
with open('har.text', 'w') as f:
    f.write(json.dumps(json.loads(response.content), indent=2))

 

  3.4 render.json,此接口包含了前面接口的所有功能,返回結果是json格式

## render.json,此接口包含了前面接口的所有功能,返回結果是json格式
## url示例:http://localhost:8050/render.json?url=https://www.baidu.com&html=1&png=1&jpeg=1&har=1
## 默認返回:{'url': 'https://www.baidu.com/', 'requestedUrl': 'https://www.baidu.com/', 'geometry': [0, 0, 1024, 768], 'title': '百度一下,你就知道'}
## 通過將html、png、jpeg、har參數置為1獲取相關的頁面數據
import requests, json

url = 'http://10.0.0.100:8050/render.json?url=https://www.baidu.com&html=1&png=1&jpeg=1&har=1'
response = requests.get(url)

data = json.loads(response.content)
print(data)
print(data.get('html'))
print(data.get('png'))
print(data.get('jpeg'))
print(data.get('har'))

 

  3.5 execute,功能強大,此接口可實現與Lua腳本的對接,實現交互性操作

## execute,此接口可實現與Lua腳本的對接,可實現交互性操作
## url示例:http://localhost:8050/execute?lua_source=
# 示例1,簡單示例,返回lua的執行結果
from urllib.parse import quote
import requests

lua = '''
function main(splash)
  return 'hello'
end
'''
url = 'http://10.0.0.100:8050/execute?lua_source=%s' % quote(lua)
response = requests.get(url)
print(response.text)
# 示例2,通過execute執行lua腳本獲取頁面的url,png,html
from urllib.parse import quote
import requests, json

lua = '''
function main(splash)
  splash:go('https://www.baidu.com')
  splash:wait(2)
  return {
    html = splash:html(),
    png = splash:png(),
    url = splash:url()
  }
end
'''
url = 'http://10.0.0.100:8050/execute?lua_source=%s' % quote(lua)
response = requests.get(url)
print(type(response.text), response.text)
dic = json.loads(response.text)
print(type(dic), len(dic), dic.keys())

 

4. Splash通過Nginx配置負載均衡基本思路

 

1. 配置多台splash服務器

2. 選中其中一台或者另起一台服務器安裝nginx服務

3. 配置nginx.conf配置文件,內容大致為:
http {
    upstream splash {
        least_conn; #最少連接負載均衡,不配置這進行輪詢,ip_hash配置ip散列負載均衡
        server 10.0.0.100:8050;
        server 10.0.0.99:8050;
        server 10.0.0.98:8050;
        server 10.0.0.97:8050;
    }
    server {
        listen 8050;
        location / {
            proxy_pass http://splash;   # 指定域名
            auth_basic "Restricted";    # 配置認證
            auth_basic_user_file /etc/nginx/conf.d/.htpasswd;   # 指定認證的用戶密碼文件
        }
    }
}

4. 如不配置認證,直接重載nginx:sudo nginx -s reload;配置了認證則需要構建密碼文件,建議用htpasswd命令構建,在重載nginx

5. 進行相關測試,測試腳本
from urllib.parse import quote
import requests, re

lua = '''
function main(splash)
  local treat = require('treat')
  response = splash:http_get('https://www.baidu.com')
  return treat.as_string(response.body)
end
'''
url = 'http://splash/execute?lua_source=%s' % quote(lua)
response = requests.get(url)
ip = re.search('(\d+\.\d+\.\d+\.\d+)', response.text).group(1)
print(ip)
View Code

 


免責聲明!

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



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