Python3网络爬虫实战---3八、动态渲染页面抓取:Splash的使用

上一篇文章: Python3网络爬虫实战---3七、动态渲染页面抓取:Selenium
下一篇文章:

Splash 是一个 JavaScript 渲染服务,是一个带有 HTTP API 的轻量级浏览器,同时它对接了 Python 中的 Twisted和 QT 库,利用它咱们一样能够实现动态渲染页面的抓取。html

1. 功能介绍

利用 Splash 咱们能够实现以下功能:node

  • 异步方式处理多个网页渲染过程
  • 获取渲染后的页面的源代码或截图
  • 经过关闭图片渲染或者使用 Adblock 规则来加快页面渲染速度
  • 可执行特定的 JavaScript 脚本
  • 可经过 Lua 脚原本控制页面渲染过程获取渲染的详细过程并经过 HAR(HTTP Archive)格式呈现

接下来咱们来了解一下它的具体用法。jquery

2. 准备工做

在本节开始以前请确保已经正确安装好了 Splash 并能够正常运行服务,如没有安装能够参考第一章的安装说明。nginx

3. 实例引入

首先咱们能够经过 Splash 提供的 Web 页面来测试 Splash的渲染过程,例如咱们在本机 8050 端口运行了 Splash 服务,打开:http://localhost:8050/ 便可看到其 Web 页面,如图 7-6 所示:web

clipboard.png

图 7-6 Web 页面
在右侧呈现的是一个渲染示例,咱们能够看到在上方有一个输入框,默认是:http://google.com,咱们在这里换成百度测试一下,将内容更改成:https://www.baidu.com,而后点击按钮,开始渲染,结果如图 7-7 所示:编程

clipboard.png

图 7-7 运行结果
能够看到网页的返回结果呈现了渲染截图、HAR 加载统计数据、网页的源代码。
经过 HAR 的结果咱们能够看到 Splash 执行了整个网页的渲染过程,包括 CSS、JavaScript 的加载等过程,呈现的页面和咱们在浏览器获得的结果彻底一致。
那么这个过程是由什么来控制的呢?咱们从新返回首页能够看到其实是有一段脚本,内容以下:json

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 语言写的脚本,Lua也是一门编程语言,简洁易用。
即便咱们不懂这个语言的语法,但经过脚本表面意思咱们也能够大体了解到它是首先调用 go() 方法去加载页面,而后调用 wait() 方法等待了必定的时间,最后返回了页面的源码、截图和 HAR 信息。
因此到这里咱们能够大致了解到 Splash 是经过 Lua 脚原本控制了页面的加载过程,加载过程彻底模拟浏览器,最后可返回各类格式的结果,如网页源码、截图等。
因此接下来咱们要学会用 Splash 的话,一是须要了解其中 Lua 脚本的写法,二是须要了解相关 API 的用法,那么接下来咱们就来了解一下这两部份内容。segmentfault

4. Splash Lua脚本

Splash能够经过Lua脚本执行一系列渲染操做,这样咱们就能够用Splash来模拟相似Chrome、PhantomJS的操做了。
首先咱们先对 Splash Lua 脚本的用法有一个基本的认识,先了解一下它的入口和执行方式。api

入口及返回值

首先咱们来看一个基本实例:浏览器

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 所示:

clipboard.png

图 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 所示:

clipboard.png

图 7-9 运行结果
在脚本内调用了 wait() 方法,这相似于 Python 中的 sleep(),参数为等待的秒数,当 Splash 执行到此方法时,它会转而去处理其余的任务,而后在指定的时间事后再回来继续处理。
在这里值得注意的是 Lua 脚本中的字符串拼接,和 Python不一样,这里的字符串拼接使用的是 .. 操做符,而不是 +,若有必要能够简单了解一下Lua脚本的语法,连接:http://www.runoob.com/lua/lua...
另外这里咱们作了加载时的异常检测,go() 方法会返回加载页面的结果状态,若是页面出现 4XX 或 5XX 状态码,ok 变量就会为空,就不会返回加载后的图片。

5. Splash对象属性

咱们注意到在前面的例子中 main() 方法的第一个参数是 splash,这个对象很是重要,相似于在 Selenium 中的WebDriver 对象:

from selenium import webdriver
browser = webdriver.Chrome()

如上所示,如今的 splash 对象就如同此处 Selenium 中的 browser 对象,咱们能够调用它的一些属性和方法来控制加载过程,接下来咱们首先看下它的属性。

args

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

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,
    "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"
}

不过通常来讲咱们不用设置此属性开关,默认开启便可。

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 秒以内没有获得响应就会抛出异常,错误以下:

{
    "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"
}

此属性适合在网页加载速度较慢的状况下设置,若是超过了某个时间无响应则直接抛出异常并忽略便可。

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

此属性能够控制浏览器插件是否开启,如 Flash 插件。默认此属性是 False 不开启,可使用以下代码控制其开启和关闭:

splash.plugins_enabled = true/false

scroll_position

此属性能够控制页面的滚动偏移,经过设置此属性咱们能够控制页面上下或左右滚动,仍是比较经常使用的一个属性,咱们用一个实例感觉一下:

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

这样咱们就能够控制页面向下滚动 400 像素值,结果如图 7-10 所示:

clipboard.png

图 7-10 运行结果
若是要控制左右滚动能够传入 x 参数,代码以下:

splash.scroll_position = {x=100, y=200}

6. Splash对象方法

go()

go() 方法就是用来请求某个连接的方法,并且它能够模拟 GET 和 POST 请求,同时支持传入 Headers、Form Data 等数据,用法以下:

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

参数说明以下:

  • url,即请求的URL。
  • baseurl,可选参数,默认为空,资源加载相对路径。
  • headers,可选参数,默认为空,请求的 Headers。
  • http_method,可选参数,默认为 GET,同时支持 POST。
  • body,可选参数,默认为空,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": "60.207.237.85", 
  "url": "http://httpbin.org/post"
}
</pre></body></html>

经过结果能够看到咱们成功实现了 POST 请求并发送了表单数据。

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

此方法能够直接调用 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...

evaljs()

此方法能够执行 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

在这里咱们用 runjs() 先声明了一个 JavaScript 定义的方法,而后经过 evaljs() 来调用获得结果。
运行结果以下:

bar

autoload()

此方法能够设置在每一个页面访问时自动加载的对象,使用方法以下:

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

参数说明以下:

  • source_or_url,JavaScript 代码或者 JavaScript 库连接。
  • source,JavaScript 代码。
  • url,JavaScript 库连接

可是此方法只负责加载 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

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

在这里咱们设置了一个定时任务,0.2 秒的时候获取网页截图,而后等待 1 秒,1.2 秒时再次获取网页截图,访问的页面是淘宝,最后将截图结果返回。
运行结果如图 7-11 所示:

clipboard.png

图 7-11 运行结果
咱们能够发现第一次截图网页尚未加载出来,截图为空,第二次网页便加载成功了。

http_get()

此方法能够模拟发送 HTTP 的 GET 请求,使用方法以下:

response = splash:http_get{url, headers=nil, follow_redirects=true}

参数说明以下:

  • url,请求URL。
  • headers,可选参数,默认为空,请求的 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

运行结果:

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

和 http_get() 方法相似,此方法是模拟发送一个 POST 请求,不过多了一个参数 body,使用方法以下:

response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}

参数说明以下:

  • url,请求URL。
  • headers,可选参数,默认为空,请求的 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 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 请求并发送了表单数据。

set_content()

此方法能够用来设置页面的内容,示例以下:

function main(splash)
    assert(splash:set_content("&lt;html&gt;&lt;body&gt;&lt;h1&gt;hello&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;"))
    return splash:png()
end

运行结果如图 7-12 所示:

clipboard.png

图 7-12 运行结果

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": "60.207.237.85", 
  "url": "https://httpbin.org/get"
}
</pre></body></html>

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

运行结果如图 7-13 所示:

clipboard.png

图 7-13 运行结果
在这里显示了页面加载过程当中的每一个请求记录详情。

url()

此方法能够获取当前正在访问的 URL,示例以下:

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

运行结果以下:

https://www.baidu.com/

get_cookies()

此方法能够获取当前页面的 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"

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() 并将结果返回。
运行结果:

Splash Response: Array[0]

能够看到Cookies被所有清空,没有任何结果。

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

此方法能够设置当前浏览器页面的大小,即宽高,用法以下:

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

运行结果如图 7-14 所示:

clipboard.png

图 7-14 运行结果

set_viewport_full()

此方法能够设置浏览器全屏显示,示例以下:

function main(splash)
    splash:set_viewport_full()
    assert(splash:go("http://cuiqingcai.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,运行结果以下:

<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 被成功设置。

set_custom_headers()

此方法能够设置请求的 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()

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 所示:

clipboard.png

图 7-15 运行结果
能够看到咱们成功填写了输入框。

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 选择器选中了节点的正文内容,随后遍历了全部节点,而后将其中的文本获取了下来。
运行结果:

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 个节点的正文内容获取了下来。

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() 方法提交查询,而后页面等待三秒,返回截图,结果如图 7-16 所示:

clipboard.png

图 7-16 运行结果
能够看到在这里咱们成功获取了查询后的页面内容,模拟了百度搜索操做。
以上咱们介绍了 Splash 的经常使用 API 操做,还有一些 API 在这再也不一一介绍,更加详细和权威的说明能够参见官方文档:https://splash.readthedocs.io...,此页面介绍了 splash 对象的全部 API 操做,另外还有针对于页面元素的 API 操做,连接为:https://splash.readthedocs.io...

7. Splash API调用

在上文中咱们说明了 Splash Lua 脚本的用法,但这些脚本是在 Splash 页面里面测试运行的,咱们如何才能利用Splash 来渲染页面呢?怎样才能和 Python 程序结合使用并抓取 JavaScript 渲染的页面呢?
其实 Splash 给咱们提供了一些 HTTP API 接口,咱们只须要请求这些接口并传递相应的参数便可获取页面渲染后的结果,下面咱们对这些接口进行介绍:

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&amp;wait=5'
response = requests.get(url)
print(response.text)

若是增长了此等待时间后,获得响应的时间就会相应变长,如在这里咱们会等待大约 5 秒多钟便可获取 JavaScript 渲染后的淘宝页面源代码。
另外此接口还支持代理设置、图片加载设置、Headers设置、请求方法设置,具体的用法能够参见官方文档:https://splash.readthedocs.io...

render.png

此接口能够获取网页截图,参数相比 render.html 又多了几个,如 width、height 来控制宽高,返回的是 PNG 格式的图片二进制数据。
示例以下:

curl http://localhost:8050/render.png?url=https://www.taobao.com&amp;wait=5&amp;width=1000&amp;height=700

在这里咱们还传入了 width 和 height 来放缩页面大小为 1000x700 像素。
若是用 Python 实现,咱们能够将返回的二进制数据保存为PNG 格式的图片,实现以下:

import requests

url = 'http://localhost:8050/render.png?url=https://www.jd.com&amp;wait=5&amp;width=1000&amp;height=700'
response = requests.get(url)
with open('taobao.png', 'wb') as f:
    f.write(response.content)

获得的图片如图 7-17 所示:

clipboard.png

图 7-17 运行结果
这样咱们就成功获取了京东首页渲染完成后的页面截图,详细的参数设置能够参考官网文档:https://splash.readthedocs.io...

render.jpeg

此接口和 render.png 相似,不过它返回的是 JPEG 格式的图片二进制数据。
另外此接口相比 render.png 还多了一个参数 quality,能够用来设置图片质量。

render.har

此接口用于获取页面加载的 HAR 数据,示例以下:

curl http://localhost:8050/render.har?url=https://www.jd.com&amp;wait=5

返回结果很是多,是一个 Json 格式的数据,里面包含了页面加载过程当中的 HAR 数据。
结果如图 7-18 所示:

clipboard.png

图 7-18 运行结果

render.json

此接口包含了前面接口的全部功能,返回结果是 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&amp;html=1&amp;har=1

这样返回的 Json 结果便会包含网页源代码和 HAR 数据。
还有更多参数设置能够参考官方文档:https://splash.readthedocs.io...

execute

此接口才是最为强大的接口,咱们在前面说了不少 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负载均衡配置

若是咱们用 Splash 来作 JavaScript 动态渲染的页面的抓取的话,若是爬取的量很是大,任务很是多,若是咱们用一个 Splash 服务来处理的话未免压力太大了,因此咱们能够考虑搭建一个负载均衡器来把压力分散到各个服务器上,这样至关于多台机器多个服务共同参与任务的处理,能够减少单个 Splash 服务的压力。

1. 配置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 服务。

2. 配置负载均衡

接下来咱们能够选用任意一台带有公网 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 端口便可实现负载均衡了。

3. 配置认证

如今 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

这样访问认证就成功配置好了。

4. 测试

最后咱们能够用代码来测试一下负载均衡的配置,看看究竟是不是每次请求会切换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

这就说明负载均衡已经成功实现了。

9. 结语

到如今为止,咱们就能够用 Python 和 Splash 实现JavaScript 渲染的页面的抓取了,除了 Selenium,本节所说的 Splash 一样能够作到很是强大的渲染功能,同时它也不须要浏览器便可渲染,使用很是方便。

本节咱们还成功实现了负载均衡的配置,配置了负载均衡以后能够多个 Splash 服务共同合做,减轻单个服务的负载,仍是比较有用的。

相关文章
相关标签/搜索