Python爬虫之Splash详解

爬虫专栏:http://t.csdnimg.cn/WfCSx

Splash 的使用

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

1. 功能介绍

利用 Splash 我们可以实现如下功能:

  • 异步方式处理多个网页渲染过程

  • 获取渲染后的页面的源代码或截图

  • 通过关闭图片渲染或者使用 Adblock 规则来加快页面渲染速度

  • 可执行特定的 JavaScript 脚本

  • 可通过 Lua 脚本来控制页面渲染过程

  • 获取渲染的详细过程并通过 HAR(HTTP Archive)格式呈现

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

2. 准备工作

在开始之前,请确保已经正确安装好了 Splash 并可以正常运行服务。如果没有安装,可以参考第 1 章。

3. 实例引入

首先,通过 Splash 提供的 Web 页面来测试其渲染过程。例如,我们在本机 8050 端口上运行了 Splash 服务,打开 http://localhost:8050/ 即可看到其 Web 页面。

在右侧呈现的是一个渲染示例,我们可以看到在上方有一个输入框,默认是 http://google.com,我们在这里换成百度测试一下,将内容更改为:百度一下,你就知道,然后点击按钮,开始渲染,结果:

可以看到,网页的返回结果呈现了渲染截图、HAR 加载统计数据、网页的源代码。

通过 HAR 的结果可以看到,Splash 执行了整个网页的渲染过程,包括 CSS、JavaScript 的加载等过程,呈现的页面和我们在浏览器中得到的结果完全一致。

那么,这个过程由什么来控制呢?重新返回首页,可以看到实际上是有一段脚本,内容如下:

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 语言写的脚本。即使不懂这个语言的语法,但从脚本的表面意思,我们也可以大致了解到它首先调用 go 方法去加载页面,然后调用 wait 方法等待了一定时间,最后返回了页面的源码、截图和 HAR 信息。

到这里,我们大体了解了 Splash 是通过 Lua 脚本来控制了页面的加载过程的,加载过程完全模拟浏览器,最后可返回各种格式的结果,如网页源码和截图等。

接下来,我们就来了解 Lua 脚本的写法以及相关 API 的用法。

4. Splash Lua 脚本

Splash 可以通过 Lua 脚本执行一系列渲染操作,这样我们就可以用 Splash 来模拟类似 Chrome、PhantomJS 的操作了。

首先,我们来了解一下 Splash Lua 脚本的入口和执行方式。

入口及返回值

首先,来看一个基本实例:

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/ 的代码编辑区域,然后点击 Render me! 按钮来测试一下。

我们看到它返回了网页的标题,这里我们通过 evaljs 方法传入 JavaScript 脚本,而 document.title 的执行结果就是返回网页标题,执行完毕后将其赋值给一个 title 变量,随后将其返回。

注意,我们在这里定义的方法名称叫作 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

运行后的返回结果是 3 个站点的截图:

在脚本内调用的 wait 方法类似于 Python 中的 sleep 方法,其参数为等待的秒数。当 Splash 执行到此方法时,它会转而去处理其他任务,然后在指定的时间过后再回来继续处理。

这里值得注意的是,Lua 脚本中的字符串拼接和 Python 不同,它使用的是.. 操作符,而不是 +。如果有必要,可以简单了解一下 Lua 脚本的语法,详见 Lua 基本语法 | 菜鸟教程。

另外,这里做了加载时的异常检测。go 方法会返回加载页面的结果状态,如果页面出现 4xx 或 5xx 状态码,ok 变量就为空,就不会返回加载后的图片。

5. Splash 对象属性

我们注意到,前面例子中 main 方法的第一个参数是 splash,这个对象非常重要,它类似于 Selenium 中的 WebDriver 对象,我们可以调用它的一些属性和方法来控制加载过程。接下来,先看下它的属性。

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 像素值,结果如图。

如果要让页面左右滚动,可以传入 x 参数,代码如下:

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

6. Splash 对象方法

除了前面介绍的属性外,Splash 对象还有如下方法。

go

该方法用来请求某个链接,而且它可以模拟 GET 和 POST 请求,同时支持传入请求头、表单等数据,其用法如下:

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

首先,我们声明了一个 JavaScript 定义的方法,然后在页面加载成功后调用了此方法计算出了页面中 div 节点的个数。

关于 JavaScript 到 Lua 脚本的更多转换细节,可以参考官方文档:Splash Scripts Reference — Splash 3.5 documentation。

evaljs

此方法可以执行 JavaScript 代码并返回最后一条 JavaScript 语句的返回结果,使用方法如下:

result = splash:evaljs(js)

比如,可以用下面的代码来获取页面标题:

local title = splash:evaljs("document.title")
runjs

此方法可以执行 JavaScript 代码,它与 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 方法来执行此 JavaScript 方法。

运行结果如下:

百度一下,你就知道

另外,我们也可以使用 autoload 方法加载某些方法库,如 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 秒时再次获取网页截图,访问的页面是淘宝,最后将截图结果返回。运行结果如图。

可以发现,第一次截图时网页还没有加载出来,截图为空,第二次网页便加载成功了。

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

运行结果:

<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

运行结果如图所示:

在这里显示了页面加载过程中的每个请求记录详情。

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

此方法可以为当前页面添加 Cookies,用法如下:

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

运行结果如图所示:

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

该方法可以选中符合条件的第一个节点,如果有多个节点符合条件,则只会返回一个,其参数是 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 选择器选中了节点的正文内容,随后遍历了所有节点,将其中的文本获取下来。

运行结果如下:

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

可以看到在这里我们成功获取了查询后的页面内容,模拟了百度搜索操作。

以上我们介绍了 Splash 的常用 API 操作,还有一些 API 在这不再一一介绍,更加详细和权威的说明可以参见官方文档:Splash Scripts Reference — Splash 3.5 documentation,此页面介绍了 splash 对象的所有 API 操作,另外还有针对于页面元素的 API 操作,链接为:Element Object — Splash 3.5 documentation。

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 设置、请求方法设置,具体的用法可以参见官方文档:Splash HTTP API — Splash 3.5 documentation。

render.png

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

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

在这里我们还传入了 width 和 height 来放缩页面大小为 1000x700 像素。

如果用 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('taobao.png', 'wb') as f:
    f.write(response.content)

得到的图片如图所示:

这样我们就成功获取了京东首页渲染完成后的页面截图,详细的参数设置可以参考官网文档 Splash HTTP API — Splash 3.5 documentation。

render.jpeg

此接口和 render.png 类似,不过它返回的是 JPEG 格式的图片二进制数据。

另外此接口相比 render.png 还多了一个参数 quality,可以用来设置图片质量。

render.har

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

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

返回结果非常多,是一个 Json 格式的数据,里面包含了页面加载过程中的 HAR 数据。

结果如图所示:

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&html=1&har=1

这样返回的 Json 结果便会包含网页源代码和 HAR 数据。

还有更多参数设置可以参考官方文档:Splash HTTP API — Splash 3.5 documentation。

execute

此接口才是最为强大的接口。前面说了很多 Splash Lua 脚本的操作,用此接口便可实现与 Lua 脚本的对接。

前面的 render.html 和 render.png 等接口对于一般的 JavaScript 渲染页面是足够了,但是如果要实现一些交互操作的话,它们还是无能为力,这里就需要使用 execute 接口了。

我们先实现一个最简单的脚本,直接返回数据:

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 进行对接,所有网页的动态渲染、模拟点击、表单提交、页面滑动、延时等待后的一些结果均可以自由控制,获取页面源码和截图也都不在话下。

到现在为止,我们可以用 Python 和 Splash 实现 JavaScript 渲染的页面的抓取了。除了 Selenium,本节所说的 Splash 同样可以做到非常强大的渲染功能,同时它也不需要浏览器即可渲染,使用非常方便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/390753.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【51单片机】AD模数转换DA数模转换(江科大)

1.AD/DA介绍 AD(Analog to Digital):模拟-数字转换,将模拟信号转换为计算机可操作的数字信号 DA(Digital to Analog):数字-模拟转换,将计算机输出的数字信号转换为模拟信号 AD/DA转换打开了计算机与模拟信号的大门,极大的提高了计算机系统的应用范围,也为模拟信号数字化处理…

常用的正则表达式,收藏必备!!!

正则表达式是一种强大的文本模式匹配工具&#xff0c;用于在字符串中查找、替换和验证特定模式的文本。下面是一些常用的正则表达式示例&#xff1a; 匹配Email地址&#xff1a; ^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$匹配URL&#xff1a; ^(https?|ftp)://[^\s/$.?#…

【python】python入门(变量名)

Hi~ o(*&#xffe3;▽&#xffe3;*)ブ今天一起来看看python入门之变量名吧~~ 变量名的规定&#xff1a; 举个例子&#xff1a; “违法”的变量名们 my love/my &#xff01;love错误&#xff1a;中间不能是空格或者其他符号1my_love错误&#xff1a;不能数字开头"my_l…

BUGKU-WEB bp

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 提示说&#xff1a;弱密码top1000&#xff1f;z???(爆破?)先看看源码有没有提示 相关工具 Burp Suit 爆破top1000字典&#xff0c;点击下载 解题步骤 随便测试账号密码admin、admin 得到提…

java8-重构、测试、调试

8.1.1 改善代码的可读性 改善代码的可读性到底意味着什么?我们很难定义什么是好的可读性&#xff0c;因为这可能非常主观。通常的理解是&#xff0c;“别人理解这段代码的难易程度”。改善可读性意味着你要确保你的代码能非常容易地被包括自己在内的所有人理解和维护。为了确保…

巨抽象的前端vue3

根据实践证明&#xff0c;越是简单的问题&#xff0c;越容易造成大bug 一个自定义组件的路径就废了我老半天了 各种查询&#xff0c;各种百度&#xff0c;各种问&#xff0c;结果规规矩矩去导入组件路径&#xff0c;成了&#xff01; 错误代码&#xff1a; <script setu…

第七篇【传奇开心果系列】Python微项目技术点案例示例:数据可视化界面图形化经典案例

传奇开心果微博系列 系列微博目录Python微项目技术点案例示例系列 微博目录一、微项目开发背景和项目目标&#xff1a;二、雏形示例代码三、扩展思路介绍四、数据输入示例代码五、数据分析示例代码六、排名统计示例代码七、数据导入导出示例代码八、主题定制示例代码九、数据过…

linux系统下vscode portable版本的c++/Cmake环境搭建002:使用 VSIX 安装VSCODE插件(暂记)

使用 VSIX 安装VSCODE插件 在 Visual Studio Code (VSCode) 中&#xff0c;你可以通过以下步骤离线安装插件&#xff1a; 获取插件的 VSIX 文件&#xff1a; 在一个联网环境中&#xff0c;访问 Visual Studio Code Marketplace&#xff0c;搜索并找到你想要的插件。 比如&am…

谷歌内部开发AI大语言模型“鹅”;OpenAI CEO 寻求大规模AI芯片全球生产投资

&#x1f989; AI新闻 &#x1f680; 谷歌内部开发AI大语言模型“鹅” 摘要&#xff1a;谷歌正在积极将AI技术融入其产品中&#xff0c;并为提升员工效率而开发了一个名为“鹅”的AI大语言模型。这一模型仅供公司内部团队使用&#xff0c;旨在辅助新产品的开发。据悉&#xf…

Leetcode1423.可获得的最大点数

文章目录 题目原题链接思路&#xff08;逆向思维&#xff09; 题目 原题链接 Leetcode1423.可获得的最大点数 思路&#xff08;逆向思维&#xff09; 由题目可知&#xff0c;从两侧选k张&#xff0c;总数为n张&#xff0c;即从中间选n - k张 nums总和固定&#xff0c;要选k张最…

【Python--Web应用框架大比较】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Django Django太重了&#xff0c;除了web框架&#xff0c;自带ORM和模板引擎&#xff0c;灵活和自由度不…

BulingBuling - 《超出α的回报》 [ Better than Alpha ]

超出α的回报 在不断变化的世界中获取超额收益的三个步骤 作者&#xff1a;Christopher Schelling Better than Alpha Three Steps to Capturing Excess Returns in a Changing World By Christopher Schelling 内容提要 《超出α的回报》&#xff08;2021&#xff09;鼓励…

Wonderland - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 Wonderland 是小王居住地一家很受欢迎的游乐园。Wonderland目前有 4 种售票方式,分别为一日票(天)、三日票(3 天)&#xff0c;周票( 7 天)和月票( 30 天) 。 每种…

【读书笔记】ICS设备及应用攻击(一)

工控系统通常是由互联设备所构成的大型复杂系统&#xff0c;这些设备包括类似于人机界面&#xff08;HMI&#xff09;、PLC、传感器、执行器以及其他使用协商好的协议进行相互通信的设备。所有交互背后的驱动力都是软件&#xff0c;软件为工控系统中几乎所有部分的运行提供支撑…

Docker笔记-搭建Python环境、安装依赖、打包镜像、导入镜像、编写bash脚本灵活调用

说明 适合无联网的机器及多Python的机器进行部署。 制作docker版Python环境 有网络及有docker的&#xff0c;拉取指定版本的python如&#xff1a; docker pull python:3.7 安装好后进入容器&#xff1a; docker run -it <name> /bin/bash 使用pip安装各种依赖&…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 2月17日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年2月17日 星期六 农历正月初八 1、 中疾控&#xff1a;我国自主研发的猴痘mRNA疫苗即将进入临床试验。 2、 2024年度总票房破100亿元&#xff0c;其中春节档已突破70亿元。 3、 国产大飞机首次国外亮相&#xff0c;C919已抵…

Harris关键点检测以及SAC-IA粗配准

一、Harris关键点检测 C #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/common/io.h> #include <pcl/keypoints/harris_3d.h> #include <pcl/visualization/pcl_visualizer.h> #include …

机器学习面试:请你谈谈逻辑回归的用法?

逻辑回归可用于以下几个方面: (1)用于概率预测。用于可能性预测时&#xff0c;得到的结果有可比性。比如根据模型进而预测在不同的自变量情况下&#xff0c;发生某病或某种情况的概率有多大。 (2)用于分类。实际上跟预测有些类似&#xff0c;也是根据模型&#xff0c;判断某人属…

沐编程APP免费下载|获取免费项目以及技术教程

软件介绍 沐编程专注于分享IT编程相关知识的网站&#xff0c;主要分享毕业设计案例代码&#xff0c;课程设计案例代码&#xff0c;实用功能代码&#xff0c;bug解决方案&#xff0c;编程工具推荐以及编程课程分享等 下载方式 蓝奏云下载&#xff1a;https://wfr.lanzout.com…

嵌入式各种存储器的区别,NAND、DDR、LPDDR、eMMC

存储领域发展至今&#xff0c;已有很多不同种类的存储器产品。下面给大家介绍几款常见的存储器及其应用&#xff1a; 一、NAND NAND Flash存储器是Flash存储器的一种&#xff0c;属于非易失性存储器&#xff0c;其内部采用非线性宏单元模式&#xff0c;为固态大容量内存的实现…