本教程所有源码下载链接: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的运转流程:
- Engine从Spiders中获取到初始化requests,在自定义spider中叫做
start_urls
; - Engine把起始请求放入Scheduler,同时,向Scheduler获取一个待下载的request;
- Scheduler返回给Engine一个待下载的request;
- Engine发送request给Downloader,会经过Downloader middlewares;
- 这个request通过Downloader下载完成后,生成了一个response,经过Downloader middlewares后到达Engine;
- Engine收到response后,经过Spider middlewares发送给Spiders中的自定义spider,执行自定义的爬虫逻辑;
- spider执行相应的回调方法,例如parse()处理response,返回item或者新的request,返回的时候经过Spider middlewares;
- Engine把item交给Item pipline处理,把新的request通过Engine交给Scheduler;
- 如此往复,直到Scheduler中没有新的request。
Scrapy项目初体验
Scrapy项目创建和执行
构建和运行一个基于Scrapy框架的爬虫的通用步骤如下:
- 使用
scrapy startproject demoSpider
创建基于Scrapy框架的爬虫项目; - 使用
scrapy genspider demo demo.com
生成一个基于basic模板的自定义爬虫,爬虫名字为demo; - 重写
pasrse
方法,编写处理和爬取规则; - 使用
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 | # -*- coding: utf-8 -*- |
demoSpider/spiders/demo.py
文件内容解析
这是一个依据默认模板Scrapy帮我们生成的爬虫,内容简单,由于没有任何自定义的编写,因此,现在还不具备爬虫的功能,我们看一下它的默认内容的使用方法:
1 | # -*- coding: utf-8 -*- |
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 | # -*- coding: utf-8 -*- |
例如,假设我们提取了学生信息,有name
、age
、score
等数据,那么我们可以在items.py
中编写一个StudentsItem
类,来存储结构化数据:
1 | import scrapy |
那么,在parse
方法中,提取出来的数据就可以这样存储:
1 | item['name'] = 'zhangsan' |
demoSpider/middlewares.py
文件内容解析
该文件中包含两个类,分别是DemospiderSpiderMiddleware
爬虫中间件和DemospiderDownloaderMiddleware
下载中间件,如果自定义了它们,那么需要在settings.py
文件中配置它们。在这里,我们不去细致讨论它们,仅需要知道它们在scrapy中的作用即可。关于它们的详解,将在用到的时候进行详细讲解。
demoSpider/pipelines.py
文件内容解析
1 | # -*- coding: utf-8 -*- |
定义item的管道文件,用来对结构化数据item进行处理,存储到文件或者存储到数据库中。process_item
方法中有两个参数:
item
:爬取的 Item对象;
spider
:爬起item对象的爬虫。
编写好pipelines.py
文件以后,需要在settings.py
文件中启用它:
1 | ITEM_PIPELINES = { |
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插件使用
安装完成以后,在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框架编写爬虫的基本步骤。
新建
doubanSpider
Scrapy项目:1
scrapy startproject doubanSpider
基于默认模板,生成爬虫
douban
:1
scrapy genspider douban douban.com
用Pycharm编辑器打开项目;
在
settings.py
中,将ROBOTSTXT_OBEY = True
改为ROBOTSTXT_OBEY = False
;在
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'
编写
items.py
文件,定义我们需要的:1
2
3
4
5
6
7
8
9
10
11
12import scrapy
class DoubanspiderItem(scrapy.Item):
# 标题
title = scrapy.Field()
# 信息
bd = scrapy.Field()
# 评分
star = scrapy.Field()
# 简介
quote = scrapy.Field()编写
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)编写
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
26import 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()在
settings.py
文件中,配置管道文件:1
2
3ITEM_PIPELINES = {
'doubanSpider.pipelines.DoubanspiderPipeline': 300,
}在命令行中执行爬虫:
1
scrapy crawl douban
结果示例:
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选择器改写实战项目
要求:
- 将
parse()
方法中用XPath
表达式提取数据的方式,修改为CSS选择器
方式提取; - 增加对电影详细信息页面url的爬取。