深入使用 Splash 服务
上一小节我们基于 Splash 服务以及 Scrapy-Splash 插件完成了今日头条热点数据的抓取,今天我们来详细地介绍 Splash Lua 脚本中支持的相关方法与 Splash 对象属性,并解决上一小节留下的作业题。
1. Splash Lua 脚本方法与相关属性介绍
上一节我们简单实用了 Splash 服务,并基于默认的脚本完成了头条热点新闻数据的爬取。我们现在来深入学习 Splash 中 lua 脚本的使用。
1.1 Splash对象属性
来看默认的 Splash lua 脚本:
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
其中这个 splash 参数非常重要,从该参数中我们可以调用 Splash 对象的一些重要属性和方法来控制加载的过程。我们来看看 Splash 对象最常用的几种属性:
-
args 属性:如
splash.args.url
是获取请求渲染的 url; -
js_enabled 属性:这个属性可以用来允许或者禁止执行 js 代码。例如下面的 lua 脚本:
function main(splash, args) splash.js_enabled = true assert(splash:go(args.url)) assert(splash:wait(0.5)) local title = splash:evaljs("document.title") return { title=title } end
得到的结果为:
Splash Response: Object title: "今日头条"
如果我们禁止执行 js 代码,即设置
splash.js_enabled = false
,则渲染页面时会报错:
resource_timeout 属性:该属性用于设置页面加载时间,单位为秒。如果设置为0或者 nil (相当于 Python 中的 None),表示不检测超时;
images_enabled 属性:用于设置是否加载图片,默认为 true,表示加载页面图片,设置为 false 后,表示禁止加载图片,这有可能会改变页面的布局,使用时要注意。另外,注意 Splash 使用了缓存,如果头一次设置 true 并加载页面,之后再设置为 false 后加载页面仍然会有图片显示,这正是缓存的影响。只需要重启 splash 服务即可显示正常;
plugins_enabled 属性:该属性用于控制浏览器插件是否开启,默认情况下为 false;
scroll_position 属性:该属性用于控制页面上下或者左右滚动。它是一个字典类型,key 为 x 表示页面水平滚动位置,key 为 y 表示页面垂直滚动的位置;我们继续拿头条的热点新闻做实验。之前默认访问时的页面如下:
从抓取的网页上看,一共获取了12篇热点新闻。接下来我们使用 scroll_position 属性来将页面滚动滚动,测试的 lua code 如下:
function main(splash, args)
assert(splash:go(args.url))
assert(splash:wait(2))
splash.scroll_position = {y=1000}
assert(splash:wait(2))
splash.scroll_position = {y=1500}
assert(splash:wait(5))
return {
png=splash:png(),
html=splash:html()
}
end
这里我做了2次页面滚动,渲染的效果如下:
可以看到,页面确实出现了滚动,且我们获取的新闻数据已经变多了,从渲染的页面上看,我们已经抓到了36条数据。
1.2 Splash 的常用方法
前面从默认的 lua 脚本中我们已经看到了 Splash 的一些常用方法,如 go()、wait()、html()、png() 等,我们来一一进行介绍:
splash:go()
:这个方法比较熟悉了,就是跳转去对应的 url 地址,目前它只支持 GET 和 POST 请求。该方法支持指定 HTTP 请求头,表单等数据。对应的方法原型如下:
ok, reason = splash:go{url, baseurl=nil, headers=nil, http_method="GET", body=nil, formdata=nil}
函数参数以及返回结果详情可参考:splash:go,官方已经给出了非常详细的说明,这里就不再进行翻译了。
splash:wait()
: 控制页面等待时间,函数原型如下:
ok, reason = splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}
cancel_on_redirect 参数默认为 false,表示如果等待中发生重定向则停止等待并返回重定向结果;cancel_on_error 默认为 true,表示在等待渲染中出现了错误则停止等待并返回 nil, "<error string>"
,其中 error string 指的是加载错误的原因;
三个和执行 js 相关的方法:
splash:jsfunc()
,该方法用于将 JavaScript 方法转换成 Lua 中可调用的方法。注意所调用的 JavaScript 函数必须在一对双中括号内,类似如下写法:
function main(splash, args)
-- get_div_count 就是表示jsfunc中定义的js方法
local get_div_count = splash:jsfunc([[
function () {
var body = document.body;
var divs = body.getElementsByTagName('div');
return divs.length;
}
]])
splash:go(args.url)
return ("There are %s DIVs in %s"):format(
get_div_count(), args.url)
end
splash:evaljs()
,直接在渲染的页面中执行 js 脚本。来看看如下示例:
local title = splash:evaljs("document.title")
splash:runjs()
,它和 evaljs()
方法功能类似,也是执行 JavaScript 代码。前者它更偏向于执行某些动作或者定义某些方法:
-- 这样子的写法,foo便会加入到全局上下文中,下面注释的这样写法就是错误的
-- assert(splash:runjs("function foo(){return 'bar'}"))
-- 下面这个为正确写法
assert(splash:runjs("foo = function (){return 'bar'}"))
local res = splash:evaljs("foo()") -- this returns 'bar'
splash:autoload()
,该方法用于设置每个页面访问时自动加载的 JavaScript 代码,该方法只负责加载代码并不执行。我们通常会用该方法去加载一些必须的 js 库函数,如 jQuery 等,也会使用该方法加载我们自定义的 js 函数。
assert(splash:autoload("https://code.jquery.com/jquery-2.1.3.min.js"))
splash:call_later()
,该方法通过设置任务的延长时间来实现任务的延迟执行。
splash:http_get()
,该方法发送 http 的 get 请求并返回响应,方法的原型如下:
response = splash:http_get{url, headers=nil, follow_redirects=true}
-
splash:http_post()
,该方法发送 http 的 post 请求并返回响应,方法的原型如下:response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}
splash:set_content()
,该方法用于设置当前页面的内容并等待页面加载;我们来看看官方给的一个简单示例:
function main(splash)
assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
return splash:png()
end
渲染效果如下:
-
splash:html()
:获取渲染后的网页源码; -
splash:png()
:获取 png 格式的页面截图; -
splash:jpg()
:获取 jpg 格式的页面截图; -
splash:url()
:获取当前访问页面的 url;
cookie 相关的方法:
-
splash:get_cookies()
:获取 CookieJar 的内容-脚本中所有 cookies 的列表; -
splash:add_cookie()
:添加一个 cookie; -
splash:init_cookies()
:将当前所有 cookies 替换成传入的 cookies -
splash:clean_cookies()
:清除所有的 cookies; -
splash:delete_cookies()
:删除指定的 cookies; -
splash:set_viewport_full()
:设置浏览器全屏显示; -
splash:on_request()
:在每个 http 请求之前注册要调用的函数。这个方法非常有用,官方给出了6中用途示例,如记录所有的请求、丢弃某个特殊的请求 (比如以 .css 结尾的请求) 等,这也从某方面说明了该方法的重要性;
接下来我们看看 Splash 中一些更高级的用法,包括页面元素定位、填充输入框以及模拟鼠标操作等方法。
2. Splash 中元素定位与操作
Splash 中涉及到元素定位和操作的方法主要有如下几个:
splash:select()
:从当前网页的 DOM 中选择与指定 CSS 选择器匹配的第一个 HTML 元素;splash:select_all()
:从当前网页的 DOM 中选择与指定 CSS 选择器匹配的 HTML 元素列表;splash:send_keys()
:将键盘事件发送到页面上下文;splash:send_text()
:将文本作为输入发送到页面上下文,一个字符一个字符发送;
来看看我们对这些方法的一个简单实例:
function main(splash)
splash:go("https://www.baidu.com")
splash:wait(2)
input = splash:select("#kw")
input:send_text("慕课网 wiki")
splash:wait(2)
return {
png = splash:png()
}
end
来看看针对百度页面的执行效果:
另外一个例子,我们还是前面的头条热点数据,我们加上滚动效果后能提取出更多的热点新闻,那么就在这里使用 splash:select_all()
方法将这些热点新闻的标题提取出来。为此,我们编写如下的 lua 代码:
function main(splash, args)
local treat = require('treat')
assert(splash:go(args.url))
assert(splash:wait(2))
splash.scroll_position = {y=1000}
assert(splash:wait(2))
splash.scroll_position = {y=1500}
assert(splash:wait(5))
news_list = splash:select_all('div.title-box a')
local result = {}
for idx, a in ipairs(news_list) do
result[idx] = a.node.innerHTML
end
return treat.as_array(result)
end
来看看渲染后的结果,如下:
3. 模拟鼠标操作
最后一部分我们来看看和鼠标操作相关的方法,总共有4个方法:
-
splash:mouse_click()
:模拟鼠标的点击动作,该方法的原型为splash:mouse_click(x, y)
;示例1:
local button = splash:select('button') -- 对于选中的button元素执行点击动作 button:mouse_click()
示例2:
-- 通过(x, y)坐标执行鼠标点击动作 function main(splash) assert(splash:go(splash.args.url)) -- 定义js函数 local get_dimensions = splash:jsfunc([[ function () { var rect = document.getElementById('button').getClientRects()[0]; return {"x": rect.left, "y": rect.top} } ]]) splash:set_viewport_full() splash:wait(0.1) -- 执行js方法,获取元素的坐标位置 local dimensions = get_dimensions() -- FIXME: button must be inside a viewport splash:mouse_click(dimensions.x, dimensions.y) -- Wait split second to allow event to propagate. splash:wait(0.1) return splash:html() end
-
splash:mouse_hover()
:模拟鼠标悬停事件,方法原型为splash:mouse_hover(x, y)
; -
splash:mouse_press()
:在网页中触发鼠标按下事件,方法原型为splash:mouse_press(x, y)
; -
splash:mouse_release()
:在网页中触发鼠标释放事件。方法原型为splash:mouse_release(x, y)
;
4. 小结
本小节中我们深入学习了 Splash 服务的使用,并详细介绍了 Splash 的部分属性以及相关方法,部分属性和方法给予详细的实战例子。接下来的两节我们会介绍一种自动化测试工具,和 Splash 服务功能类似,但是更为强大和简单易用,还等什么,快来学习吧!