- scrapy 框架
- 1. scrapy 是什么?
- ① scrapy 架构组成 (基本模块概述)
- ② scrapy 工作原理
- ③ scrapy 工作原理图
- 2. scrapy 框架的基本使用
- ① 基本使用方法
- ② spiders 自定义文件内部
- ③ response 常用的方法
- 3. scrapy shell
- ① 什么是 scrapy shell?
- ② 安装 ipython
- ③ 进入到 scrapy终端
- 4. pipelines 管道封装
- 4.1 yield 关键字
- 4.2 管道封装(单管道)
- ① 确定数据结构
- ② 获取网页内容
- ③ 管道操作
- ④ 开启管道(单)
- 4.3 开启多管道
- 5. 日志信息和日志等级
- 6. scrapy 的 post请求
- 7. 设置代理
- 8. CrawlSpider
- ① CrawlSpider 概述
- ② 运行原理图
- ③ CrawlSpider案例
- 9. ★ 写代码注意点(业务逻辑)
- 10. 案例代码 (使用CrawlSpider)
- ① football.py (spiders)
- ② items.py
- ③ piplines.py
- ④ settings.py
- ⑤ start.py (启动文件)
- 11. scrapy 的一些注意点 (特性)
- ① scrapy 采用异步下载
- ② 使用默认方法下载图片注意点
- ③ meta 为只读属性
- ④ 导入 settings 失败
① scrapy 架构组成 (基本模块概述)Scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理
或存储历史数据等一系列的程序中。
- 调度器(Scheduler)
调度器,可以把它假设成为一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址(不做无用功)。用户可以自己的需求定制调度器。 - 下载器(Downloader)
下载器,是所有组件中负担最大的,它用于高速地下载网络上的资源。Scrapy 的下载器代码不会太复杂,但效率高,主要的原因是整个 Scrapy框架都在建立在 twisted 这个高效的异步模型上的, Scrapy 下载器也是是建立在 twisted 这个高效的异步模型上的。 - 爬虫(Spider)
爬虫,是用户最关心的部分。用户定制自己的爬虫(通过定制正则表达式等语法),用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。例如使用 Xpath 提取感兴趣的信息。
用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。 - 实体管道(Item Pipeline)
实体管道,用于接收网络爬虫传过来的数据,并经过几个特定的次序处理数据,典型的任务包括验证实体的有效性、清除不需要的信息、存入数据库(持久化实体)、存入文本文件等。 - Scrapy引擎(Scrapy Engine)
Scrapy 引擎是整个框架的核心,用来处理整个系统的数据流,触发各种事件。它用来控制调试器、下载器、爬虫。实际上,引擎相当于计算机的CPU,它控制着整个流程。 - 中间件(Middlewares)
整个 Scrapy 框架有很多中间件,如下载器中间件、网络爬虫中间件等,这些中间件相当于过滤器,夹在不同部分之间截获数据流,并进行特殊的加工处理。
(1)引擎 ‐‐‐> 自动运行,无需关注,会自动组织所有的请求对象,分发给下载器
(2)下载器 ‐‐‐> 从引擎处获取到请求对象后,请求数据
(3)spiders ‐‐‐> Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例 如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及 分析某个网页(或者是有些网页)的地方。
(4)调度器 ‐‐‐> 有自己的调度规则,无需关注
(5)管道(Item pipeline) ‐‐‐> 最终处理数据的管道,会预留接口供我们处理数据
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。 每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行 一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
1. 清理HTML数据
2. 验证爬取的数据(检查item包含某些字段)
3. 查重(并丢弃)
4. 将爬取结果保存到数据库中
③ scrapy 工作原理图
2. scrapy 框架的基本使用
① 基本使用方法
1. 创建 scrapy项目:
先切换到项目所要放置的位置: cd /d D:Pycharmuntitled2Python-爬虫10. Scrapy 框架
终端输入: scrapy startproject 项目名称
注意:
1. 项目名称不能以数字开头!
2. 爬虫文件不能以汉字开头,项目名称可以,但不建议
项目组成:
spiders
__init__.py 自定义的爬虫文件.py ‐‐‐> 由我们自己创建,是实现爬虫核心功能的文件
__init__.py
items.py ‐‐‐> 定义数据结构的地方,是一个继承自 scrapy.Item的类
middlewares.py ‐‐‐> 中间件 代理
pipelines.py ‐‐‐> 管道文件,里面只有一个类(可以增加),用于处理筛选出来的的实体数据(如将数据存储在数据库中),默认是300优先级,值越小优先级越高(1‐1000)
settings.py ‐‐‐> 配置文件 比如:是否遵守robots协议,User‐Agent定义等
2. 创建爬虫文件
(1) 切换到项目中的 spiders目录下
cd 目录名字/目录名字/spiders
cd scrapy_baidu_01scrapy_baidu_01spiders
(2) 创建爬虫文件
scrapy genspider 爬虫名字 网页的域名
scrapy genspider baidu http://www.baidu.com
爬虫文件的基本组成:
继承scrapy.Spider类
name = 'baidu' ‐‐‐> 运行爬虫文件时使用的名字
allowed_domains ‐‐‐> 爬虫允许的域名,在爬取的时候,如果不是此域名之下的 url,会被过滤掉
start_urls ‐‐‐> 声明了爬虫的起始地址,可以写多个url,一般是一个
parse(self, response) ‐‐‐> 解析数据的回调函数
response.text ‐‐‐> 响应的是字符串
response.body ‐‐‐> 响应的是二进制文件
response.xpath()‐> xpath方法的返回值类型是selector列表
extract() ‐‐‐> 提取的是 selector对象的是 data
extract_first() ‐‐‐> 提取的是 selector列表中的第一个数据
3. 运行爬虫代码
scrapy crawl 爬虫名称
【注意】:应在 spiders 文件夹内执行
将 settings文件中 的 ROBOTSTXT_OBEY = True 注释掉,就不用遵守 robots君子协议了
【注意1】:start.py 文件和 scrapy_cfg 文件处于同一级目录
【注意2】:此启动方法会使得代码中的相对路径出错,原本是相对于 piplines.py 或者 settings.py 所在目录那一级,现在是相对于 start.py 文件!
设置请求头
在 settings.py 文件中取消以下代码段,添加请求头
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,**;q=0.8',
'Accept-Language': 'en'
}
# 局部设置使用的请求头
# 爬虫程序类中设置
custom_settings = {
'DEFAULT_REQUEST_HEADERS' : {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
}
DOWNLOAD_DELAY
默认值:0 Downloader从同一网站上访问页面应该等待的时间。这个设置常常被用来控制爬取的速度,以免对网站造成太大的压力。这个值的单位为秒,并且可以使用小数。 这个值同样被RANDOMIZE_DOWNLOAD_DELAY所影响。在默认的情况下,Scrapy不会每次都等待一个固定的时间,而是使用一个范围在0.5-1.5之间的值乘以DOWNLOAD_DELAY。 当CONCURRENT_REQUESTS_PER_IP的值不为0时,延迟将按照每个IP判断,而不再是Domain。 这个设置可以在Spider中通过download_delay属性进行设置。
RANDOMIZE_DOWNLOAD_DELAY
默认值:True 如果启用,那么DOWNLOAD_DELAY等待的时间将会随机乘以范围在0.5-1.5之间的一个小数。这个设置的作用是使爬虫的等待时间更有随机性,更难以被检测。10. 案例代码 (使用CrawlSpider)
普通版本_1 (不使用 CrawlSpider) 下载多层请求、多页数据(图片):Scrapy 下载多层请求、多页图片(下载使用urllib.request.urlretrieve方法)
普通版本_2 (不使用 CrawlSpider) 下载多层请求、多页数据(图片):Scrapy 下载多层请求、多页图片 (重写get_media_requests、file_path方法)
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import ScrapyFootballItem
class FootballSpider(CrawlSpider):
name = 'football'
allowed_domains = ['mynba.tv']
start_urls = ['http://www.mynba.tv/video/?page=1']
rules = (
Rule(LinkExtractor(allow=r'?page=d+'), follow=True),
Rule(LinkExtractor(allow=r'/video/.*?.html'), callback='parse_item', follow=False),
)
def parse_item(self, response):
# 匹配数据
game_title = response.xpath('//*[@id="app"]/div/div[4]/div/div[2]/p[1]/text()').extract_first()
name_1 = response.xpath('//*[@id="app"]/div/div[4]/div/div[1]/p/a/text()').extract_first()
logo_src_1 = response.xpath('//*[@id="app"]/div/div[4]/div/div[1]/img/@src').extract_first()
name_2 = response.xpath('//*[@id="app"]/div/div[4]/div/div[3]/a/text()').extract_first()
logo_src_2 = response.xpath('//*[@id="app"]/div/div[4]/div/div[3]/img/@src').extract_first()
info = ScrapyFootballItem(game_title=game_title, name_1=name_1, logo_src_1=logo_src_1, name_2=name_2,logo_src_2=logo_src_2)
yield info
② items.py
import scrapy
class ScrapyFootballItem(scrapy.Item):
game_title = scrapy.Field() # 比赛详情标题
name_1 = scrapy.Field() # 主队球队名称
logo_src_1 = scrapy.Field() # 主队球队logo
name_2 = scrapy.Field() # 客队球队名称
logo_src_2 = scrapy.Field() # 客队球队logo
③ piplines.py
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import xlwt
class ScrapyFootballPipeline:
def open_spider(self, spider):
self.workbook = xlwt.Workbook()
self.worksheet = self.workbook.add_sheet('sheet1')
self.line_cnt = 0
self.col_name = ['game_title', 'name_1', 'logo_src_1', 'name_2', 'logo_src_2']
# 写入表头
for i in range(4):
self.worksheet.write(self.line_cnt, i, self.col_name[i])
self.line_cnt += 1
def process_item(self, item, spider):
# 写入数据
for i in range(4):
self.worksheet.write(self.line_cnt,i,item[self.col_name[i]])
self.line_cnt += 1
self.workbook.save('football_data.xls')
return item # 必须加,否则其他管道就无法获得item了!
import urllib.request # 用于下载图片
class ScrapyFootballPipeline_2:
def process_item(self, item, spider):
img_src_1 = item['logo_src_1']
img_src_2 = item['logo_src_2']
name_1 = img_src_1.split('/')[-1]
name_2 = img_src_2.split('/')[-1]
urllib.request.urlretrieve(img_src_1,filename=f'img/{name_1}')
urllib.request.urlretrieve(img_src_2,filename=f'img/{name_2}')
return item
④ settings.py
BOT_NAME = 'scrapy_football'
SPIDER_MODULES = ['scrapy_football.spiders']
NEWSPIDER_MODULE = 'scrapy_football.spiders'
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 3
RANDOMIZE_DOWNLOAD_DELAY = True
ITEM_PIPELINES = {
'scrapy_football.pipelines.ScrapyFootballPipeline': 300,
'scrapy_football.pipelines.ScrapyFootballPipeline_2': 301,
}
⑤ start.py (启动文件)
from scrapy import cmdline
cmdline.execute('scrapy crawl football'.split(' '))
11. scrapy 的一些注意点 (特性)
用过几次 scrapy 写爬虫后,对这个框架的一些特性有所感悟
① scrapy 采用异步下载Scrapy 的下载器代码不会太复杂,但效率高! 主要的原因是 Scrapy 下载器是建立在 twisted 这个高效的异步模型上的(其实整个框架都在建立在这个模型上的)
② 使用默认方法下载图片注意点 scrapy 采用异步处理请求,即scrapy发送请求之后,不会等待这个请求的响应(也就是不会阻塞),而是可以同时发送其他请求或者做别的事情。而我们知道服务器对于请求的响应是由很多方面的因素影响的,如网络速度、解析速度、资源抢占等等,其响应的顺序是难以预测的。
scrapy异步的根源,在于它依赖于Twisted框架。Twisted框架是一个Python的event-driven的框架,这里可以理解为是异步I/O的。爬取内容时输出的顺序可能每次运行时都不一样
scrapy也支持多线程
官方文档
如果使用默认的方法对数据处理:
如果单纯获取文本,那么只需start_urls是一个list;
而如果获取图片,则必须 start_urls与item中存储图片路径字段这两者必须都是 list,并且存储图片路径字段必须是 image_urls
【注意】:可以在 get_media_requests 中把相应地方重写了
如果涉及下列方式下载图片
1.用 ImagesPipeline 的默认方法下载或者不修改 ImagesPipeline 的 get_media_requests的返回值
2.重写 ImagesPipeline 的 get_media_requests 方法,但不修改其返回值(即返回值还是一个 Request请求对象列表)
那么item中存储图片路径字段必须是 list! 即:这里要下载的图片(即使只有一张) url 必须命名为 image_urls 放到 list 中传送给 item
除非重写 ImagesPipeline 的 get_media_requests 的方法的返回值(本是一个请求对象的列表,修改为返回单个Request请求对象即可)
参考链接
③ meta 为只读属性# request.meta = {"item": item} 这里不行!meta为只读属性!想法是给Requests的meta属性添加值,效果一样,都是为了传参用,但只能是在构造函数中 scrapy.Request(meta=) 才能用,其他地方不允许修改值
④ 导入 settings 失败
from bizhi import settings # 记得把根目录标记为: 源/根 后导入
原因: 这个报错的意思是:试图在顶级包(top-level package)之外进行相对导入。也就是说相对导入只适用于顶级包之内的模块
由于在"顶层模块"之外引用包,这里用到"顶层模块"的概念,“顶层模块” 是这执行文件同级的文件
from . import XXX 或者 from .. import XXX 时会遇到这样两个错误: SystemError: Parent module '' not loaded, cannot perform relative impor 和 ValueError: attempted relative import beyond top-level package
其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,package所对应的文件夹必须正确的被python解释器视作package,而不是普通文件夹。否则由于不被视作package,无法利用package之间的嵌套关系实现python中包的相对导入。
文件夹被python解释器视作package需要满足两个条件:
-
1、文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件
-
2、不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口 模块的__name__ 不能等于__main__)
很多时候就是导入和当前执行的py文件同级的package中的模块时报错: attempted relative import beyond top-level package
此时,该包作为顶层模块(和执行文件同级),已经不被视为一个package了,需要将他们的父级目录标记为源/根
补充:在"from YY import XX"这样的代码中,无论是XX还是YY,只要被python解释器视作package,就会首先调用该package的__init__.py文件。如果都是package,则调用顺序是YY,XX。
也就是说 你不能在一个x.py 文件中 执行 from .模块名 import * 同时运行 python x.py
另外,练习中“from . import XXX”和“from … import XXX”中的’.‘和’…’,可以等同于linux里的shell中’.‘和’…'的作用,表示当前工作目录的package和上一级的package。
Pycharm中的解决方案:把根目录标记为: 源/根 后导入(右键相应文件夹,选择 Mark Directory as Sources 即可)



