深入使用 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,则渲染页面时会报错:
    图片描述

禁止js后,执行js语句报错

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 服务获取百度搜索结果

另外一个例子,我们还是前面的头条热点数据,我们加上滚动效果后能提取出更多的热点新闻,那么就在这里使用 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

来看看渲染后的结果,如下:
图片描述

使用 select_all() 方法提取热点新闻标题

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 服务功能类似,但是更为强大和简单易用,还等什么,快来学习吧!