Splash
安装,连接及配置
Docker的安装
Splash
GitHub:https://github.com/scrapy-plugins/scrapy-splash
PyPI:https://pypi.python.org/pypi/scrapy-splash
使用说明:https://github.com/scrapy-plugins/scrapy-splash#configuration
Splash官方文档:http://splash.readthedocs.io
docker安装Splash:docker run -p 8050:8050 scrapinghub/splash
Splash在8050端口上运行。这时我们打开http://localhost:8050,即可看到Splash的主页
docker run -d -p 8050:8050 scrapinghub/splash
-d参数,它代表将Docker容器以守护态运行,这样在中断远程服务器连接后,不会终止Splash服务的运行
Scrapy-Splash的安装
安装:pip3 install scrapy-splash
Splash Lua脚本
- 入口及返回值:
function main(splash, args) -- 方法名称叫作main()。这个名称必须是固定的,Splash会默认调用这个方法
assert(splash:go(args.url)) --go()方法去加载页面
assert(splash:wait(0.5)) --wait()方法等待了一定时间,参数为等待的秒数,它会转而去处理其他任务,然后在指定的时间过后再回来继续处理
local title = splash:evaljs("document.title") --evaljs()方法传入JavaScript脚本,document.title的执行结果就是返回网页标题,执行完毕后将其赋值给一个title变量
return { -- 返回值既可以是字典形式,也可以是字符串形式,最后都会转化为Splash HTTP Response
html = splash:html(), --返回了页面的源码、截图和HAR信息
png = splash:png(),
har = splash:har(),
title=title --返回title变量
}
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 --迭代函数 ipairs
local ok, reason = splash:go("http://" .. url) -- 字符串拼接使用的是..操作符
if ok then -- 做了加载时的异常检测。go()方法会返回加载页面的结果状态,如果页面出现4xx或5xx状态码,ok变量就为空,就不会返回加载后的图片
splash:wait(2)
results[url] = splash:png()
end
end
return results -- 返回是3个站点的截图
end
Splash对象属性
main()方法的第一个参数是splash,这个对象非常重要,它类似于Selenium中的WebDriver对象,我们可以调用它的一些属性和方法来控制加载过程
- args
该属性可以获取加载时配置的参数,比如URL(splash.args.url),如果为GET请求,它还可以获取GET请求参数;如果为POST请求,它可以获取表单提交的数据。Splash也支持使用第二个参数直接作为args(args.url)
- js_enabled
JavaScript执行开关(splash:evaljs()),可以将其配置为true或false来控制是否执行JavaScript代码,默认为true
- resource_timeout
可以设置加载的超时时间,单位是秒。如果设置为0或nil(类似Python中的None),代表不检测超时
- images_enabled
可以设置图片是否加载,默认情况下是加载的。禁用该属性(splash.images_enabled = false)后,可以节省网络流量并提高网页加载速度。但是需要注意的是,禁用图片加载可能会影响JavaScript渲染。
- plugins_enabled
控制浏览器插件(如Flash插件)是否开启(splash.plugins_enabled = true/false)。默认情况下,此属性是false,表示不开启
- scroll_position
通过设置此属性,我们可以控制页面上下或左右滚动(splash.scroll_position = {x=100, y=200})x代表右滚动,y代表下滚动
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变量中包含了错误的原因,否则证明页面加载成功。
- 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的组合
- 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
- evaljs()
可以执行JavaScript代码并返回最后一条JavaScript语句的返回结果.result = splash:evaljs(js)
- 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
- autoload()
可以设置每个页面访问时自动加载的对象,只负责加载JavaScript代码或库,不执行任何操作。使用方法如下:
ok, reason = splash:autoload{source_or_url, source=nil, url=nil}
+ source_or_url:JavaScript代码或者JavaScript库链接。
+ source:JavaScript代码。
+ url:JavaScript库链接
例如:
splash:autoload([[ --声明了一个JavaScript方法
function get_document_title(){
return document.title;
}
]])
assert(splash:autoload("https://code.jquery.com/jquery-2.1.3.min.js")) --加载jQuery库
- 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) --截图任务,定时0.2
splash:go("https://www.taobao.com")
splash:wait(3.0)
return snapshots
end
- http_get()
此方法可以模拟发送HTTP的GET请求,返回response对象
‘response = splash:http_get{url, headers=nil, follow_redirects=true}’
+ url:请求URL。
+ headers:可选参数,默认为空,请求头。
+ follow_redirects:可选参数,表示是否启动自动重定向,默认为true。
- http_post()
和http_get()方法类似,此方法用来模拟发送POST请求,不过多了一个参数body,可选参数,即表单数据,默认为空。
例如:
local json = require("json")
local response = splash:http_post{"http://httpbin.org/post",
body=json.encode({name="Germey"}),
headers={["content-type"]="application/json"}
}
- set_content()
用来设置页面的内容
例如:assert(splash:set_content("<html><body><h1>hello</h1></body></html>"))
- html()
用来获取网页的源代码return splash:html()
- png()
此方法用来获取PNG格式的网页截图
- jpeg()
此方法用来获取JPEG格式的网页截图
- har()
此方法用来获取页面加载过程描述
HAR(HTTP Archive),是一个用来储存HTTP请求/响应信息的通用文件格式,基于JSON。
- url()
此方法可以获取当前正在访问的URL
- get_cookies()
此方法可以获取当前页面的Cookies
- add_cookie()
可以为当前页面添加Cookie,用法如下:cookies = splash:add_cookie{name, value, path=nil, domain=nil, expires=nil, httpOnly=nil, secure=nil}
- clear_cookies()
此方法可以清除所有的Cookies
- get_viewport_size()
此方法可以获取当前浏览器页面的大小,即宽高
- et_viewport_size()
此方法可以设置当前浏览器页面的大小,即宽高splash:set_viewport_size(width, height)
- set_viewport_full()
此方法可以设置浏览器全屏显示
- set_user_agent()
此方法可以设置浏览器的User-Agent
- set_custom_headers()
此方法可以设置请求头
splash:set_custom_headers({
["User-Agent"] = "Splash",
["Site"] = "Splash",
})
- select()
该方法可以选中符合条件的第一个节点,如果有多个节点符合条件,则只会返回一个,其参数是CSS选择器。
- select_all()
此方法可以选中所有符合条件的节点,其参数是CSS选择器
- mouse_click()
此方法可以模拟鼠标点击操作,传入的参数为坐标值x和y。此外,也可以直接选中某个节点,然后调用此方法
- send_text()方法填写了文本
官方文档https://splash.readthedocs.io/en/stable/scripting-ref.html,此页面介绍了Splash对象的所有API操作。
针对页面元素的API操作,链接为https://splash.readthedocs.io/en/stable/scripting-element-object.html。
Splash API调用
- render.html
此接口用于获取JavaScript渲染的页面的HTML代码,接口地址就是Splash的运行地址加此接口名称,例如接口页面http://localhost:8050/render.html,接口可指定参数
url:url = 'http://localhost:8050/render.html?url=https://www.baidu.com'
wait指定等待秒数:url = 'http://localhost:8050/render.html?url=https://www.taobao.com&wait=5'
此接口还支持代理设置、图片加载设置、Headers设置、请求方法设置,具体的用法可以参见官方文档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('taobao.png', 'wb') as f:
f.write(response.content)
详细的参数设置可以参考官网文档https://splash.readthedocs.io/en/stable/api.html#render-png。
- render.jpeg
此接口和render.png类似,不过它返回的是JPEG格式的图片二进制数据。另外,此接口比render.png多了参数quality,它用来设置图片质量
- render.har
此接口用于获取页面加载的HAR数据,是一个JSON格式的数据,其中包含页面加载过程中的HAR数据
- render.json
此接口包含了前面接口的所有功能,返回结果是JSON格式
以通过传入不同参数控制其返回结果。比如,传入html=1,返回结果即会增加源代码数据;传入png=1,返回结果即会增加页面PNG截图数据;传入har=1,则会获得页面HAR数据。
更多参数设置,具体可以参考官方文档:https://splash.readthedocs.io/en/stable/api.html#render-json。
- execute
用此接口便可实现与Lua脚本的对接。lua_source参数传递了转码后的Lua脚本,通过execute接口获取了最终脚本的执行结果
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)
Splash负载均衡配置
-
- 配置Splash服务
要有多个Splash服务。假如这里在4台远程主机的8050端口上都开启了Splash服务,它们的服务地址分别为41.159.27.223:8050、41.159.27.221:8050、41.159.27.9:8050和41.159.117.119:8050,这4个服务完全一致,都是通过Docker的Splash镜像开启的。访问其中任何一个服务时,都可以使用Splash服务
- 2.配置负载均衡
选用任意一台带有公网IP的主机来配置负载均衡,配置完成后重启一下Nginx服务sudo nginx -s reload
。首先,在这台主机上装好Nginx,然后修改Nginx的配置文件nginx.conf,添加如下内容:
http {
upstream splash {
least_conn;
server 41.159.27.223:8050;
server 41.159.27.221:8050;
server 41.159.27.9:8050;
server 41.159.117.119:8050;
}
server {
listen 8050;
location / {
proxy_pass http://splash;
}
}
}
upstream字段定义了一个名字叫作splash的服务集群配置
least_conn代表最少链接负载均衡,它适合处理请求处理时间长短不一造成服务器过载的情况
upstream splash {
server 41.159.27.223:8050;
server 41.159.27.221:8050;
server 41.159.27.9:8050;
server 41.159.117.119:8050;
}
默认以轮询策略实现负载均衡,每个服务器的压力相同。此策略适合服务器配置相当、无状态且短平快的服务使用。
upstream splash {
server 41.159.27.223:8050 weight=4;
server 41.159.27.221:8050 weight=2;
server 41.159.27.9:8050 weight=2;
server 41.159.117.119:8050 weight=1;
}
weight参数指定各个服务的权重,权重越高,分配到处理的请求越多。假如不同的服务器配置差别比较大的话,可以使用此种配置。
upstream splash {
ip_hash;
server 41.159.27.223:8050;
server 41.159.27.221:8050;
server 41.159.27.9:8050;
server 41.159.117.119:8050;
}
IP散列负载均衡,服务器根据请求客户端的IP地址进行散列计算,确保使用同一个服务器响应请求,这种策略适合有状态的服务,比如用户登录后访问某个页面的情形。对于Splash来说,不需要应用此设置。
- 3.配置认证
不想让其公开访问,还可以配置认证,这仍然借助于Nginx。可以在server的location字段中添加auth_basic和auth_basic_user_file字段,具体配置如下:
http {
upstream splash {
least_conn;
server 41.159.27.223:8050;
server 41.159.27.221:8050;
server 41.159.27.9:8050;
server 41.159.117.119:8050;
}
server {
listen 8050;
location / {
proxy_pass http://splash;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
}
}
}
使用的用户名和密码配置放置在/etc/nginx/conf.d目录下,我们需要使用htpasswd命令创建。例如,创建一个用户名为admin的文件,相关命令如下:htpasswd -c .htpasswd admin
接下来就会提示我们输入密码,输入两次之后,就会生成密码文件.配置完成后,重启一下Nginx服务:sudo nginx -s reload
-
- 测试
利用http://httpbin.org/get测试即可,实现代码如下:
import requests
from urllib.parse import quote
import re
lua = '''
function main(splash, args)
local treat = require("treat")
local response = splash:http_get("http://httpbin.org/get")
return treat.as_string(response.body)
end
'''
url = 'http://splash:8050/execute?lua_source=' + quote(lua) # splash字符串自行替换成自己的Nginx服务器IP
response = requests.get(url, auth=('admin', 'admin'))
ip = re.search('(\d+\.\d+\.\d+\.\d+)', response.text).group(1)
print(ip)