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进行对接,
所有网页的动态渲染、模拟点击、表单提交、页面滑动、延时等待后的一些结果均可以自由控制。

 

posted @ 2019-02-12 09:58  贫道从来不吃素  阅读(1410)  评论(0编辑  收藏  举报