一、安装 Splash
Splash 是一个 JavaScript 渲染服务,是一个含有 HTTP API 的轻量级浏览器,它还对接了 Python 中的 Twisted 库和 QT 库。利用它,同样可以爬取动态渲染的页面。利用 Splash,可以实现如下功能:
- 异步处理多个网页的渲染过程;
- 获取渲染后页面的源代码或截图。
- 通过关闭图片渲染或者使用 Adblock 规则的方式加快页面渲染的速度;
- 执行特定的 JavaScript 脚本;
- 通过 Lua 脚本控制页面的渲染过程;
- 获取页面渲染的详细过程并以 HAR(HTTP Archive) 的格式呈现出来。
Splash 建议的安装方式是通过 docker,安装是通过 docker 安装,在这之前请确保已经正确安装好了 docker,官方文档如下:
https://docs.docker.com/desktop/windows/install/
有了 docker,只需要一键启动 Splash 即可,命令如下:
docker run -p 8050:8050 scrapinghub/splash
安装完成之后如下图所示: 这样就证明 Splash 已经在 8050 端口上运行了。这时我们使用 Google 浏览器打开地址:http://localhost:8050 即可看到 Splash 的主页,如下图所示: Splash 也可以直接安装在远程服务器上,在服务器上以守护态运行 Splash 即可,命令如下:
docker run -d -p 8050:8050 scrapinghub/splash
在这里多了一个 -d 参数,它代表将 docker 容器以守护态运行,这样在中断远程服务器连接后不会终止 Splash 服务的运行。docker 基础学习笔记可以参考此文章:https://blog.csdn.net/xw1680/article/details/113360133
二、Splash 的使用
1、基本用法:
在本机 8050 端口上运行 Splash 服务,然后打开 http://localhost:8050/,即可看到 Splash 的 Web 页面,如下图所示: 我们可以更改上方输入框中的 URL 为 https://blog.csdn.net/xw1680?,换完内容后单击 Render me!按钮,开始渲染,结果如下图所示: 渲染结果中包含渲染截图、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 的用法。
2、入口及返回值:
基本实例:
function main(splash, args)
splash:go("https://blog.csdn.net/xw1680?")
splash:wait(0.5)
local title = splash:evaljs("document.title")
return {title=title}
end
将这段代码粘贴到下图所示的代码编辑区域,然后单击 Render me! 按钮 返回结果如下图所示: 渲染结果包含网页的标题。通过 evaljs 方法传入了 JavaScript 脚本,而 document.title 返回的就是网页的标题,evaljs 方法执行完毕后将标题赋值给 title 变量,随后将其返回。注意:main 方法是固定的,Splash 默认会调用该方法。main 方法的返回值既可以是字典形式,也可以是字符串形式,最后都会转换为 Splash 的 HTTP 响应,例如:
function main(splash)
return {hello="amoxiang!"}
end
function main(splash)
return 'amoxiang'
end
Splash 支持异步处理,但是并没有显示地指明回调方法,其回调的跳转是在内部完成的。示例如下:
function main(splash, args)
local example_urls = {"www.baidu.com","www.jd.com","www.taobao.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 个网站的页面截图,如下图所示: 3、splash 对象的属性:
main() 方法中的第一个参数是 splash,类似于 Selenium 中的 WebDriver 对象,我们可以调用它的一些属性和方法来控制加载过程。
args 属性: 获取页面加载时配置的参数,例如请求的地址(URL)。对于 GET 请求,args 属性还可以用于获取 GET 请求的参数;对于 POST 请求,args 属性还可以用于获取表单提交的数据。此外,Splash 支持将 main() 方法的第二个参数直接设置为 args,例如:
function main(splash, args)
local url = args.url
end
js_enabled 属性: Splash 执行 JavaScript 代码的开关,默认是 true,对其进行设置 true 或 false 可以控制是否执行 JavaScript 代码:
function main(splash, args)
splash:go("https://blog.csdn.net/xw1680?")
splash.js_enabled = false
splash:wait(0.5)
local title = splash:evaljs("document.title")
return {title=title}
end
当我们禁止执行 JavaScript 代码后,重新调用 evaljs 方法执行了 JavaScript 代码,此时运行这段代码,会抛出异常,如下图所示: 一般不设置此属性,默认开启。
resource_timeout 属性:用于设置页面加载的超时时间,单位为秒。如果设置为 0 或 nil(类似 Python 中的 None),代表不检测超时。示例代码如下:
function main(splash)
splash.resource_timeout = 0.1
splash:go("https://blog.csdn.net/xw1680?")
return splash:png()
end
此属性适合在页面加载速度较慢的情况下设置。如果超过某个时间后页面依然无响应,则直接抛出异常并忽略。
images_enabled 属性: 用于设置是否加载图片,默认是加载。禁用该属性可以节省网络流量并提高网页加载速度。但是:禁用图片加载可能会影响 JavaScript 渲染。因为禁用图片后,外层 DOM 节点的高度会受影响,进而影响 DOM 节点的位置。因此,如果 JavaScript 对图片节点有操作的话,其执行就会受到影响。另外:Splash 使用了缓存。如果一开始加载出来了网页图片,然后禁用了图片加载, 再重新加载页面,之前加载好的图片可能还会显示出来,这时直接重启 Splash 即可。禁用 images_enabled 属性示例代码如下:
function main(splash)
splash.images_enabled = false
splash:go("https://www.jd.com/")
return {png=splash:png()}
end
这样返回的页面截图不会带有任何图片,加载速度也会快很多,如下图所示: splash.plugins_enabled 属性: 可以控制浏览器插件(如 Flash 插件) 是否开启。默认情况下,此属性是 false,表示不开启。可以使用如下代码控制其开启和关闭 plugins_enabled:
splash.plugins_enabled = true/false
scroll_position 属性: 控制页面上下滚动或左右滚动,是一个比较常用的属性。示例如下:
function main(splash)
splash:go("https://www.jd.com/")
splash.scroll_position = {y=600}
return {png=splash:png()}
end
控制页面向下滚动 600 像素值,运行结果如下图所示: 如果要让页面左右滚动,可以传入 x 参数,代码如下:
splash.scroll_position = {x=100, y=200}
4、splash 对象的方法:
go 方法: 用来请求某个链接,可以模拟 GET 和 POST 请求,同时支持传入请求头、表单等数据,用法如下:
ok, reason = spalsh:go{url, baseurl=nil,headers=nil,http_method="GET",body=nil,formdata=nil}
该方法的返回值是 ok 变量和 reason 变量的组合,如果 ok 为空,代表页面加载出现了错误,reason 中包含错误的原因,否则代表页面加载成功。示例代码如下:
function main(splash)
local ok, reason = splash:go{"http://www.httpbin.org/post", http_method="POST", body="name=AmoXiang"}
if ok then
return splash.html()
end
end
这里模拟了 POST 请求,并传入了 POST 的表单数据,如果页面加载成功,则返回页面的源代码。运行结果如下图所示: 可以看到,成功实现了 POST 请求并发送了表单数据。
wait() 方法: 用于控制页面等待时间,语法如下:
ok, reason = splash:wait{time,cancel_on_redirect=false,cancel_on_error=true}
其返回值同样是 ok 变量和 reason 变量的组合。示例代码如下:
function main(splash)
splash:go{"https://blog.csdn.net/xw1680"}
splash:wait(2)
return {html=splash.html()}
end
执行上面的代码,可以访问笔者博客主页并等待2秒,随后返回页面源代码,如下图所示: jsfunc 方法: 可以直接调用 JavaScript 定义的方法,但是需要用双中括号把调用的方法包起来,相当于实现了 JavaScript 方法到 Lua 脚本的转换。示例代码如下:
function main(splash, args)
local get_span_count = splash:jsfunc([[function (){
let body = document.body;
const spanList = body.getElementsByTagName("span");
return spanList.length;}
]])
splash:go{"https://blog.csdn.net/xw1680"}
return ("There are % s SPAN"):format(get_span_count())
end
首先,声明了一个 JavaScript 定义的方法,然后在页面加载成功后调用此方法计算出页面中 span 节点的个数。关于 JavaScript 到 Lua 脚本的更多转换细节,参考官方文档: https://splash.readthedocs.io/en/stable/scripting-ref.html#splash-jsfunc
evaljs(): 此方法用于执行 JavaScript 代码并返回最后一条 JavaScript 语句的返回结果,其用法如下:
result = splash:evaljs(js)
例如,可以用下面的代码获取页面标题:
local title = splash:evaljs("document.title")
runjs() 方法: 可以执行 JavaScript 代码,它的功能与 evaljs() 方法的功能类似,但是更偏向于执行某些动作或声明某些方法。例如:
function main(splash, args)
splash:go{"https://blog.csdn.net/xw1680"}
splash:runjs("person = function() {return 'AmoXiang'}")
local result = splash:evaljs("person()")
return result
end
这里先用 runjs 方法声明了一个 JavaScript 方法 foo,然后通过 evaljs 方法调用 foo 方法得到的结果,如下图所示: html 方法:用于获取页面的源代码,是一个非常简单且常用的方法,示例代码如下:
function main(splash, args)
splash:go("https://www.httpbin.org/get")
return splash:html()
end
运行结果如下: png 方法: 此方法用于获取 PNG 格式的页面截图,示例如下:
function main(splash, args)
splash:go("https://blog.csdn.net/xw1680/")
return splash:png()
end
jpeg 方法: 获取 JPEG 格式的页面截图,示例如下:
function main(splash, args)
splash:go("https://blog.csdn.net/xw1680/")
return splash:jpeg()
end
har 方法: 获取页面加载过程的描述信息,示例如下:
function main(splash, args)
splash:go("https://blog.csdn.net/xw1680/")
return splash:har()
end
运行结果如下图所示: 这张图里显示了博客页面加载过程中的每个请求记录的详情。
url 方法: 获取当前正在访问的 URL,示例如下:
function main(splash, args)
splash:go("https://blog.csdn.net/xw1680/")
return splash:url()
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, 运行结果如下图所示: 可以看到,设置的 User-Agent 属性值生效了。
select() 方法: 选中符合条件的第一个节点,如果有多个节点符合条件只会返回一个,其参数是 css 选择器。示例代码如下:
function main(splash)
splash:go("https://www.baidu.com/")
input = splash:select("#kw")
input:send_text('AmoXiang')
splash:wait(3)
return splash:png()
end
先访问百度官网,然后用 select 方法选中搜索框,随后调用 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
运行结果如下: 可以发现,成功获取了10个节点的正文内容。
mouse_click() 方法: 可以模拟鼠标点击操作,传入的参数为坐标值 x 和 y。也可以直接选中某个节点直接调用此方法,示例如下:
function main(splash)
splash:go("https://www.baidu.com")
input = splash:select("#kw")
input:send_text('Splash')
splash:wait(3)
submit = splash:select('#su')
submit:mouse_click()
splash:wait(5)
return splash:png()
end
首先选中页面的输入框,输入文本 Splash,然后选中提交按钮,调用了 mouse_click() 方法提交查询,之后等待5秒,就会返回页面截图,如下图所示: 可以看到,成功获取了查询后的页面内容,模拟了百度搜索操作。至此,splash 对象的常用方法介绍完毕,还有一些方法这里就不逐一进行介绍了,如:获取 cookie 等,更加详细和权威的说明可以参见官方文档:https://splash.readthedocs.io/en/stable/scripting-ref.html,此页面介绍了 splash 对象的所有方法。另外,还有针对页面元素的方法,见官方文档: https://splash.readthedocs.io/en/stable/scripting-element-object.html
5、调用 Splash 提供的 API:
前面简单介绍了 Splash Lua 脚本的用法,但这些脚本是在 Splash 页面里运行测试的,如何才能利用 Splash 渲染页面?Splash 怎样才能和 Python 程序结合使用并爬取 JavaScript 渲染的页面?其实,Splash 给我们提供了一些 HTTP API,我们只需要请求这些 API 并传递相应的参数即可获取页面渲染后的结果,下面介绍开始学习 API。
render.html: 获取 JavaScript 渲染的页面的 HTML 代码,API 地址是 Splash 的运行地址加上此 API 的名称,例如:http://localhost:8050/render.html,用 curl 工具测试一下,如下图所示: 此处给 API 传递了一个 url 参数,以指定渲染的 URL,返回结果即为页面渲染后的源代码。用 Python 实现的代码如下:
import requests
url = "http://localhost:8050/render.html?url=https://www.baidu.com"
response = requests.get(url)
print(response.text)
这样就可以成功输出百度页面渲染后的源代码了。此 API 还有其他参数,例如:wait,用来指定等待秒数。如果要确保页面完全加载出来,就可以设置此参数,例如:
import requests
url = "http://localhost:8050/render.html?url=https://www.jd.com&wait=5"
response = requests.get(url)
print(response.text)
增加等待时间后,得到响应的时间会相应变长,如这里我们等待大约5秒钟才能获取 JavaScript 渲染后的京东页面源代码。另外,此 API 还支持代理设置、图片加载设置、请求头设置和请求方法设置,具体的用法可以参见官方文档:https://splash.readthedocs.io/en/stable/api.html#render-html
render.png: 用于获取页面截图,其参数比 render.html 要多几个,例如 width 和 height 用来控制截图的宽和高,返回值是 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 file:
file.write(response.content)
得到的图片如下图所示: 这样就成功获取了京东首页渲染完成后的页面截图,详细的参数设置可以参考官网文档:https://splash.readthedocs.io/en/stable/api.html#render-png
render.jpeg: 此 API 和 render.png 类似,不过它返回的是 JPEG 格式图片的二进制数据。另外,此 API 比 render.png 多一个参数 quality,该参数可以设置图片质量。https://splash.readthedocs.io/en/stable/api.html#render-jpeg render.har: 此 API 用于获取页面加载的 HAR 数据,运行结果非常多,是一个 JSON 格式的数据,里面包含页面加载过程中的 HAR 数据。如下图所示: render.json: 此 API 包含前面介绍的所有 render 相关的 API 的功能,返回值是 JSON 格式的数据,我们可以通过传入不同的参数控制返回结果,例如:传入 html=1,返回结果会增加页面源代码;传入 png=1,返回结果会增加 PNG 格式的页面截图;传入 har=1,返回结果会增加页面的 HAR 数据。参考官方文档:https://splash.readthedocs.io/en/stable/api.html#render-json
execute: 此 API 才是最为强大的 API。之前介绍了很多关于 Splash Lua 脚本的操作,用此 API 即可实现与 Lua 脚本的对接。要爬取一般的 JavaScript 渲染页面,使用前面的 render.html 和 render.png 等 API 就足够了,但如果要实现一些交互操作,这些 API 还是心有余而力不足,就需要使用 execute 了。实现一个最简单的脚本:
import requests
from urllib.parse import quote
lua = """
function main(splash)
return 'hello splash'
end
"""
url = "http://localhost:8050/execute?lua_source=" + quote(lua)
response = requests.get(url)
print(response.text)
运行结果如下: 实例:
import requests
from urllib.parse import quote
lua = """
function main(splash, args)
local treat = require("treat")
local response = splash:http_get("http://www.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)
返回结果是 JSON 形式的,我们成功获取了请求 URL、状态码和页面源代码。之前所有提过的 Lua 脚本都可以用此方式与 Python 对接了,所有网页的动态渲染、模拟点击、表单提交、页面滑动、延时等待后的结果均可以自由控制获取细节,获取页面源代码和截图也都不在话下。除了 Selenium,Splash 同样可以实现非常强大的渲染功能,同时它不需要浏览器便可以渲染,使用起来非常方便。
笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习爬虫技术的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!
????好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。 ????如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞 、评论 、收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。 ?编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!
|