Splash渲染引擎
Splash是Scrapy官方推荐的JavaScript渲染,它是使用WebKit开发的轻量级无界面浏览器,提供基于HTTP接口的JavaScript渲染服务,支持以下功能:
- 为用户返回经过渲染的HTML页面或页面截图。
- 并发渲染多个页面
- 关闭图片加载,加速渲染
- 在页面中执行用户自定义的JavaScript代码。
- 执行用户自定义的渲染脚本(lua),功能类似于PhantomJS。
首先安装Splash,通过Docker安装。
$ docker run -p 8050:8050 -p 8051:8051 scrapinghub/splash
或者通过docker-compose.yml
:
splash:
container_name: splash
image: scrapinghub/splash
restart: always
ports:
- "8050:8050"
- "8051:8051"
详情可见:https://www.jianshu.com/p/1ab7f03f4e5a
安装完成后,在本机的8050和8051端口开启Splash服务。
Splash功能丰富,包含多个服务端点。这里只介绍两个最常用的端点:
- render.html:提供JavaScript页面渲染服务
- execute:执行用户自定义的渲染脚本(lua),利用该端点可在页面中执行JavaScript代码。
Splash文档地址:http://splash.readthedocs.io/en/latest/api.html
render.html端点
JavaScript页面渲染服务是Splash中最基础的服务。
服务端点 | render.html |
---|---|
请求地址 | http://localhost:8050/render.html |
请求方式 | GET/POST |
返回类型 | html |
render.html端点支持的参数如下表所示。
参数 | 是否必选 | 类型 | 描述 |
---|---|---|---|
url | 必选 | string | 需要渲染页面的url |
timeout | 可选 | float | 渲染页面超时时间 |
proxy | 可选 | string | 代理服务器地址 |
wait | 可选 | float | 等待页面渲染的时间 |
images | 可选 | integer | 是否下载图片,默认为1 |
js_source | 可选 | string | 用户自定义JavaScript代码,在页面渲染前执行 |
这里仅列出部分常用参数,详细内容参见官方文档。
下面是使用requests库调用render.html端点服务对页面:http://quotes.toscrape.com/js/进行渲染的示例代码。
>>> import requests
>>> from scrapy.selector import Selector
>>> splash_url = 'http://localhost:8050/render.html'
>>> args = {'url':'http://quotes.toscrape.com/js', 'timeout':'5', 'image':0}
>>> response = requests.get(splash_url, params=args)
>>> sel = Selector(response)
>>> texts = sel.css('div.quote span.text::text').extract()
>>> for text in texts:
... print text
...
“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”
“It is our choices, Harry, that show what we truly are, far more than our abilities.”
“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.”
“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”
“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”
“Try not to become a man of success. Rather become a man of value.”
“It is better to be hated for what you are than to be loved for what you are not.”
“I have not failed. I've just found 10,000 ways that won't work.”
“A woman is like a tea bag; you never know how strong it is until it's in hot water.”
“A day without sunshine is like, you know, night.”
在上述代码中,依据文档中的描述设置参数url、timeout、images,然后发送HTTP请求到服务接口地址。从运行结果看出,页面渲染成功,我们爬取到了页面中的10条名人名言。
execute端点
在爬取某些页面时,我们想在页面中执行一些用户自定义的JavaScript代码,例如,用JavaScript模拟点击页面中的按钮,或调用页面中的JavaScript函数与服务器交互,利用Splash的execute端点提供的服务可以实现这样的功能。
服务端点 | execute |
---|---|
请求地址 | http://localhost:8050/execute |
请求方式 | POST |
返回类型 | 自定义 |
execute端点支持的参数如下表所示。
参数 | 必选/可选 | 类型 | 描述 |
---|---|---|---|
lua_source | 必选 | string | 用户自定义的lua脚本 |
timeout | 可选 | float | 渲染页面超时时间 |
proxy | 可选 | string | 代理服务器地址 |
我们可以将execute端点的服务看做一个可用lua语言编程的浏览器,功能类似于PhantomJS。使用时需传递一个用户自定义的lua脚本给Splash,该lua脚本中包含用户想要模拟的浏览器行为,例如:
- 打开某URL地址的页面
- 等待页面加载及渲染
- 执行JavaScript代码
- 获取HTTP响应头部
- 获取Cookie
下面是使用requests库调用execute端点服务的示例代码:
>>> import requests
>>> import json
>>> lua_script = '''
... function main(splash)
... splash:go("http://example.com")
... splash:wait(0.5)
... local title = splash:evaljs("document.title")
... return {title=title}
... end
... '''
>>> splash_url = 'http://localhost:8050/execute'
>>> headers = {'content-type':'application/json'}
>>> data = json.dumps({'lua_source':lua_script})
>>> response = requests.post(splash_url, headers=headers, data=data)
>>> response.content
'{"title": "Example Domain"}'
>>> response.json()
{u'title': u'Example Domain'}
用户自定义的lua脚本中必须包含一个main函数作为程序入口,main函数被调用时会传入一个splash对象(lua中的对象),用户可以调用该对象上的方法操纵Splash。例如,在上面的例子中,先调用go方法打开某页面,再调用wait方法等待页面渲染,然后调用evaljs方法执行一个JavaScript表达式,并将结果转换为相应的lua对象,最终Splash根据main函数的返回值构造HTTP响应返回给用户,main函数的返回值可以是字符串,也可以是lua中的表(类似于Python字典),表会被编码成json串。
接下来,看一下splash对象常用的属性和方法。
- splash.args属性:用户传入参数的表,通过该属性可以访问用户传入的参数,如splash.args.url、splash.args.wait。
- splash.js_enabled属性:用于开启/禁止JavaScript渲染,默认为True。
- splash.images_enabled属性:用于开启/禁止图片加载,默认为True。
- splash:go方法:splash:go{url, baseurl=nil, headers=nil, http_method="GET", body= nil, formdata=nil}类似于在浏览器中打开某url地址的页面,页面所需资源会被加载,并进行JavaScript渲染,可以通过参数指定HTTP请求头部、请求方法、表单数据等。
- splash:wait方法:splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}等待页面渲染,time参数为等待的秒数。
- splash:evajs方法:splash:evajs(snippet)在当前页面下,执行一段JavaScript代码,并返回最后一句表达式的值。
- splash:runjs方法:splash:runjs(snippet)在当前页面下,执行一段JavaScript代码,与evajs方法相比,该函数只执行Javasc代码,不返回值。
- splash:url方法:splash:url()获取当前页面的url。
- splash:html方法:splash:html()获取当前页面的HTML文本。
- splash:get_cookies方法:splash:get_cookies()获取全部Cookie信息
在Scrapy中使用Splash
掌握了Splash渲染引擎的基本使用后,我们继续学习如何在Scrapy中调用Splash服务,Python库的scrape-splash是非常好的选择。
使用pip安装scrape-splash。
$ pip install scrapy-splash
在项目环境中讲解scrapy-splash的使用,创建一个Scrapy项目,取名为splash_examples:
$ scrapy startproject splash_examples
首先在项目配置文件settings.py
中对scrappy-splash进行配置,添加内容如下:
# Splash服务器地址
SPLASH_URL = 'http://localhost:8050'
# 开启Splash的两个下载中间件并调整HttpCompressionMiddleware的次序
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# 设置去重过滤器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# 用来支持cache_args(可选)
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
编写Spider代码过程中,使用scrapy_splash调用Splash服务非常简单,scrapy_splash中定义了一个SplashRequest类,用户只需要使用scrapy_splash.SplashRequest(替代scrapy.Request)提交请求即可。下面是SplashRequest构造器方法中的一些常用参数。
- url:与scrapy.Request中的url相同,也就是待爬取页面的URL(注意,不是Splash服务器地址)
- headers:与scrapy.Request中的headers相同。
- cookies:与scrapy.Request中的cookies相同。
- args:传递给Splash的参数(除URL以外),如wait、timeout、images、js_source等。
- cache_args:如果args中的某些参数每次调用都重复传递并且数据量较大(例如一段JavaScript代码),此时可以把该参数名填入cache_args列表中,让Splash服务器缓存该参数,如(SplashRequest(url,args={'js_source':js, 'wait':0.5}, cache_args=['js_source']))。
- endpoint:Splash服务端点,默认为'render.html',即JavaScript页面渲染服务,该参数可以设置为'render.json'、'render.har'、'render.png'、'render.jpeg'、'execute'等,有些服务端点的功能我们没有讲解,详细内容可以查阅文档。
- splash_url:Splash服务器地址,默认为None,即使用配置文件中的SPLASH_URL的地址。
现在,大家已经对如何在Scrapy中使用Splash渲染引擎爬取动态页面有了一定了解,接下来我们在已经配置了Splash使用环境的splash_examples项目中完成两个实战项目。
项目实战:爬取toscrape中的名人名言
项目需求
爬取网站http://quotes.toscrape.com/js中的名人名言信息。
页面分析
该网站的页面已在本章开头部分分析过,大家可以看相关内容。
编码实现
首先,在splash_examples项目目录下使用scrape gensipder命令创建Spider:
$ scrapy genspider quotes quotes.toscrape.com
在这个案例中,我们只需使用Splash的render.html端点渲染页面,再进行爬取即可实现QuotesSpider,代码如下:
# -*- coding: utf-8 -*-
import scrapy
from scrapy_splash import SplashRequest
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/js/']
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(url, args={'images': 0, 'timeout': 3})
pass
def parse(self, response):
for sel in response.css('div.quote'):
quote = sel.css('span.text::text').extract_first()
author = sel.css('small.author::text').extract_first()
yield {'quote': quote, 'author': author}
href = response.css('li.next>a::attr(href)').extract_first()
if href:
url = response.urljoin(href)
yield SplashRequest(url, args={'images': 0, 'timeout': 3})
pass
上述代码中,使用SplashRequest提交请求,在SplashRequest的构造器中无须传递endpoint参数,因为该参数默认值便是'render.html'。使用args参数禁止Splash加载图片,并设置渲染超时时间。
运行爬虫,观察结果:
$ scrapy crawl quotes -o quotes.csv
成功爬取了10个页面中的100条名人名言。