之前爬取的都是静态页面中的信息,静态页面的内容始终不变。之前爬取的都是静态页面中的信息,静态页面的内容始终不变。但在现实中的绝大多数网站页面都是动态页面,动态页面中的部分内容是浏览器运行页面中的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)、功能类似于PhantomJS

1
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
7
import 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
15
import 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
18
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})
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条名人名言