1. 程式人生 > >Class 17 - 2 動態渲染頁面爬取 — Splash

Class 17 - 2 動態渲染頁面爬取 — Splash

一、Splash 的使用

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

  1. 例項引入
    • 通過 Splash 提供的 Web 頁面來測試其渲染過程。例:在本機 8050 埠上執行 Splash 服務,開啟 http://localhost:8050/ 即可看到其 Web 頁面:
    • 黑色框顯示的是一個渲染示例。上方有個輸入框,預設是 http://google.com, 換成百度測試,將內容更改為 https://www.baidu.com,點選 Render me 按鈕開始渲染。 結果:
    • 網頁的返回結果呈現了渲染截圖、HAR 載入統計資料、網頁的原始碼。
    • 通過 HAR 的結果可以看到,Splash 執行整個網頁的渲染過程,包括 CSS、JavaScript 的載入等過程,呈現的頁面和瀏覽器中得到的結果完全一致。
    • 這個過程由什麼來控制呢?重新返回首頁,可以看到有段指令碼,內容:
      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

      這個指令碼是用 Lua 語言寫的指令碼。從指令碼的表面意思,它首先呼叫 go()方法去載入頁面,再呼叫 wait()方法等待一定時間,最後返回頁面的原始碼、截圖和 HAR 資訊。

    • Splash 通過 Lua 指令碼來控制了頁面載入過程,載入過程完全模擬瀏覽器,最後可返回各種格式的結果,如:網頁原始碼和截圖等。
  2. Splash Lua指令碼
    • 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/ 的程式碼編輯區域,點選 Render me!按鈕 測試

      • 它返回網頁的標題,這裡通過 evaljs()方法傳人 JavaScript 指令碼,而 document.title 的執行結果就是返回網頁標題,執行完畢後將其賦值給一個 title 變數,隨後將其返回:

        注意,在這裡定義的方法名稱叫作 main()。這個名稱必須是固定的,Splash 會預設呼叫這個方法

      • 該方法的返回值既可以是字典形式,也可以是字串形式,最後都會轉化為 Splash HTTP Response,例如:
        function main(splash)
          return { hello="world"  }
        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

        輸出:

        • 在指令碼內呼叫的 wait ()方法類似於 Python 中的 sleep(),其引數為等待的秒數。當 Splash 執行到此方法時,它會轉而去處理其他任務,然後在指定的時間過後再回來繼續處理。
        • 注意:Lua 指令碼中的字串拼接和 Python 不同,它使用的是..操作符,而不是+。簡單瞭解 Lua 指令碼的語法:http://www.runoob.com/lua/lua-basic-syntax.html。
        • 這裡做了載入時的異常檢測。go()方法會返回載入頁面的結果狀態,如果頁面州現 4xx 或 5xx 狀態碼,ok 變數就為空,不會返回載入後的圖片。
  3. Splash 物件屬性
    • 前面例子中 main()方法的第一個引數是 splash,這個物件非常重要,類似於 Selenium 中的 WebDriver 物件,可以呼叫它的一些屬性和方法來控制載入過程。
    • args
      • 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.url
        end
    • js_enabled
      • 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 程式碼,此時執行結果就會丟擲異常:

        {
            "error": 400,
            "info": {
                "line_number": 1,
                "error": "')' expected near char(239)",
                "source": "[string \"function main(splash, args)\r...\"]",
                "message": "[string \"function main(splash, args)\r...\"]:1: ')' expected near char(239)",
                "type": "LUA_INIT_ERROR"
            },
            "description": "Error happened while executing Lua script",
            "type": "ScriptError"
        }
        View Code

        一般來說,不用設定此屬性,預設開啟即可。

    • 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

        例,這裡將超時時間設定為 0.1秒。如果在 0.1 秒之內沒有得到響應,就會丟擲異常。 此屬性適合在網頁載入速度較慢的情況下設定,如果超過了某個時間無響應,則直接丟擲異常並忽略即可。

    • images_enabled      

      • images_enabled 屬性可以設定圖片是否載入,預設情況下是載入的。禁用該屬性後,可以節省網路流量並提高網頁載入速度。注意:禁用圖片載入可能會影響 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

          這樣返回的頁面截圖就不會帶有圖片,載入速度也會快很多

    • plugins_enabled

      • plugins_enabled 屬性可以控制瀏覽器外掛(如 Flash 外掛)是否開啟。預設情況下,此屬性是 false,表示不開啟。可以使用如下程式碼控制其開啟和關閉:

        splash。plugins_enabled = true/false
    • scroll_position  

      • 設定 scroll_position 屬性,可以控制頁面上下或左右滾動。是一個比較常用的屬性,示例如下:

        function main(splash, args)
          assert(splash:go('https://www.taobao.com'))
          splash.scroll_position = {y=400}
          return {png=splash:png()}
        end

        這樣我們就可以控制頁面向下滾動 400 畫素值。

      • 如果要讓頁面左右滾動,可以傳入 x 引數,程式碼:

          splash.scroll_position = {x=100,y=200}
  4. Splash 物件的方法

    • 除了前面介紹的屬性外,Splash 物件還有如下方法:

    • go()

      • go() 方法用來請求某個連結,而且它可以模擬 GET 和 POST 請求,同時支援傳入請求頭、表單等資料,用法:

        ok, reason = spalsh:go{url, baseurl=nil,headers=nil,http_method="GET",body=nil,formdata=nil}

        引數說明:

        • url:請求的URL\

        • baseurl:可選引數,預設為空,表示自願載入相對路徑。

        • headers:可選引數,預設為空,表示請求頭

        • http_method:可選引數,預設為空,發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 的表單資料,如果成功,則返回頁面的原始碼。執行結果:

        <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": "180.175.116.149", 
          "url": "http://httpbin.org/post"
        }
        </pre></body></html>
        View Code

        成功實現POST請求併發送了表達資料。

    • wait()

      • 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("https://www.taobao.com")
          splash:wait(2)
          return {html=splash:html()}
        end

        該功能實訪問淘寶並等待2秒,隨後返回頁面原始碼的功能。

    • jsfunc()

      • 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
        輸出:
        Splash Response: "There are 22 DIVs"

        首先,聲明瞭一個 JavaScript 定義的方法,然後在頁面載入成功後呼叫此方法計算出頁面中 div 節點的個數。

      • 關於 JavaScript 到 Lua 指令碼的更多轉換細節,參考官方文件: https://splash.readthedocs.io/en/ stable/scripting-ref.html#splash-jsfunc。

    • evaljs()

      • evaljs() 方法可以執行 JavaScript 程式碼並返回最後一條 JavaScript 語句的返回結果,使用方法: 

        result = splash:evaljs(js)

        如,可以用下面的程式碼來獲取頁面標題:

        local title = splash:evaljs("document.title")
    • runjs()

      • 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
        輸出:
        bar

        用 runjs()先宣告一個 JavaScript 定義的方法,通過 evaljs()來呼叫得到的結果。

    • autoload()

      • autoload() 方法可以設定每個頁面訪問時自動載入的物件,使用方法:

        ok, reason = splash:autoload{source_or_url, source=nil, url=nil}

        引數說明:    

        • source_or_url:JavaScript程式碼或者JavaScript庫連結

        • source:JavaScript程式碼

        • url:JavaScript庫連結

      • autoload() 方法只負責載入 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 ()方法來執行此 JavaScript 方法。輸出百度頁面文字。

      • 另外,也可以使用 autoload()方法載入某些方法庫,如 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"

    • call_later()

      • 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.05)
          splash:go("https://www.taobao.com")
          splash:wait(3.0)
          return snapshots
        end

        這裡設定了一個定時任務,0.05 秒的時候獲取網頁截圖,然後等待 1 秒,1.05 秒時再次獲取網頁截圖,訪問的頁面是淘寶,最後將截圖結果返回。 結果將是:第一次截圖時網頁還沒有加載出來,第二次網頁載入成功。

    • http_get()

      • 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.statsus
          }
        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": "180.175.116.149", 
          "url": "http://httpbin.org/get"
        }
        View Code
    • 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

        輸出:

        Splash Response:Object
        html:String(length 535)
        {
          "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": "180.175.116.149", 
          "url": "http://httpbin.org/post"
        }
        View Code

        這裡成功模擬提交POST請求併發送表單資料。

    • set_content()

      • set_content() 方法用來設定頁面內容,示例:

        function main(splash)
          assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
          return splash:png()
        end
    • html()

      • html() 方法用來獲取網頁的原始碼。示例:
        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": "180.175.116.149", 
          "url": "https://httpbin.org/get"
        }
        </pre></body></html>
        View Code
    • png()

      • png() 方法用來獲取 PNG 格式的網頁截圖,示例:

        function main(splash,args)
          splash:go("https://www.taobao.com")
          return splash:png()
        end
    • jpeg()  

      • jpeg() 方法用來獲取 JPEG 格式的網頁截圖,示例:

        function main(splash,args)
          splash:go("https://www.taobao.com")
          return splash:jpeg()
        end
    • har()

      • har() 方法用來獲取頁面載入過程描述,示例:

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

        輸出結果:

    • url()

      • url()方法可以獲取當前正在訪問的 URL,示例:

        function main(splash,args)
          splash:go("https://www.baidu.com")
          return splash:url()
        end
        輸出:
        "https://www.baidu.com/"
    • get_cookies()

      • get_cookies() 方法可以獲取當前頁面的 Cookies,示例:
        function main(splash, args)
          splash:go("https://www.baidu.com")
          return splash:get_cookies()
        end
        輸出:
        Splash Response: Array[7]
        0: Object
        domain: ".baidu.com"
        expires: "2087-01-21T09:30:08Z"
        httpOnly: false
        name: "BAIDUID"
        path: "/"
        secure: false
        value: "70BACA9750D3C6FBE7DA3EDC2B9E0FCE:FG=1"
        1: Object
        domain: ".baidu.com"
        expires: "2087-01-21T09:30:08Z"
        httpOnly: false
        name: "BIDUPSID"
        path: "/"
        secure: false
        value: "70BACA9750D3C6FBE7DA3EDC2B9E0FCE"
        2: Object
        domain: ".baidu.com"
        expires: "2087-01-21T09:30:08Z"
        httpOnly: false
        name: "PSTM"
        path: "/"
        secure: false
        value: "1546496161"
        3: Object
        domain: ".baidu.com"
        httpOnly: false
        name: "delPer"
        path: "/"
        secure: false
        value: "0"
        4: Object
        domain: "www.baidu.com"
        httpOnly: false
        name: "BD_HOME"
        path: "/"
        secure: false
        value: "0"
        5: Object
        domain: ".baidu.com"
        httpOnly: false
        name: "H_PS_PSSID"
        path: "/"
        secure: false
        value: "1456_21095_28206_28131_26350_28139_27543"
        6: Object
        domain: "www.baidu.com"
        expires: "2019-01-13T06:16:01Z"
        httpOnly: false
        name: "BD_UPN"
        path: "/"
        secure: false
        value: "143354"
        View Code
    • add_cookie()
      • 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()
      • clear_cookies() 方法可以清除所有的 Cookies,示例:
        function main(splash)
          splash:go("https://www.baidu.com")
          splash:clear_cookies()
          return splash:get_cookies()
        end

        清除了所有的 Cookies,然後呼叫 get_cookies()將結果返回。 輸出:Splash ResponseArray[0], Cookies 被全部清空。

    • get_viewport_size()  

      • get_viewport_size() 方法可以獲取當前瀏覽器頁面的大小,即寬高,示例:
        function main(splash)
          splash:go("https://www.baidu.com/")
          return splash:get_viewport_size()
        end

        輸出:

        Splash Response: Array[2]
        0: 1024
        1: 768
    • set_viewport_size()

      • set_viewport_size() 方法可以設定當前瀏覽器頁面的大小,即寬高,用法:

        splash:set_viewport_size(width,height)

        例:訪問一個寬度自適應的頁面:

        function main(splash)
          splash:set_viewport_size(400,700)
          assert(splash:go("https://cuiqingcai.com"))
          return splash:png()
        end
    • set_viewport_full() 

      • set_viewport_full() 方法可以設定瀏覽器全屏顯示,示例:

        function main(splash)
          splash:set_viewport_full()
          assert(splash:go("https://www.cuiqingcai.com"))
          return splash:png()
        end
    • set_user_agent()

      • 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. 結果:User-agent 被設定成功。

    • set_custom_headers()

      • 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 屬性,輸出結果中User-Agent 和Site被更改。

    • select()

      • 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()

      • 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()

      • 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() 方法提交查詢,然後頁面等待3秒,返回截圖。成功獲取了查詢後的頁面內容,模擬了百度搜索操作。

    • Splash 物件的所有 API 操作:詳細說明參見官方文件 https://splash.readthedocs.io/en/stable/scripting-ref.html 。

    • 針對頁面元素 API 操作:https://splash.readthedocs.io/en/stable/scripting-element-object.html。   

  5. Splash API 呼叫
    • Splash Lua 指令碼是在 Splash 頁面中測試執行的,Splash 提供了一些 HTTPAPI 介面,只需請求這些介面並傳遞相應的引數即可。就可以利用 Splash 渲染頁面 和 Python 程式結合使用並抓取 JavaScript 渲染的頁面。
    • render.html
      • 介面用於獲取 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 秒多鍾才能獲取淘寶頁面的原始碼。

      • 介面還支援代理設定、圖片載入設定、Header 設定、請求方法設定,具體的用法見官方文件:https://splash.readthedocs.io/en/stable/api.html#render-html。
    • render.png
      • 介面可以獲取網頁截圖,其引數比 render.html 多幾個,如通過 width 和 height 來控制寬高,返回的是 PNG 格式的圖片二進位制資料。示例:
        curl http://localhost:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700

        這裡傳入 width 和 height 來設定頁面大小為 1000 畫素×700 畫素。

      • 如果用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)

        這樣就成功獲取了京東首頁渲染完成後的頁面截圖,詳細的引數設定參考官網文件: https://splash.readthedocs.io/en/stable/api.html#render-png。

    • render.jpeg
      • render.jpej 介面和 render.png 類似,不過它返回的是 JPEG 格式的圖片二進位制資料。
      • 另外, 介面比 render.png 多了引數 quality,用來設定圖片質量。
    • render.har()
      • 介面用於獲取頁面載入的 HAR 資料,示例:
        curl http://localhost:8050/render.har?url=https://www.jd.com&wait=5

        運回結果非常多,是一個 JSON 格式的資料,其中包含頁面載入過程中的 HAR 資料

    • render.json
      • 介面包含前面介面的所有功能,返回結果是 JSON 格式,示例:
        curl http://localhost:8050/render.json?url=https://httpbin.org
      • 可以通過傳人不同引數控制其返回結果。如:傳人 html=1,返回結果即會增加原始碼資料;傳入 png=1 ,返回結果即會增加頁面 PNG 截圖資料;傳入 har=1 ,則會獲得頁面 HAR 資料。例:

        curl http://localhost:8050/render.json?url=https://httpbin.org&html=1

        這樣返回的 JSON 結果會包含網頁原始碼和 HAR 資料。

      • 更多引數設定參考官方文件: https://splash.readthedocs.io/en/stable/api.html#render-json。

    • excute

      • excute() 介面是最為強大的介面。前面說了很多 Splash Lua 指令碼的操作,用此介面便可實現與 Lua 本的對接。  前面的 render.html 和 render.png 等介面對於一般的 JavaScript 渲染頁面是足夠了,但是如果要實現一些互動操作的話,它們還是無能為力,這裡就需要使用 execute 介面。  先實現一個最簡單的指令碼,直接返回資料:

        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

        通過 lua_source 引數傳遞了轉碼後的 Lua 指令碼,通過 execute 介面獲取了最終指令碼的執行結果。 用 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)

        輸出:

        {"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\": \"180.165.241.94\", \n  \"url\": \"http://httpbin.org/get\"\n}\n", "status": 200, "url": "http://httpbin.org/get"}
        View Code

        返回結果是 JSON 形式,成功獲取了請求的 URL, 狀態碼和網頁原始碼。

      • 之前所說 Lua 指令碼均可以用此方式與 Python 進行對接,所有網頁的動態渲染,模擬點選、表單提交、頁面滑動、延時等待後的一些結果均可以自由控制,獲取頁面原始碼和截圖也都ok。