目录

用Python写网络爬虫(6)-Scrapy

Scrapy

Scrapy组件

  • engine:用来控制整个系统所有组件间的数据流,在特定动作发生时触发事务;
  • spider:分析器,用于解析response,抓取数据,返回items和新的request
  • downloader:下载器,接收request下载网页内容,并将结果response返回给spider;
  • scheduler:调度器,用来接收request压入队列中,并在引擎再次要求返回时给出request
  • item pipeline:项目管道,处理spider返回的格式化数据,进行清洗、验证和存储,比如存入数据库等;
  • middleware:中间件,实现定制化功能,比如下载器使用代理池等。

Scrapy数据流

https://doc.scrapy.org/en/latest/_images/scrapy_architecture_02.png

上图描述了Scrapy框架中数据的流向:

  1. Engine从Spider中获得初始Request
  2. Engine把Request放入Scheduler并要求返回下一个Request
  3. Sheduler返回下一个Request给Engine;
  4. Engine经过Downloader Middleware把Request发送给Downloader(process_request());
  5. 网页下载完成后,Downloader生成一个Response,经过Downloader Middleware发送给Engine(process_response());
  6. Engine接收Response,并经过Spider Middleware把Response发送给Spider(process_spider_input());
  7. Spider处理Response后,经过Spider Middleware把Items和新的Requests(解析得到)发送给Engine(process_spider_output());
  8. Engine把Items发送给Item Pipelines,把新的Requests发送给Scheduler并要求返回下一个Requests
  9. 重复以上步骤(从1开始,因为初始Request可能有多个),直到Scheduler中所有的请求都处理完成且没有新请求为止。

官方推荐使用姿势

官方文档推荐你这样写一个爬虫:

  1. 新建一个Scrapy项目;
  2. 写爬虫爬取单个网页并抓取数据;
  3. 使用命令行导出抓取的数据,检查数据;
  4. 把爬虫改为爬取所有链接;
  5. 使用spider参数,添加中间件,存储数据等。

爬取豆瓣电影top250榜单

新建项目

安装好Scrapy后,可以运行以下命令生成项目的默认结构(使用douban作为项目名):

1
scrapy startproject douban

下面是scrapy生成的文件树结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.
├── douban
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── douban_spider.py
└── scrapy.cfg

2 directories, 8 files

各个文件的作用如下:

  • items.py:该文件用于定义待抓取的模型item;
  • middlewares.py:该文件用于定义中间件middleware;
  • pipelines.py:该文件用于定义项目管道item pipeline;
  • settings.py:该文件定义了一些设置,如Headers、用户代理和爬取延时等等。
  • spiders/:该目录存储实际的爬虫代码。

另外scrapy.cfg定义了项目配置,一般无需修改,除非定义了多个爬虫。

定义模型

对每条记录,我们爬取四项内容:排名、电影名称、评分、点评人数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
### -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
## http://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class DoubanMovieItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    ranking = scrapy.Field()	# 排名
    movie_name = scrapy.Field()	# 电影名称
    score = scrapy.Field()		# 评分
    score_num = scrapy.Field()	# 点评人数

创建爬虫

用户创建的爬虫(Spider)继承自scrapy.Spider类,用于解析网页数据。

可以使用以下命令快速生成Spider:

1
scrapy genspider douban_movie_top250 https://movie.douban.com/top250

以下是几个主要的属性和方法:

  • name:该属性定义了爬虫名称的字符串。名字应该是唯一的,用于区别Spider,应当为不同的Spider设定不同的名字。
  • start_urls:该属性定义了爬虫起始URL列表。但通过模版生成的URL列表可能与实际需求不一致。
  • allowed_domains:该属性定义了可以爬取的域名列表。如果没有定义该属性,则表示可以爬取任何域名。
  • rules:该属性为一个scrapy.spiders.Rule对象集合,用于过滤需要跟踪哪些链接进行下载。该属性还有一个callback参数,用于解析下载得到的response
  • start_requests():该方法也返回可以爬取的域名列表,但可以在方法中做一些处理,应实现为生成器
  • parse():该方法参数为response对象,需要在该方法中解析数据,返回item对象和新的request对象,也应实现为生成器

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
### -*- coding: utf-8 -*-

from scrapy import Request
from scrapy.spiders import Spider
from douban.items import DoubanMovieItem

class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'

    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36',
    }

    def start_requests(self):
        url = 'https://movie.douban.com/top250'
        yield Request(url, headers=self.headers)

    def parse(self, response):
        item = DoubanMovieItem()
        movies = response.css('ol.grid_view>li')
        for movie in movies:
            item['ranking'] = movie.css('div.pic>em::text').extract()[0]
            item['movie_name'] = movie.css('div.hd span.title::text').extract()[0]
            item['score'] = movie.css('div.star>span.rating_num::text').extract()[0]
            item['score_num'] = movie.css('div.star>span::text').re(r'(\d+)人评价')[0]
            yield item

        next_url = response.css('span.next>a::attr(href)').extract()
        if next_url:
            next_url = 'https://movie.douban.com/top250' + next_url[0]
            yield Request(next_url, headers=self.headers)

关于解析

  • 可以使用xpath、css选择器和正则表达式解析response对象,也可以组合使用几种解析方式;
  • 使用css选择器时,要取得html标签的文本使用::text,要取得html标签的属性使用::attr(href)(取得href属性)。

优化设置

settings.py文件中可以对爬虫进行设置。

  • CONCURRENT_REQUESTS_PER_DOMAIN:单域名并发数;
  • DOWNLOAD_DELAY:下载延时,但延时并不是精确的,精确延时会造成爬虫更加容易被检测到。
  • DEFAULT_REQUEST_HEADERS:设置默认的Headers。

测试爬虫

想要从命令行运行爬虫,需要使用crawl命令,并带上爬虫名称。

1
scrapy crawl douban_movie_top250 -o result.csv

-o选项指定了输出形式,可以选择为json、csv或xml。

如果出现403错误,可能是被反爬虫策略拦截了,可以采用设置UA的形式来绕过。

其他技巧

添加中间件

占坑待更。参考https://github.com/amoyiki/LearnedAndProTest/tree/master/douban。

使用shell命令抓取

为了帮助测试如何从网页中抽取数据,Scrapy提供了shell命令,可以下载URL并在Python解释器中给出结果状态。

1
scrapy shell https://movie.douban.com/top250

完成后的python解释器界面中,可以直接使用response对象:

  • response.url:抓取链接;
  • response.status:HTTP响应码;
  • response.css():css选择器;

中断与恢复爬虫

Scrapy内置了对暂停与恢复爬取的支持,要开启该功能,只需要设定用于保存爬虫当前状态目录的JOBDIR即可。需要注意的是,多个爬虫的状态需要保存在不同的目录当中。

1
scrapy crawl douban_moive_top250 -s JOBDIR=douban/jobdir

另外,要中止爬虫时,首次使用Crtl+C发送终止信号,Scrapy可以保存爬虫状态;但如果再次使用Crtl+C,爬虫会立即强行终止,此时无法恢复。