GitOPEN's Home.

《手把手带你学爬虫──初级篇》第6课 强大的爬虫框架Scrapy

Word count: 4,432 / Reading time: 18 min
2018/09/14 Share

本教程所有源码下载链接:https://share.weiyun.com/5xmFeUO 密码:fzwh6g

强大的爬虫框架Scrapy

简介与安装

Scrapy是一个Python爬虫应用框架,爬取和处理结构性数据非常方便。使用它,只需要定制开发几个模块,就可以轻松实现一个爬虫,让爬取数据信息的工作更加简单高效。

Scrapy使用了Twisted异步网络框架来处理网络通信,可以加快下载速度。结合Scrapy-redis,我们可以实现分布式爬虫,极大地提高了爬虫的效率。试想一下,10台、20台、100台服务器同时爬取数据。。。

Scrapy的安装也非常简单:

1
pip install scrapy

Scrapy架构初体验

Scrapy架构及组件

我们先看一下Scrapy的架构图,来试图理解一下Scrapy的执行流程,然后,带着这些问题,进入Scrapy框架的编写爬虫学习。

这张官方的图解非常形象,但看起来不那么直观,我们来看另一张网友做的图,简单好看,点个赞!

首先,解释一下图中各个组件的作用:

5个组件:

Scrapy Engine:核心引擎,负责控制和调度各个组件,保证数据流转;

Scheduler:负责管理任务、过滤任务、输出任务的调度器,存储、去重任务都在此控制;

Downloader:下载器,负责在网上下载网页数据,输入待下载URL,输出下载结果;

Spiders:用户自己编写的爬虫脚本,自定义抓取的意图,就是说你需要哪些数据,怎么爬,在这里定义;

Item Pipline:负责将获取到的数据格式化,格式化、存储、存储位置等在这里质量定义;

2个中间件组件:

Downloader middlewares:介于引擎和下载器之间,对Scrapy的request/response处理的钩子框架,是用于全局修改Scrapy request和response的一个组件,可以在网页下载前后进行逻辑处理;

Spider middlewares:介于引擎和爬虫之间,处理引擎发送给Spiders的response,处理spider产生的item和request返回给引擎。

Scrapy执行流程

用根据图中的序号,我们用文字来描述一下,Scrapy的运转流程:

  1. Engine从Spiders中获取到初始化requests,在自定义spider中叫做start_urls
  2. Engine把起始请求放入Scheduler,同时,向Scheduler获取一个待下载的request;
  3. Scheduler返回给Engine一个待下载的request;
  4. Engine发送request给Downloader,会经过Downloader middlewares;
  5. 这个request通过Downloader下载完成后,生成了一个response,经过Downloader middlewares后到达Engine;
  6. Engine收到response后,经过Spider middlewares发送给Spiders中的自定义spider,执行自定义的爬虫逻辑;
  7. spider执行相应的回调方法,例如parse()处理response,返回item或者新的request,返回的时候经过Spider middlewares;
  8. Engine把item交给Item pipline处理,把新的request通过Engine交给Scheduler;
  9. 如此往复,直到Scheduler中没有新的request。

Scrapy项目初体验

Scrapy项目创建和执行

构建和运行一个基于Scrapy框架的爬虫的通用步骤如下:

  1. 使用scrapy startproject demoSpider创建基于Scrapy框架的爬虫项目;
  2. 使用scrapy genspider demo demo.com生成一个基于basic模板的自定义爬虫,爬虫名字为demo;
  3. 重写pasrse方法,编写处理和爬取规则;
  4. 使用scrapy crawl demo执行爬虫。

在命令行中创建基于Scrapy框架的爬虫的步骤:

Scrapy项目结构解析

我们在PyCharm中打开创建的项目,项目结构如图:

  • scrapy.cfg:项目的主配置文件;
  • demoSpider:最外层的是项目根目录;第二个是该项目的Python模块;
  • demoSpider/items.py:项目中item文件,设置数据存储模板,保存爬取到的数据的容器,用于结构化数据,使用方法和字典类似;
  • demoSpider/piplines.py:项目中的pipelines文件(管道文件),用于数据的持久化处理;
  • demoSpider/middlewares.py:项目的中间件;
  • demoSpider/settings.py:项目的设置文件,如,下载延迟、并发数等;
  • demoSpider/spiders/:编写spider代码的目录。

settings.py文件内容解析

刚创建好的demoSpider的settings文件内容是这样的,每个配置项有什么作用,在注释中已经标明了,这里做到心中有数即可,后面实战的时候,会再次使用。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# -*- coding: utf-8 -*-

# 爬虫项目的名字
BOT_NAME = 'demoSpider'

# 爬虫的路径
SPIDER_MODULES = ['demoSpider.spiders']
NEWSPIDER_MODULE = 'demoSpider.spiders'

# 是否遵守爬虫协议
ROBOTSTXT_OBEY = True

# 设置并发请求书,最大是32,默认是16
#CONCURRENT_REQUESTS = 32

# 为同一个网站的请求配置延迟,默认为0;通常用来控制访问爬取频率,防止被识别被禁止。
# 例如设置为0.25,则表示250ms的延迟。
#DOWNLOAD_DELAY = 3
# 每个域名最大并发请求数
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
# 每个ip最大并发请求数,如果设置了,将忽略设置的域名最大并发请求数
#CONCURRENT_REQUESTS_PER_IP = 16

# 是否禁用cookies,默认不禁用
#COOKIES_ENABLED = False

# 通过Telnet可以监听当前爬虫的状态、信息,操作爬虫等。使用方法是:打开cmd,使用telnet 127.0.0.1 6023 以及est(),即可进入操作页面。不常用。
#TELNETCONSOLE_ENABLED = False

# 默认的请求头,每个请求都可以携带。
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}

# 开始或者禁用中间件,后面的顺序表示优先级,数字越小优先级越高
# 第一个中间件是靠近引擎的中间件,最后一个是靠近蜘蛛的中间件
# 文档 https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'demoSpider.middlewares.DemospiderSpiderMiddleware': 543,
#}

# 下载中间件,后面的顺序表示优先级,数字越小优先级越高
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'demoSpider.middlewares.DemospiderDownloaderMiddleware': 543,
#}

# 自定义扩展
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}

# 自定义PIPELINES处理请求,主要为了存储数据使用,后面的顺序表示优先级,数字越小优先级越高
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'demoSpider.pipelines.DemospiderPipeline': 300,
#}

# 做智能的限速请求。从AUTOTHROTTLE_ENABLED = True开始,到AUTOTHROTTLE_DEBUG = False结束。中间的设置AUTOTHROTTLE_START_DELAY = 5表示第一个请求延迟多少秒。
# 默认禁用。
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# 起始请求的延迟
#AUTOTHROTTLE_START_DELAY = 5
# 最大的请求延迟
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# 启用后,显示每个响应的控制信息
#AUTOTHROTTLE_DEBUG = False

# 默认禁用,设置HTTP缓存
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

demoSpider/spiders/demo.py文件内容解析

这是一个依据默认模板Scrapy帮我们生成的爬虫,内容简单,由于没有任何自定义的编写,因此,现在还不具备爬虫的功能,我们看一下它的默认内容的使用方法:

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

class DemoSpider(scrapy.Spider):
# 爬虫的名字,对应于刚才生成爬虫时指定的名字
name = 'demo'
# 支持的域名,对应于刚才生成爬虫时指定的域名
allowed_domains = ['demo.com']
# 起始链接,爬虫启动后,默认会从这里的url开始发送请求
start_urls = ['http://demo.com/']

# 处理方法,处理引擎转发回来的响应response
def parse(self, response):
pass

parse方法是我们今后处理内容的方法,也就是从response中提取网页的元素或内容。

parse方法的response中,有很多我们可以用的东西:

response.url:访问的连接;

response.text:响应的字符串内容;

response.body:响应的二进制格式内容;

response.meta:它包含四个信息,如:

1
{'depth': 1, 'download_timeout': 180.0, 'download_slot': 'dig.chouti.com', 'download_latency': 0.23752975463867188}

demoSpider/items.py文件内容解析

items.py文件中定义数据存储模板,用面向对象的思维来思考,items中的每个类的实例化对象都是一个包含特定字段和值的结构化数据对象,我们可以将在parse方法中提取到的数据,保存到这个对象中,然后通过管道文件pipeline进行后续处理,如保存到文件,或者保存到数据库。

1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-
# 定义数据模板
import scrapy


class DemospiderItem(scrapy.Item):
# 定义字段
# name = scrapy.Field()
pass

例如,假设我们提取了学生信息,有nameagescore等数据,那么我们可以在items.py中编写一个StudentsItem类,来存储结构化数据:

1
2
3
4
5
6
7
8
9
import scrapy

class StudentsItem(scrapy.Item):
# 姓名
name = scrapy.Field()
# 年龄
age = scrapy.Field()
# 分数
score = scrapy.Field()

那么,在parse方法中,提取出来的数据就可以这样存储:

1
2
3
item['name'] = 'zhangsan'
item['age'] = 18
item['score'] = 99

demoSpider/middlewares.py文件内容解析

该文件中包含两个类,分别是DemospiderSpiderMiddleware爬虫中间件和DemospiderDownloaderMiddleware下载中间件,如果自定义了它们,那么需要在settings.py文件中配置它们。在这里,我们不去细致讨论它们,仅需要知道它们在scrapy中的作用即可。关于它们的详解,将在用到的时候进行详细讲解。

demoSpider/pipelines.py文件内容解析

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

# 定义item的管道文件
#
# 不要忘记在settings文件的ITEM_PIPELINES中启用它
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html


class DemospiderPipeline(object):
# 处理item的方法,该方法必须要返回一个字典数据,Item(或其子类)或者 抛出一个 DropItem 异常。被 drop 的 Item 将不会被接下来的 pipeline 组件处理。
def process_item(self, item, spider):
# raise DropItem()
return item

定义item的管道文件,用来对结构化数据item进行处理,存储到文件或者存储到数据库中。process_item方法中有两个参数:

item:爬取的 Item对象;

spider:爬起item对象的爬虫。

编写好pipelines.py文件以后,需要在settings.py文件中启用它:

1
2
3
ITEM_PIPELINES = {
'demoSpider.pipelines.DemospiderPipeline': 300,
}

XPath语法

XPath 使用路径表达式来选取 XML 文档中的节点或节点集。节点是通过沿着路径 (path) 或者步 (steps) 来选取的。

XPath基于XML的树状结构,有不同类型的节点,包括元素节点,属性节点和文本节点,提供在数据结构树中找寻节点的能力。起初 XPath 的提出的初衷是将其作为一个通用的、介于XPointer与XSLT间的语法模型。但是 XPath 很快的被开发者采用来当作小型查询语言。

简单来说,我们通过Xpath可以获取XML中的指定元素和指定节点的值。在网络爬虫中通常会把爬虫获取的HTML数据转换成XML结构,然后通过XPath解析,获取我们想要的结果。

下面,看一下最常用的路径表达式,也是最基础的:

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

XPath Helper插件

XPath Helper插件安装

为了使用方便,我们在Chrome浏览器中安装XPath Helper插件,帮助我们在页面上测试XPath表达式。

你可以在Chrome扩展商店中直接搜索下载,由于众所周知的原因,很可能(100%)不能访问,那么可以使用备份下载地址:

XPath Helper备份下载地址

安装方法如图所示:

XPath Helper插件使用

安装完成以后,在Chrome浏览器右上角的扩展插件区域,点击XPath Helper图标即可激活使用。

这里,我们使用豆瓣电影Top250作为测试页面,同时实战一下XPath Helper的用法。如图所示:

常用XPath表达式用法

表达式 含义
//div 选取页面上全部div元素
//div[@class='article'] 选取页面上属性class的值为article的div元素
//div[@class='article']//div[@class='item']//div[@class='hd']//span[@class='title'][1]//text() 在上面选取的基础上,选取class属性为title的span元素,由于这个span元素有多个,是同一层级下的并列关系,我们只提取第一个,因此需要用[1]获取。text()用来获取文本内容
//div[@class='article']//div[@class='item']//div[@class='hd']//a//@href 获取a标签的属性href的值,也就是电影详细信息页面的URL连接
//a[contains(@href,'douban')]//@href 找到a标签属性href的值中包含douban字符串的a元素,然后取出来href的值
//a[starts-with(@href,'https://movie.douban.com')]//@href 找到a标签属性href的值中以https://movie.douban.com字符串开头的a元素,然后取出来href的值

CSS选择器基础

CSS选择器是用来对HTML页面中的元素进行控制的,然后设置属性与值,达到对网页样式就行修饰的目的。

要使用css对HTML页面中的元素实现一对一,一对多或者多对一的控制,这就需要用到CSS选择器。

我们在编写爬虫的过程中,可以使用CSS选择器来对网页上的元素、内容进行定位或者获取。

常用CSS选择器语法

表达式 含义
* 选择所有节点
#container 选择id为container的节点
.container 选择所有class包含container的节点
li a 选取所有li 下所有a节点
ul + p 选取ul后面的第一个p元素
div#container > ul 选取id为container的div的第一个ul子元素
ul ~p 选取与ul相邻的所有p元素
a[title] 选取所有有title属性的a元素
a[href="http://sunjiajia.com"] 选取所有href属性为http://sunjiajia.com的a元素
a[href*="sunjiajia"] 选取所有href属性值中包含sunjiajia的a元素
a[href^="http"] 选取所有href属性值中以http开头的a元素
a[href$=".jpg"] 选取所有href属性值中以.jpg结尾的a元素
input[type=radio]:checked 选择选中的radio的元素
div:not(#container) 选取所有id为非container 的div属性
li:nth-child(3) 选取第三个li元素
li:nth-child(2n) 选取第偶数个li元素

有关CSS选择器的用法,我们将在实战中进行编写体验。

实战──用Scrapy爬取豆瓣电影Top250

这章的第1个实战,就是用Scrapy框架重新来爬取豆瓣电影Top250,在这个过程中,熟悉Scrapy框架编写爬虫的基本步骤。

  1. 新建doubanSpiderScrapy项目:

    1
    scrapy startproject doubanSpider
  2. 基于默认模板,生成爬虫douban

    1
    scrapy genspider douban douban.com
  3. 用Pycharm编辑器打开项目;

  4. settings.py中,将ROBOTSTXT_OBEY = True改为ROBOTSTXT_OBEY = False

  5. settings.py中,配置User-Agent:

    1
    USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
  6. 编写items.py文件,定义我们需要的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import scrapy


    class DoubanspiderItem(scrapy.Item):
    # 标题
    title = scrapy.Field()
    # 信息
    bd = scrapy.Field()
    # 评分
    star = scrapy.Field()
    # 简介
    quote = scrapy.Field()
  7. 编写spiders/douban.py爬虫文件:

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    # -*- coding: utf-8 -*-
    import scrapy
    from doubanSpider.items import DoubanspiderItem


    class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['douban.com']
    # 基础url地址
    url = "https://movie.douban.com/top250?start="
    # url参数
    offset = 0
    # 起始url列表
    start_urls = [
    url + str(offset)
    ]

    def parse(self, response):
    item = DoubanspiderItem()
    # 拿到
    movies = response.xpath('//div[@class="info"]')

    for each in movies:
    # 标题
    item['title'] = each.xpath(
    './/div[@class="hd"]//span[@class="title"][1]/text()').extract()[0]
    # 信息
    item['bd'] = each.xpath(
    './/div[@class="bd"]/p/text()').extract()[0].strip()
    # 评分
    item['star'] = each.xpath(
    './/div[@class="star"]/span[@class = "rating_num"]/text()').extract()[0]
    # 简介
    quote = each.xpath('.//div[@class="bd"]//p[@class="quote"]/span/text()').extract()
    if len(quote) != 0:
    item['quote'] = quote[0]

    # 交给管道文件处理
    yield item

    # 循环发送请求,读取每一页的内容
    if self.offset < 225:
    self.offset += 25
    yield scrapy.Request(self.url + str(self.offset), callback=self.parse)
  8. 编写pipelines.py管道文件,用来数据持久化处理:

    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
    import json


    class DoubanspiderPipeline(object):
    def __init__(self):
    """
    构造方法,在这里打开文件
    """
    self.item_list = []
    self.filename = open("douban.json", "w+")

    def process_item(self, item, spider):
    """
    处理item数据
    """
    item_json = json.dumps(dict(item), ensure_ascii=False)
    self.item_list.append(item_json)
    return item

    def close_spider(self, spider):
    """
    爬虫关闭时执行,对数据进行最后的修正工作,并且关闭文件输入输出流
    """
    content = "[" + ",".join(self.item_list) + "]"
    self.filename.write(content)
    self.filename.close()
  9. settings.py文件中,配置管道文件:

    1
    2
    3
    ITEM_PIPELINES = {
    'doubanSpider.pipelines.DoubanspiderPipeline': 300,
    }
  10. 在命令行中执行爬虫:

    1
    scrapy crawl douban
  11. 结果示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [
    {
    "title": "肖申克的救赎",
    "bd": "导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...",
    "star": "9.6",
    "quote": "希望让人自由。"
    },
    {
    "title": "霸王别姬",
    "bd": "导演: 陈凯歌 Kaige Chen 主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha...",
    "star": "9.5",
    "quote": "风华绝代。"
    },
    {
    "title": "这个杀手不太冷",
    "bd": "导演: 吕克·贝松 Luc Besson 主演: 让·雷诺 Jean Reno / 娜塔莉·波特曼 ...",
    "star": "9.4",
    "quote": "怪蜀黍和小萝莉不得不说的故事。"
    }
    ]

作业──使用CSS选择器改写实战项目

要求:

  1. parse()方法中用XPath表达式提取数据的方式,修改为CSS选择器方式提取;
  2. 增加对电影详细信息页面url的爬取。

欣慰帮到你 一杯热咖啡
【奋斗的Coder!】企鹅群
【奋斗的Coder】公众号
CATALOG
  1. 1. 强大的爬虫框架Scrapy
  2. 2. 简介与安装
  3. 3. Scrapy架构初体验
    1. 3.1. Scrapy架构及组件
    2. 3.2. Scrapy执行流程
  4. 4. Scrapy项目初体验
    1. 4.1. Scrapy项目创建和执行
    2. 4.2. Scrapy项目结构解析
      1. 4.2.1. settings.py文件内容解析
      2. 4.2.2. demoSpider/spiders/demo.py文件内容解析
      3. 4.2.3. demoSpider/items.py文件内容解析
      4. 4.2.4. demoSpider/middlewares.py文件内容解析
      5. 4.2.5. demoSpider/pipelines.py文件内容解析
  5. 5. XPath语法
    1. 5.1. XPath Helper插件
      1. 5.1.1. XPath Helper插件安装
      2. 5.1.2. XPath Helper插件使用
    2. 5.2. 常用XPath表达式用法
  6. 6. CSS选择器基础
    1. 6.1. 常用CSS选择器语法
  7. 7. 实战──用Scrapy爬取豆瓣电影Top250
  8. 8. 作业──使用CSS选择器改写实战项目