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