天天看点

爬虫框架Scrapy(12)爬取动态页面

文章目录

    • 爬取动态页面
      • (一)Splash 渲染引擎
        • 1. render.html 端点
        • 2. execute 端点
        • 3. 常用属性与方法
          • (1)Splash 对象的属性
          • (2)Splash 对象的方法
      • (二)安装 Scrapy-Scrapy
        • 1. 安装 splash 服务器
        • 2. 安装 Scrapy-Splash 库
        • 3. plash Lua 脚本
      • (三)在 Scrapy 中使用 Splash
        • 1. 页面分析
        • 2. 新建项目
        • 3. 添加配置
        • 4. 编写爬虫
          • (1)编写 quotes.py
          • (2)修改 item.py
          • (3)修改 pipeline.py
        • 5. 运行爬虫

爬取动态页面

在之前章节中,我们爬取的都是静态页面中的信息,静态页面的内容始终不变,爬取相对容易,但在现实中,目前绝大多数网站的页面都是动态页面,动态页面中的部分内容是浏览器运行页面中的 JavaScript 脚本动态生成的,爬取相对困难,这一章来学习如何爬取动态页面。

先来看一个简单的动态页面的例子,

(一)Splash 渲染引擎

Splash 是 Scrapy 官方推荐的 JavaScript 渲染引擎,它是使用 Webkit 开发的轻量级无界面浏览器,提供基于 HTTP 接口的 JavaScript 渲染服务,支持以下功能:

  • 为用户返回经过渲染的 HTML 页面或页面截图。
  • 并发渲染多个页面。
  • 关闭图片加载,加速渲染。
  • 在页面中执行用户自定义的 JavaScript 代码。
  • 执行用户自定义的渲染脚本(lua),功能类似于 PhantomJS。

Splash功能丰富,包含多个服务端点,由于篇幅有限,这里只介绍其中两个最常用的端点:

  • render.html 提供 JavaScript 页面渲染服务。
  • execute:执行用户自定义的渲染脚本(lua),利用该端点可在页面中执行 JavaScript 代码。

Splash文档地址:http://splash.readthedocs.io/en/latest/api.html

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 代码,在页面渲染前执行

这里仅列出部分常用参数,详细内容参见官方文档。

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

3. 常用属性与方法

接下来,看一下splash对象常用的属性和方法。

(1)Splash 对象的属性
属性 描述
splash.args 用户传入参数的表,通过该属性可以访问用户传入的参数,如splash.args.url、splash.args.wait
splash.js_enabled 用于开启/禁止 JavaScript 渲染,默认为 true
splash.images_enabled 用于开启/禁止图片加载,默认为 true
(2)Splash 对象的方法
  • 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 方法

    调用格式:splash:evaljs(snippet)

    方法功能:在当前页面下,执行一段JavaScript代码,并返回最后一句表达式的值。

  • splash:runjs 方法

    调用格式:splash:runjs(snippet)

    方法功能:在当前页面下,执行一段JavaScript代码,与evaljs方法相比,该函数只执行 JavaScript 代码,不返回值。

  • splash:url 方法

    调用格式:splash:url()

    方法功能:获取当前页面的 url。

  • splash:html 方法

    调用格式:splash:html()

    方法功能:获取当前页面的HTML文本。

  • splash:get_cookies 方法

    调用格式:splash:get_cookies()

    方法功能:获取全部Cookie信息。

(二)安装 Scrapy-Scrapy

掌握了 Splash 渲染引擎的基本使用后,我们继续学习如何在 Scrapy 中调用 Splash 服务,Python 库的 scrapy-splash 是非常好的选择。

Scrapy-Splash 的安装分为两部分:一个是 Splash 服务的安装,具体通过 Docker 来安装服务,运行服务会启动一个 Splash 服务,通过它的接口来实现JavaScript 页面的加载;另外一个是 Scrapy-Splash 的 Python 库的安装,安装后就可在 Scrapy 中使用 Splash 服务了。

1. 安装 splash 服务器

$ docker pull scrapinghub/splash
$ docker run -d --name splash -p 8050:8050 scrapinghub/splash
           

2. 安装 Scrapy-Splash 库

使用 pip 安装 scrapy-splash:

$ pip install scrapy-splash
           

3. plash Lua 脚本

在安装了 splash 服务器之后,我们需要在 Virtualbox 设置中添加端口转发:

爬虫框架Scrapy(12)爬取动态页面

运行 splash 服务后,通过web页面访问服务的8050端口(可以在VirtualBox虚拟机的浏览器中访问,也可以在本地浏览器中访问)

http://127.0.0.1:8050/
           

即可看到其web页面,如下图:

爬虫框架Scrapy(12)爬取动态页面

上面有个输入框,默认是http://google.com,我们可以换成想要渲染的网页如:https://www.baidu.com,然后点击

Render me

按钮开始渲染。

爬虫框架Scrapy(12)爬取动态页面

(三)在 Scrapy 中使用 Splash

这里我们可以观察一个典型的供我们练习爬虫技术的网站:quotes.toscrape.com/js/。

1. 页面分析

在浏览器中打开 http://quotes.toscrape.com/js,显示如下图所示。页面中有10条名人名言,每一条都包含在一个

<div class="quote">

元素中。

爬虫框架Scrapy(12)爬取动态页面

现在,我们在

scrapy shell

环境下尝试爬取页面中的名人名言:

$ scrapy shell http://quotes.toscrape.com/js/
...
>>> response.css('div.quote')
[]
           

从结果看出,爬取失败了,在页面中没有找到任何包含名人名言的

<div class="quote">

元素。这些

<div class="quote">

就是动态内容,从服务器下载的页面中并不包含它们(所以我们爬取失败),浏览器执行了页面中的一段 JavaScript 代码后,它们才被生成出来,如下图所示:

爬虫框架Scrapy(12)爬取动态页面
爬虫框架Scrapy(12)爬取动态页面

阅读代码可以了解页面动态生成的细节,所有名人名言信息被保存在数组 data 中,最后的 for 循环迭代 data中的每项信息,使用 document.write 生成每条名人名言对应的

<div class="quote">

元素。

上面是动态网页中最简单的一个例子,数据被硬编码于 JavaScript 代码中,实际中更常见的是 JavaScript 通过HTTP 请求跟网站动态交互获取数据(AJAX),然后使用数据更新 HTML 页面。爬取此类动态网页需要先执行页面中的 JavaScript 代码渲染页面,再进行爬取。因此,我们进行爬取页面信息之前首先需要用 splash 渲染页面。

2. 新建项目

在项目环境中讲解 scrapy-splash 的使用,创建一个 Scrapy 项目,取名为scrapy_splash:

$ scrapy startproject scrapy_quotes
           

然后,在 scrapy_splash 项目目录下使用

scrapy genspider

命令创建Spider:

$ cd scrapy_quotes/scrapy_quotes
$ scrapy genspider quotes quotes.toscrape.com
           

在 scrapy.cfg 同级目录,创建 bin.py,用于启动 Scrapy 项目,内容如下:

# 在项目根目录下新建:bin.py
from scrapy.cmdline import execute
# 第三个参数是爬虫程序名
execute(['scrapy', 'crawl', 'quotes',"--nolog"])
           

创建好的项目树形目录如下:

$ tree
.
├── bin.py
├── scrapy.cfg
└── scrapy_quotes
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── __init__.py
        └── quotes.py
           

3. 添加配置

在项目配置文件 settings.py 中对 scrapy-splash 进行配置,添加内容如下:

# Splash服务器地址
SPLASH_URL = 'http://127.0.0.1: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,
}
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
ITEM_PIPELINES = {
   'scrapy_quotes.pipelines.ScrapyQuotesPipeline': 300,
}
           

编写 Spider 代码过程中,使用 scrapy_splash 调用 Splash 服务非常简单,scrapy_splash 中定义了一个 SplashRequest 类,用户只需使用

scrapy_splash.SplashRequest

(替代

scrapy.Request

)提交请求即可。下面是 SplashRequest 构造器方法中的一些常用参数:

参数 功能
url 与 scrapy.Request 中的 url 相同,也就是待爬取页面的 url
headers 与 scrapy.Request 中的 headers 相同
cookies 与 scrapy.Request 中的 cookies 相同
args 传递给 Splash 的参数(除url以外),如wait、timeout、images、js_source等
cache_args 如果 args 中的某些参数每次调用都重复传递并且数据量较大,此时可以把该参数名填入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 的地址

4. 编写爬虫

(1)编写 quotes.py

在这个案例中,我们只需使用 Splash 的 render.html 端点渲染页面,再进行爬取即可实现 QuotesSpider,代码如下:

import scrapy
from scrapy_splash import SplashRequest
from ..items import QuotesItem


class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def start_requests(self):
        for url in self.start_urls:
            yield SplashRequest(url, args={'images': 0, 'timeout': 60})

    def parse(self, response):
        authors = response.css('div.quote small.author::text').extract()
        quotes = response.css('div.quote span.text::text').extract()
        item = QuotesItem()
        item['authors'] = authors
        item['quotes'] = quotes
        yield from (dict(zip(['author', 'quote'], item)) for item in zip(authors, quotes))

        next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
        if next_url:
            url = response.urljoin(next_url)
            yield SplashRequest(url, args={'images': 0, 'timeout': 60})
           

上述代码中,使用 SplashRequest 提交请求,在 SplashRequest 的构造器中无须传递 endpoint 参数,因为该参数默认值便是

'render.html'

。使用

args

参数禁止

Splash

加载图片,并设置渲染超时时间。

另外,上述代码除了以上这种写法,还可以进行如下编写:

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': 8})
            
    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()
            item = QuotesItem()
            item['authors'] = author
            item['quotes'] = quote
            yield item
        next_url = response.css('li.next > a::attr(href)').extract_first()
        if href:
            url = response.urljoin(next_url)
            yield SplashRequest(url, args={'images': 0, 'timeout': 8})
           
(2)修改 item.py
import scrapy

class QuotesItem(scrapy.Item):
    authors = scrapy.Field()
    quotes = scrapy.Field()
           
(3)修改 pipeline.py
import json

class ScrapyQuotesPipeline:
    def __init__(self):
        self.f = open("quotes.json", 'wb')

    def process_item(self, item, spider):
        # 读取item中的数据 并换行处理
        content = json.dumps(dict(item), ensure_ascii=False) + ',\n'
        self.f.write(content.encode('utf=8'))
        return item

    def close_spider(self, spider):
        # 关闭文件
        self.f.close()
           

5. 运行爬虫

运行 bin.py,等待 1 分钟,就会生成文件

quotes.json

上述文章内容如有错误,欢迎各位读者在评论区留言!