Scrapy框架介绍
1. 基本介绍
官方文档:
整体架构大致如下:
Components:
1、引擎(EGINE)
引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。
2、调度器(SCHEDULER)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
3、下载器(DOWLOADER)
用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
4、爬虫(SPIDERS)
SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求
5、项目管道(ITEM PIPLINES)
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,
6、爬虫中间件(Spider Middlewares)
位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
2. 命令行工具
* 1 查看帮助
scrapy -h
scrapy <command> -h
* 2 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要
Global commands:
startproject #创建项目
genspider #创建爬虫程序
settings #如果是在项目目录下,则得到的是该项目的配置
runspider #运行一个独立的python文件,不必创建项目
shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否
fetch #独立于程单纯地爬取一个页面,可以拿到请求头
view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
Project-only commands:
crawl #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
check #检测项目中有无语法错误
list #列出项目中所包含的爬虫名
parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确
bench #scrapy bentch压力测试
* 3 官网链接
https://docs.scrapy.org/en/latest/topics/commands.html
3. 创建项目
scrapy startproject xxx
cd xxx
scrapy genspider xxxx xxx.com
目录
├── NewsPro
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ └── wangyi.py
└── scrapy.cfg
4. 文件说明
- scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
- items.py 设置数据存储模板,用于结构化数据,如:Django的Model
- pipelines 数据处理行为,如:一般结构化的数据持久化
- settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT=’xxxx’
- spiders 爬虫目录,如:创建文件,编写爬虫规则
5. 启动项目
scrapy crawl xxx
优雅的方法
在根目录下创建一个 bin.py 文件
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'xxx', "--nolog"])
6. settings 配置文件
1. 为什么项目中需要配置文件
在配置文件中存放一些公共变量,在后续的项目中方便修改,如:本地测试数据库和部署服务器的数据库不一致
2. 配置文件中的变量使用方法
- 变量名一般全部大写
- 导入即可使用
3. settings.py中的重点字段和含义
- USER_AGENT 设置ua
- ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守
- CONCURRENT_REQUESTS 设置并发请求的数量,默认是16个
- DOWNLOAD_DELAY 下载延迟,默认无延迟 (下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。这可以用来限制爬行速度,以避免对服务器造成太大影响)
- COOKIES_ENABLED 是否开启cookie,即每次请求带上前一次的cookie,默认是开启的
- DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用
- SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同
- DOWNLOADER_MIDDLEWARES 下载中间件
- LOG_LEVEL 控制终端输出信息的log级别,终端默认显示的是debug级别的log信息
- LOG_LEVEL = "WARNING"
- CRITICAL 严重
- ERROR 错误
- WARNING 警告
- INFO 消息
- DEBUG 调试
- LOG_FILE 设置log日志文件的保存路径,如果设置该参数,终端将不再显示信息
LOG_FILE = "./test.log"
- FEED_EXPORT_ENCODING='UTF-8' 保存的是unicode编码字符转为utf8
Spider类
Spiders是定义如何抓取某个站点(或一组站点)的类,包括如何执行爬行(即跟随链接)以及如何从其页面中提取结构化数据(即抓取项目)。换句话说,Spiders是您为特定站点(或者在某些情况下,一组站点)爬网和解析页面定义自定义行为的地方。
1、 生成初始的Requests来爬取第一个URLS,并且标识一个回调函数
第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,
默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发
2、 在回调函数中,解析response并且返回值
返回值可以4种:
包含解析数据的字典
Item对象
新的Request对象(新的Requests也需要指定一个回调函数)
或者是可迭代对象(包含Items或Request)
3、在回调函数中解析页面内容
通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsoup,lxml或其他你爱用啥用啥。
4、最后,针对返回的Items对象将会被持久化到数据库
通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html)
或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html)
Item
抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy爬虫可以像Python一样返回提取的数据。虽然方便和熟悉,但很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多爬虫的较大项目中。
为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集抓取数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。
1、声明项目
使用简单的类定义语法和Field 对象声明项。这是一个例子:
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
注意那些熟悉Django的人会注意到Scrapy Items被宣告类似于Django Models,除了Scrapy Items更简单,因为没有不同字段类型的概念。
Field对象用于指定每个字段的元数据。例如,last_updated上面示例中说明的字段的序列化函数。
您可以为每个字段指定任何类型的元数据。Field对象接受的值没有限制。出于同样的原因,没有所有可用元数据键的参考列表。
Field对象中定义的每个键可以由不同的组件使用,只有那些组件知道它。您也可以根据Field自己的需要定义和使用项目中的任何其他键。
Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。通常,行为取决于每个字段的那些组件使用某些字段键来配置该行为。
Item PipeLine
在一个项目被spider抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。
每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。
项目管道的典型用途是:
- cleansing HTML data
- validating scraped data (checking that the items contain certain fields)
- checking for duplicates (and dropping them)
- storing the scraped item in a database
1 编写自己的项目管道
'''
每个项管道组件都是一个必须实现以下方法的Python类:
process_item(self,项目,spider)
为每个项目管道组件调用此方法。process_item()
必须要么:返回带数据的dict,返回一个Item (或任何后代类)对象,返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。
此外,他们还可以实现以下方法:
open_spider(self,spdier)
打开蜘蛛时会调用此方法。
close_spider(self,spider)
当蜘蛛关闭时调用此方法。
from_crawler(cls,crawler )
如果存在,则调用此类方法以从a创建管道实例Crawler。它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,
如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。
'''
2 项目管道示例
(1) 价格验证和丢弃物品没有价格
让我们看看下面的假设管道,它调整 price
那些不包含增值税(price_excludes_vat
属性)的项目的属性,并删除那些不包含价格的项目:
from scrapy.exceptions import DropItem
class PricePipeline(object):
vat_factor = 1.15
def process_item(self, item, spider):
if item['price']:
if item['price_excludes_vat']:
item['price'] = item['price'] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)
(2) 将项目写入JSON文件
以下管道将所有已删除的项目(来自所有spider)存储到一个items.jl
文件中,每行包含一个以JSON格式序列化的项目:
注意JsonWriterPipeline的目的只是介绍如何编写项目管道。如果您确实要将所有已删除的项目存储到JSON文件中,则应使用Feed导出。
import json
class JsonWriterPipeline(object):
def open_spider(self, spider):
self.file = open('items.jl', 'w')
def close_spider(self, spider):
self.file.close()
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item
(3) 将项目写入数据库(网易新闻应用)
在这个例子中,我们将使用pymongo将项目写入MongoDB。MongoDB地址和数据库名称在Scrapy设置中指定; MongoDB集合以item类命名。
from scrapy.exceptions import DropItem
import pymongo
class NewsproPipeline:
def process_item(self, item, spider):
print("item:::", item)
if not item["content"]:
raise DropItem(f"{item['title']}content为空")
return item
class MongoPipeline(object):
mongo_db = "news"
collection_name = 'wangyiNews'
def __init__(self, mongo):
self.mongo = mongo
@classmethod
def from_crawler(cls, crawler):
# Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完成实例化
return cls(crawler.settings.get('MONGO'))
def open_spider(self, spider):
self.client = pymongo.MongoClient(host=self.mongo[0], port=self.mongo[1])
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.collection_name].insert_one(dict(item))
print(f"news数据库的{self.collection_name}表中插入{item['title']}记录")
return item
这个例子的要点是展示如何使用from_crawler()
方法以及如何正确地清理资源。
补充知识点(from_crawler):
当您使用 settings
对象获取全局变量时,可以在 Scrapy 配置文件中设置该变量的值,然后通过 settings.get('MY_SETTING')
方法来访问它。这样做很方便,但也有一些限制:
- 您无法根据特定的 Crawler 实例动态地调整全局变量的值。例如,当您在多个爬虫之间共享同一个 Scrapy 项目时,某些设置可能需要在每个爬虫之间进行微调。
- 您无法轻松地将其他参数传递给中间件。如果您想基于环境变量、命令行参数或其他外部因素配置中间件,那么使用
settings
对象可能很麻烦。
为了解决这些问题,Scrapy 引入了 from_crawler
方法。这个方法可以让您根据具体的 Crawler 实例创建中间件实例,并动态地向中间件传递其他参数。
具体来说,from_crawler
方法接收一个 crawler
参数,这是当前 Crawler 实例的引用。您可以使用 crawler.settings
访问 Scrapy 的全局配置变量,还可以使用 crawler.signals
注册信号处理程序,以及使用 crawler.engine
和 crawler.spider
访问其他 Scrapy 子系统和资源。
因此,在 from_crawler
中,您可以使用更多的 Scrapy 资源,并根据需要动态地配置中间件。这使得中间件更加灵活和可扩展,可以适应多种场景和需求。
(4) 重复过滤
一个过滤器,用于查找重复项目,并删除已处理的项目。假设我们的项目具有唯一ID,但我们的蜘蛛会返回具有相同ID的多个项目:
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item
3 激活项目管道组件
要激活Item Pipeline组件,必须将其类添加到settings.py
的 ITEM_PIPELINES
设置中,如下例所示:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
"NewsPro.pipelines.MongoPipeline": 500,
}
您在此设置中为类分配的整数值决定了它们运行的顺序:项目从较低值到较高值类进行。习惯上在0-1000范围内定义这些数字。
下载中间件
class MyDownMiddleware(object):
def process_request(self, request, spider):
"""
请求需要被下载时,经过所有下载器中间件的process_request调用
:param request:
:param spider:
:return:
None,继续后续中间件去下载;
Response对象,停止process_request的执行,开始执行process_response
Request对象,停止中间件的执行,将Request重新调度器
raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
"""
pass
def process_response(self, request, response, spider):
"""
spider处理完成,返回时调用
:param response:
:param result:
:param spider:
:return:
Response 对象:转交给其他中间件process_response
Request 对象:停止中间件,request会被重新调度下载
raise IgnoreRequest 异常:调用Request.errback
"""
print('response1')
return response
def process_exception(self, request, exception, spider):
"""
当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
:param response:
:param exception:
:param spider:
:return:
None:继续交给后续中间件处理异常;
Response对象:停止后续process_exception方法
Request对象:停止中间件,request将会被重新调用下载
"""
return None