之前爬取的都是静态页面中的信息,静态页面的内容始终不变。之前爬取的都是静态页面中的信息,静态页面的内容始终不变。但在现实中的绝大多数网站页面都是动态页面,动态页面中的部分内容是浏览器运行页面中的javascript脚本动态生成的,爬取相对困难。
比如爬取quotes.toscrape.com/js的名人名言信息,用selector提取到的是空列表[],从服务器的下载页面中并不包含这些信息,所以爬取失败了。当浏览器执行了页面中的一段javascript代码后,它们才被生成出来。可以阅读javascript代码,了解页面动态生成的细节,发现价格信息都保存在什么地方,然后用for循环迭代它的每项信息。
例子中的价格数据被硬编码于javascript代码中,实际中更常见的是javascript是通过HTTP请求跟网站动态交互获取数据(AJAX),然后使用数据更新HTML页面,爬取此类动态网页需要先执行页面中的javascript代码渲染页面,再进行爬取。下面我们介绍如何使用javascript渲染引擎渲染页面
Splash渲染引擎
Splash是scrapy官方推荐的javascript渲染引擎,它是使用Webkit开发的轻量级无界限浏览器,提供基于HTTP接口的javascript渲染服务,支持以下功能:①为用户返回经过渲染的HTML页面或页面截图 ②并发渲染多个页面 ③关闭图片加载,加速渲染 ④在页面中执行用户自定义的javascript代码 ⑤执行用户自定义的渲染脚本(lua)、功能类似于PhantomJS1
2
3
4
5先安装Splash,在linux下使用dockker安装十分方便:
$sudo apt-get install docker
$sudo docker pull scrapinghub/splash
安装完后,在本机的8050和8051端口开启Splash服务:
$sudo docker run -pp 8050 -p 8051:8051 scrapinghub/splash
Splash功能丰富,包含多个服务端点,比如 ①render.html:提供javascript页面渲染服务 ②execute:执行用户自定义的渲染脚本(lua),利用该端点可在页面中执行javascript代码
(1)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端点服务对页面quotes.toscrape.com/js进行渲染的示例代码1
2
3
4
5
6
7import requests
from scrapy.selector import Selector
spash_url='http://localhost:8050/render.html'
args={'url':'http://quotes.toscrape.com/js','timeout':5,'image':0} #根据文档中的描述设置参数url、timeout、images
response=requests.get(splash_url,params=args) #发送HTTP请求到服务接口地址
sel=Selector(response)
sel.css('div.quote span.text::text').extract() 提取所有名人名言
(2)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端点服务的示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import requests
import json
lua_script="
function main(splash)
splash:go("http://example.com") 打开野茫茫
splash:wait(0.5) 等待加载
local title=splash:evaljs("document.title") 执行js代码获取结果
return {title=title} 返回json形式的结果
end
splash_url="http://localhost:8050/execute"
headers={'content-type':'application/json'}
data=json.dumps({'lua_source':lua_script})
response=requests.post(splash_rul,headers=headers,data=data)
response.content #返回b'{"title":"Example Domain"}'
response.json() #返回{"title":"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:evaljs(snippet) 在当前页面下,执行一段javascript代码,并返回最后一句表达式的值;⑦splash:runjs(snippet),在当前页面下,执行一段javascript代码,与evaljs方法相比,该函数只执行javascript代码,不返回值;⑧splash:url()获取当前页面的url;⑨splash:html()获取当前页面的HTML文本;⑩splash:get_cookies()获取全部Cookie信息
在scrpay中使用Splash
掌握了Splash渲染引擎的基本使用后,继续学习如何在scrapy中调用Splash服务,Python库的scrapy-splash是非常好的选择。项目开始前先安装scrapy-splash。在设置文件中设置scrapy-splash,添加内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14#Splash服务器地址
SPLASH_URL='http://localhost:8050'
#开启Splash的两个下载中间件并调整HttpCompressionMiddleware的次序
DOWNLOADER_MIDDLEWARES= {
'scrapy_splash.SplashCookiesMiddleware':723,
'scrapy_splash.SplashMiddleware':725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':800,
}
#设置去重过滤器
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中的名人名言
项目需求:爬取网站quotes.toscrape.com/js中的名人名言信息
创建项目:scrapy genspider quotes quotes.toscrapy.com
在这个案例中,我们只需要使用Splash的render.html端点渲染页面,再进行爬取即可实现QuotesSpider,代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import 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})
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})
上述代码中,使用SplashRequest提交请求,在SplashRequest的构造器中无须传递endpoint参数,因为该参数默认值便是’render.html’。使用args参数进制Splash加载图片,并设置渲染超时时间。
运行爬虫,观察结果>>scrapy crawl quotes -o quotes.csv >>at -n quotes.csv,运行结果显示,我们成功爬取了10个页面中的100条名人名言