本主题主要是scrapy入门,包含内容如下:
1. Scrapy框架环境搭建;
2. 理解scrapy框架结构;
3. 理解并能处理简单的数据流;
如果想关注爬虫的高级技术与应用场景,请关注后继内容与马哥教育。这个系列包括数据采集,数据分析与数据可视化。
一、Scrapy安装
Scrapy的安装是比较简单的,直接使用pip可以完成最新版本的安装。
目前最新版本是:1.6。
1.官网地址
Scrapy的官方地址是:https://scrapy.org !
2. 安装
安装指令
pip install scrapy
安装过程3. 测试安装
只需要启动python交互式编程终端,看看能否import模块scrapy即可;4. scrapy帮助
在交互式编程终端,使用import引入scrapy模块,并使用help(scrapy)获取scrapy框架API整体模块结构(其他更加详细的帮助可以使用help与dir获取)5. 教程与参考资料
最好的教程我个人认为还是官方的教程+API帮助。二、Scrapy组件结构与工作流程
1. 核心组件介绍
1.1. 组件01:引擎(Scrapy Engine)
Scrapy Engine引擎作用有两个:
1. 控制Scrapy框架中所有组件之间的数据流;
2. 并在某些爬取动作发生的时候触发数据处理事件。
1.2. 组件02:调度器(Scheduler)
调度器(Scheduler)接收来自引擎的请求,并将它们排队,以便在引擎请求时向引擎提供这些请求。
1.3. 组件03:下载器(Downloader)
下载者(Downloader)负责下载网页并将其发送给引擎,引擎把下载的网页发送给蜘蛛/爬虫处理。
1.4. 组件04:蜘蛛/爬虫(Spiders)
蜘蛛/爬虫(Spider)负责解析下载器下载的网页,并从中提取数据项(ITEM)(也称为爬取项)或后续的爬取请求。
蜘蛛/爬虫(Spider)一般由用户实现,用来实现用户的爬取逻辑。一般继承Spider类来定制实现。
1.5. 组件05:数据项管道(Item Pipeline)
数据项管道(Item Pipeline)负责处理被蜘蛛/爬虫提取(或爬取)的数据项。典型的任务包括:
1. 清理;
2. 验证;
3. 持久存储(如将数据项存储在数据库关系数据库或者NoSQL数据库中)。
1.6. 组件06:下载器中间件(Downloader middlewares)
下载器中间件(Downloader middlewares)是位于引擎和下载器之间的特定功能的回调Hook,负责处理从引擎传递到下载器时处理请求,以及从下载器传递到引擎的响应。
使用下载器中间件(Downloader middlewares)的几种情况:
1. 在将请求发送给下载者之前处理该请求(即在Scrapy将请求发送到网站之前);
2. 在传递给spider之前改变接受到的响应;
3. 重新发送新的请求,而不是将收到的响应传递给spider;
4. 在没有爬取到网页的情况下,发送一个响应给spider;
5. 需要根据条件放弃一些请求。
1.7. 组件07:蜘蛛/爬虫中间件(Spider middlewares)
蜘蛛/爬虫中间件(Spider middlewares)是位于引擎和蜘蛛之间的特定功能的Hook,负责处理Spider的输入(响应)和输出(数据项或者请求)。
蜘蛛/爬虫中间件(Spider middlewares)的几种情况:
1. spider回调的输出后的处理:包含:更改/添加/删除请求或数据项;
2. 开始请求的后处理;
3. 处理spider异常;
4. 对一些基于响应内容的请求调用errback,而不是回调。
2. 核心工作流程
Scrapy的工作流程是按照爬取的数据设计的流程,并据此设计组件结构。(下图是来自Scrapy的官方文档)2.1. 流程01-获取请求
引擎从蜘蛛获取需要爬取的初始请求。
源:Spider
目标:Engine
数据:请求
2.2. 流程02-请求调度安排
引擎调度爬取请求到调度器,并申请下一个需要爬取的爬取请求。
源:Engine
目标:Scheduler
数据:请求
2.3. 流程03-调度爬取请求
引擎器返回下一个爬取请求给引擎。(为什么不直接爬取,而是需要经过调度器处理呢?调度的好处在于:多任务爬取,还可以处理爬取请求与爬取过程的时间不一致的时间差。)
源:Scheduler
目标:Engine
数据:请求
2.4. 流程04-发送请求给下载器
引擎将请求发送到下载器,并通过下载器中间软件传递(process_request回调函数可以处理请求数据)。
源:Engine
目标:Downloader
数据:请求
2.5. 流程05-下载器完成下载
一旦页面下载器完成页面下载,下载器将使用下载好的页面生成一个响应(使用该页面),并将其发送到引擎,通过下载器中间软件(process_response回调函数完成下载后的页面数据处理)。
源:Downloader
目标:Engine
数据:响应
2.6. 流程06-数据项抽取
引擎从下载器接收响应并将其发送到spider进行处理,并通过spider中间件进行处理(process_spider_input回调函数处理爬取的网页数据)。
源:Engine
目标:Spider
数据:响应
2.7. 流程07-返回抽取的数据与请求
Spider处理响应(从爬取的网页中抽取需要的数据项),并通过spider中间件(process_spider_output回调函数处理Spider处理过的数据)向引擎返回抽取的数据项或者新的附加请求。
源:Spider
目标:Engine
数据:数据项(附加的请求)
2.8. 流程08-存储抽取的数据项
引擎将已处理的数据项发送到数据项管道,然后将已附加的请求发送到调度程序,并请求可能的下一个请求进行爬取。
源:Engine
目标:Item Pipeline/Scheduler
数据:数据项/附加请求
2.9. 流程09-结束爬取
重复01-08,直到调度器中没有请求调度为止。
三、Scrapy入门
这个入门是按照官方的教程组织。
不是上面介绍的每个组件都需要我们开发,实际只需要我们开发业务部分,爬虫的通用功能部分都封装到框架中,所以我们需要一个框架的环境,并理解整个工作流程,并关注需要开发的部分,以及开发部分与整个框架组件的关系。
1. 创建一个爬虫项目
爬虫项目使用scrapy框架提供的一个工具创建:scrapy,该工具创建的项目会提供业务部分运行的环境与配置。
1.1. scrapy工具介绍
1.1.1. 获取scrapy工具帮助
直接在终端输入scrapy,可以直接输出scrapy工具的帮助。
命令:
localhost:~ yangqiang$ scrapy
其中startproject命令项就是我们马上要使用来创建项目的。
1.1.2. 获取startproject命令项帮助
获取帮助的指令:
localhost:~ yangqiang$ scrapy startproject -h
1.2. 使用scrapy工具创建一个爬虫项目
创建爬虫项目的两个重要参数:
1. 项目名称
2. 项目存放目录(可选,默认当前目录)
1.2.1. 创建项目
localhost:codes yangqiang$ scrapy startproject FirstScrapy
1.2.2. 创建好的项目文件
可以查看创建的项目,其中的文件结合上面的组件与工作流程,大致也知道其用途与作用。1.3. 使用pycharn打开创建的项目
可以使用pycharm IDE工具打开创建的爬虫项目:
2. 实现爬虫业务
因为在框架在开发,为了保证框架能顺利工作,需要按照设计的结构,继承scrapy.Spider类,并发起一个爬取请求,并处理。
2.1. 创建爬虫代码模块
在项目的spiders包路径下,创建一个python源代码文件:spiders.home_spider.py
。
2.2. 继承Spider
由于Spider是抽象类,需要override其中的抽象函数。
def parse(self, response):
raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))
继承Spider类后的子类:
# coding = utf-8
import scrapy
class HomeSpider(scrapy.Spider):
def parse(self, response):
pass
2.3. 爬虫中两个重要的属性
2.3.1. name属性
name属性,用来在执行爬虫的时候指定爬虫。属性类型是字符串。
2.3.2. start_urls属性
start_urls属性用来发起一个爬虫任务请求。属性类型是列表。
2.3.3. 属性实现代码
# coding = utf-8
import scrapy
class HomeSpider(scrapy.Spider):
name = 'home'
start_urls = [
'https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1',
]
def parse(self, response):
pass
3. scrapy工具与运行爬虫项目
3.1. 在项目目录下的scrapy工具的帮助
在scrapy工具创建的爬虫项目顶层目录下,执行scrapy工具获取的帮助会更多。
命令:
localhost:FirstScrapy yangqiang$ scrapy
执行效果:
与项目相关的命令有(6个):
list:列出项目中爬虫列表。
check:检查爬虫。
crawl:启动爬虫任务。
edit:编辑爬虫。
fetch:获取。
parse:解析。
上述命令的使用,使用帮助可以获取。具体的使用在后面会介绍。
3.2. list爬虫
命令:
localhost:FirstScrapy yangqiang$ scrapy list
效果:3.3. edit爬虫
命令:
localhost:FirstScrapy yangqiang$ scrapy edit home
一般不使用这个指令在字符界面下编辑,而是使用IDE工具编辑。不过远程维护使用字符界面是非常方便的,
3.4. crawl爬虫-运行爬虫
命令:
localhost:FirstScrapy yangqiang$ scrapy crawl home
使用该命令启动爬虫任务,其中home是爬虫程序中name指定的爬虫名。
效果:
3.5. check爬虫
命令:
localhost:FirstScrapy yangqiang$ scrapy check home
检查爬虫代码中的错误。
下面是没有错误的例子:3.6. parse爬虫
parse命令用来测试爬虫的parse函数。
parse命令的帮助:
命令:
localhost:FirstScrapy yangqiang$ scrapy parse --spider=home https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
效果(当没有指定爬虫名,这会使用url直接创建一个爬虫,但是scrapy存在bug,会报错,在github上已经有人修正这个bug,可以通过百度找到这个帖子):
3.7. fetch爬虫
直接下载页面,并显示在终端。
命令:
localhost:FirstScrapy yangqiang$ scrapy fetch https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
效果:
3.8. genspider生成爬虫
genspider可以查看爬虫模板,创建爬虫,编辑爬虫等功能。其帮助如下:
3.8.1. 查看模板
一般默认的模板是basic。
命令:
localhost:FirstScrapy yangqiang$ scrapy genspider -l
效果:
3.8.2. 创建爬虫
命令:
localhost:FirstScrapy yangqiang$ scrapy genspider -t crawl myspider https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
效果:3.8.3. 创建并编辑爬虫
命令:
localhost:FirstScrapy yangqiang$ scrapy genspider -e myspider 'https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1'
这个命令是先创建,后编辑。注意:编辑中需要项目的模块导入,否则编辑不会正常。不过一般创建好以后,也不会在终端下编辑。
3.9. view查看爬取页面
该命令首先下载页面,然后使用浏览器打开。
命令:
localhost:FirstScrapy yangqiang$ scrapy view https://www.baidu.com
效果:3.10. shell交互式爬虫处理
使用交互式处理爬虫数据处理。与代码一样,只是交互式开发模式。
命令:
localhost:FirstScrapy yangqiang$ scrapy shell 'https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1'
效果(绿色的表示代码输入): 3.11. settings获取settings.py中的配置
settings指令用来获取与设置settings.py中的配置值。
命令:
localhost:FirstScrapy yangqiang$ scrapy settings --get=SPIDER_MODULES
效果:3.12. runspider运行爬虫文件
这个命令是直接执行爬虫代码文件,与crawl的区别在于,crawl执行的是爬虫项目spiders目录下的有爬虫名的爬虫。
命令:
localhost:FirstScrapy yangqiang$ scrapy runspider ./FirstScrapy/spiders/home_spider.py
效果:四、爬虫中的数据流与数据处理
1. 创建一个测试项目
使用scrapy工具创建一个爬虫数据流与数据处理的测试项目。
创建命令:
localhost:codes yangqiang$ scrapy startproject Scra_DataFlow
创建过程: 下面主要关注点在数据上,所以其他scarpy工具等使用细节可以参考上面的帮助。
同时,scrapy爬虫框架提供了一些快捷实现方式,下面都采用传统的思路实现,这样容易理解,快捷方式的介绍不作为重点,甚至不在这里介绍。
2. 爬虫目标
2.1. 爬取站点
爬取腾讯课堂上的数据。爬取腾讯课堂基础课程中的Python付费课程信息。2.2. 爬取数据
课程名称,培训机构,购买人数,课程价格,开课方式等。
2.3. 理解domain与url
domain:https://ke.qq.com
urls:https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1
3. 使用命令创建爬虫
使用scrapy的genspider指令创建spider代码模板。
命令:
localhost:Scra_DataFlow yangqiang$ scrapy genspider -t basic course ke.qq.com
创建好的代码如下:
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['http://ke.qq.com/']
def parse(self, response):
pass
在Pycharm中,代码在项目中的截图:
4. 爬取URL
实际需要爬取的页面URL为:
https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1
这是scrapy框架数据流的第01步。从爬虫发起一个请求。
该请求,使用迭代器的方式返回给爬虫引擎:
方式一:是引擎调用def start_requests(self)函数,通过返回值得到请求。
方式二:覆盖父类的start_urls属性,用来替代默认的 start_requests函数,返回请求。
然后爬虫引擎,把请求发送给调度器(流程02),根据处理资源的情况,引擎从调度器获取请求(流程03),再发送给下载器(流程04)。
下载器使用引擎发送过来的请求,完成下载任务,并把下载的响应返回给引擎(流程05)。引擎把响应通过parse函数传递给爬虫程序(流程06),爬虫通过parse函数参数,得到下载响应。其中想用通过response对象获取,该响应对象的类型是:<class 'scrapy.http.response.html.HtmlResponse'>。
scrapy框架的数据流,注意其中的编号,对应这我们这里的一样的编号。注意:流程01,02,03,04,05,06都是框架自动完成,如果不做特别的处理,可以不干预框架的流程,直接在流程06的结束得到一个响应对象。
5. 通过parse函数的参数,获取下载响应
5.1. scrapy.http.response.html.HtmlResponse类
数据成员:
|- encoding
|- selector
|- text
|- body
|- meta
|- url
函数成员:
|- body_as_unicode(self)
|- 返回一个unicode的body
|- css(self, query)
follow(self, url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding=None, priority=0, dont_filter=False, errback=None)
|- 返回Request类型的对象。
|- replace(self, *args, **kwargs)
|- urljoin(self, url)
|- xpath(self, query, **kwargs)
获取解码后的文本页面内容。
|- text
数据解析接口:
|- css(self, query)
|- xpath(self, query, **kwargs)
5.2. 直接处理(中断数据流)
5.2.1. 直接保存成文件
这类直接保存成html文本文件。
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
# 2019-02-14 15:00:14 [course] DEBUG: <class 'scrapy.http.response.html.HtmlResponse'>
# self.log(type(response))
# utf-8
# self.log(response.encoding)
# <Selector xpath=None data='<html lang="zh">\n<head>\n <meta charse'>
# self.log(response.selector)
# self.log(response.text) # unicode的文本内容
# self.log(response.body) # 二进制内容
# {'download_timeout': 180.0, 'download_slot': 'ke.qq.com', 'download_latency': 0.592094898223877}
# self.log(response.meta)
# https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1
# self.log(response.url)
# 保存下载的页面到文件,护或者到数据库(可以考虑文档数据库或者mysql关系数据库)
with open('page.html', 'w') as fd:
fd.write(response.text)
下载后保存的文件内容。5.2.2. 解析保存成csv文件
从浏览器获取XPATH格式(/html/body/section[1]/div/div[3]/ul):
把浏览器中获取的XPATH按照我们的需求修改:
| - 浏览器XPATH:/html/body/section[1]/div/div[3]/ul
| - 修改后XPATH://section[1]/div/div[3]/ul/li
因为XPATH是从body文档开始解析的,同时我们希望获取页面上24门课程的内容。
代码如下:
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
# 解析数据,并保存成csv文件。
result = response.xpath('//section[1]/div/div[3]/ul/li')
self.log('XXXX:{}'.format(len(result)))
运行爬虫的输出结果是: xpath返回的是一个list列表,其中元素的类型是:<class 'scrapy.selector.unified.Selector'>,该类型的API帮助可以使用help获取。
下面我们可以直接解析得到需要的字段(保存到csv的实现,这里就略掉,其中使用了xpath或者css,这个知识点,下面专门讲解)。
# -*- coding: utf-8 -*-
import scrapy
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
# 解析数据,并保存成csv文件。
result = response.xpath('//section[1]/div/div[3]/ul/li')
for course_ in result:
# self.log(type(course_))
# 课程名称
course_name = course_.xpath('h4[@class="item-tt"]/a/text()').get()
self.log('课程名称:{}'.format(course_name.strip() if course_name else ''))
# 培训机构
course_organization = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="item-source"]/a/text()').get()
self.log('培训机构:{}'.format(course_organization.strip() if course_organization else ''))
# 课程连接
course_link = course_.xpath('h4[@class="item-tt"]/a/@href').get()
self.log('课程连接:{}'.format(course_link.strip() if course_link else ''))
# 报名人数
course_number = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="line-cell item-user"]/text()').get()
self.log('报名人数:{}'.format(course_number.strip() if course_number else ''))
# 课程状态
course_status = course_.xpath('div[@class="item-status"]/text()').get()
self.log('课程状态:{}'.format(course_status.strip() if course_status else ''))
# 课程价格
course_price = course_.xpath('div[@class="item-line item-line--bottom"]/span/text()').get()
self.log('课程价格:{}'.format(course_price.strip() if course_price else ''))
解析的效果:5.3. 返回数据项到管道(流程07与流程08)
在parse函数中,实际是可以返回数据的:
| - 返回的数据是Items,则引擎接受到数据后,会发送给管道Pipeline。
| - 返回Request请求,则把Request发送给调度器,继续爬取数据。
这里只关注返回Items,返回Request的情路,后面说明。
5.3.1. 定义Items的数据项
继承scrapy.Item类,对应需要接续的字段定义数据项。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class ScraDataflowItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 课程名称
course_name = scrapy.Field()
# 培训机构
course_organization = scrapy.Field()
# 课程连接
course_link = scrapy.Field()
# 报名人数
course_number = scrapy.Field()
# 课程状态
course_status = scrapy.Field()
# 课程价格
course_price = scrapy.Field()
5.3.2. 使用数据项,缓存爬取的数据字段
使用数据项比较模式化,比较容易理解。 下面是实现代码:
# -*- coding: utf-8 -*-
import scrapy
from Scra_DataFlow.items import ScraDataflowItem
class CourseSpider(scrapy.Spider):
name = 'course'
allowed_domains = ['ke.qq.com']
start_urls = ['https://ke.qq.com/course/list?mt=1001&st=2002&tt=3019&price_min=1&page=1']
def parse(self, response):
result = response.xpath('//section[1]/div/div[3]/ul/li')
items = [] # 数据项数组列表
for course_ in result:
# 数据项
item_ = ScraDataflowItem()
course_name = course_.xpath('h4[@class="item-tt"]/a/text()').get()
item_['course_name'] = '{}'.format(course_name.strip() if course_name else '')
# 培训机构
course_organization = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="item-source"]/a/text()').get()
item_['course_organization'] = course_organization.strip() if course_organization else ''
# 课程连接
course_link = course_.xpath('h4[@class="item-tt"]/a/@href').get()
item_['course_link'] = course_link.strip() if course_link else ''
# 报名人数
course_number = course_.xpath(
'div[@class="item-line item-line--middle"]/span[@class="line-cell item-user"]/text()').get()
item_['course_number'] = course_number.strip() if course_number else ''
# 课程状态
course_status = course_.xpath('div[@class="item-status"]/text()').get()
item_['course_status'] = course_status.strip() if course_status else ''
# 课程价格
course_price = course_.xpath('div[@class="item-line item-line--bottom"]/span/text()').get()
item_['course_price'] = course_price.strip() if course_price else ''
items.append(item_)
# 返回数据项到管道
return items
5.3.3. 使用管道保存数据
使用命令:
localhost:Scra_DataFlow yangqiang$ scrapy crawl course -o course.csv
执行过程:附录
需要掌握爬虫的高级应用,以及一些经典场景应用,可以关注后继内容与马哥教育。