scrapy_cffi基础使用 - 指南
继scrapy_cffi:完全异步,scrapy框架的 asyncio + curl_cffi 重构_scrapy-asyncio-CSDN博客后写一篇基础使用,其实本质上是文档的基础使用部分的中文介绍,部分不常用的或者比较复杂的都不会在本文说明,本文只有基础用法,如果需要完整版还是得参考 github 项目文档。
1.构建项目
1.1 实际项目
目前 scrapy_cffi 版本 0.1.6,提供了 Spider 普通爬虫 和 RedisSpider 两种模式,暂未提供类似 scrapy 的 CrawlSpider。
通过命令行快速创建项目:
scrapy-cffi startproject
然后进入到目录下:
cd
生成普通爬虫:
scrapy-cffi genspider
生成 redis 爬虫:
scrapy-cffi genspider -r
1.2 参考 demo 项目
构建普通爬虫 demo:
scrapy-cffi demo
构建 redis 爬虫 demo:
scrapy-cffi demo -r
然后按照 readme.txt 说明,先启动服务器,再运行 runner.py
2.setting.py
在 scrapy 的设置中,全都是按照 key=value 形式设置的,这种配置很灵活,但是有时候容易写错,所以 scrapy_cffi 统一封装成类,通过类属性的方法,还能够实现代码提示,开发效率更高。可以参考 demo 项目,其实就是一个函数内先创建一个实例对象,然后对这个实例对象的属性进行修改,而在函数内部,你还可以进行条件分支判断:
scrapy_cffi 提供了 run_spider 和 run_all_spiders 两种模式启动程序,因此这个 settings 的配置允许了为每个爬虫单独实现,对于程序而且其实就是调用函数,并不像真正的配置文件一样只有各种属性。
2.1 SettingInfo
2.1.1 常规配置
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
MAX_GLOBAL_CONCURRENT_TASKS | int None | 300 | 全局并发锁,如果配置None不做限制,很可能触发操作系统 fd 上限报错,尤其是在 window 操作系统大多数都是只有512的情况下。而即使在 linux/mac 上,也更推荐配置全局并发上限,作为程序稳定性的第一道安全保障,防止操作系统资源耗尽。由于实际底层并不是一个socket刚好对应1个fd占用,更多时候可能是占用好几个fd,所以这个只能根据经验值配置上限。 |
ROBOTSTXT_OBEY | bool | True | 是否遵循 robot.txt 协议 |
2.1.2 请求配置
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
USER_AGENT | str | "scrapy_cffiBot" | 默认的 ua,当构造请求没有配置时自动设置 |
DEFAULT_HEADERS | dict | {} | 默认请求头,当构造请求没有配置时自动设置 |
DEFAULT_COOKIES | dict | {} | 默认cookies,当构造请求没有配置时自动设置 |
MAX_CONCURRENT_REQ | int None | None | 限制下载器的最大并发数,为None是不做限制,是全局并发锁下的二级锁 |
USE_STRICT_SEMAPHORE | bool | False | 是否采用严格模式限制下载器的并发行为 |
TIMEOUT | int | 30 | 请求超时时间 |
MAX_REQ_TIMES | int | 2 | 请求失败后的重试次数 |
DELAY_REQ_TIME | int | 3 | 请求失败后进行重试的延时时间 |
2.1.3 代理设置
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
PROXY_URL | str | None | 字符串url,自动转换配置 PROXIES |
PROXIES | dict | {} | {"http": PROXY_URL, "https": PROXY_URL} |
PROXIES_LIST | list | [] | 存放字符串 url 的列表,当没有配置PROXIES且配置此项时,采用这个列表的 url 进行随机代理 |
2.1.4 组件配置
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
SPIDERS_PATH | str | None | 配置爬虫的路径,如果不配置,默认找项目下的/spider/目录,启动所有爬虫。 如果配置了此项,在 run_spider 模式下,此项要求是模块导包路径。在 run_all_spiders 模式下,此项要求是爬虫文件夹所在的目录。 |
其他
SPIDER_INTERCEPTORS_PATH
DOWNLOAD_INTERCEPTORS_PATH
ITEM_PIPELINES_PATH
EXTENSIONS_PATH
都是组件路径的配置项,见章节 2.2
2.1.5 调度器配置
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
SCHEDULER | str | None | 采用的调度器,要求是调度器的模块路径 |
SCHEDULER_PERSIST | bool | False | 是否持久化存储,目前只有 redis 调度器有效 |
INCLUDE_HEADERS | list | [] | 存在所有请求头 key 的列表,用于调度器去重时包含 |
FILTER_KEY | str | "cffiFilter" | 去重的基础 key,目前只有 redis 调度器有效 |
DONT_FILTER | bool | False | 是否不去重,全局配置项,可以被构造请求对象的 dont_filter 覆盖 |
2.1.6 会话结束行为
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
WS_END_TAG | str | "websocket end" | websocket 连接结束时进行资源释放的标记,允许自定义,避免被特殊响应误操作。 |
RET_COOKIES | str | "ret_cookies" | 当数据传递到管道,标记了 session_end 的情况下,返回的 cookies 所存储的 key |
2.1.7 额外配置
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
JS_PATH | str bool | None | 框架内置的 self.ctx_dict,通过 pyexecjs 调用简单 js,此项提供 js 文件夹所在目录,复杂 js 不建议继承进来。 |
2.2 ComponentInfo
组件配置都是配置模块路径,scrapy_cffi 支持多种模式配置
1.str
"extensions.CustomExtension"
# => ["extensions.CustomExtension"]
2.list
比如提供 ["pipelines.CustomPipeline2", "pipelines.CustomPipeline1"],框架会直接使用,索引大小就是距离引擎的远近。
3.dict
与 scrapy 一致,key 是模块路径,value是距离引擎的远近,框架内部会自动转换为列表形式。
{
"interceptors.CustomDownloadInterceptor1": 300,
"interceptors.CustomDownloadInterceptor2": 200
}
# => ["interceptors.CustomDownloadInterceptor2", "interceptors.CustomDownloadInterceptor1"]
2.3 LogInfo
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
LOG_ENABLED | bool | True | 是否启用框架日志 |
LOG_WITH_STREAM | bool | True | 框架日志是否输出 |
LOG_LEVEL | str | "DEBUG" | 日志等级 |
LOG_FORMAT | str | "%(asctime)s [%(name)s] %(levelname)s: %(message)s" | 日志格式 |
LOG_DATEFORMAT | str | "%Y-%m-%d %H:%M:%S" | 日志时间格式 |
LOG_FILE | str | "" | 日志文件路径 |
LOG_ENCODING | str | "utf-8" | 日志编码方式 |
2.4 RedisInfo
scrapy_cffi 内置 redis 数据库的支持。
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
URL | str | None | redis 数据库的连接 url |
HOST | str | None | redis 数据库连接的域名 |
PORT | str int | None | redis 数据库连接的端口 |
DB | str int | None | redis 数据库连接的数据库 |
USERNAME | str | None | redis 数据库连接的用户名 |
PASSWORD | str | None | redis 数据库连接的密码 |
核心是 URL,其余配置项也只是框架内部生成这个 URL 字段。
当 URL 有效,框架内部会自动维护一个 redis 数据库的连接,通常在组件中是一个属性 redisManager,能够直接调用 redis.asyncio 相关 api 进行增删查改操作。
2.5 MysqlInfo
scrapy_cffi 内置 mysqlo 数据库的支持,事实上很多时候爬虫不会直接与 mysql 对接,所以 mysql 相关操作库不会随框架安装而一起安装,如果需要,自行手动安装:
pip install sqlalchemy[asyncio] aiomysql
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
DRIVER | str | "mysql+asyncmy" | mysql 连接的 driver 头,允许自定义使用其他头 |
数据库的配置其实都是通用的,所以其余属性与 RedisInfo 相同。
相关使用参考:scrapy_cffi/tests/test_mysql.py at main · aFunnyStrange/scrapy_cffi · GitHub
mysql 相关操作是比 redis、mongodb 复杂不少的,如果不熟悉 sqlalchemy[asyncio]、aiomysql 的相关使用,可以忽略此项。
2.6 MongodbInfo
scrapy_cffi 内置 mongdb 数据库的支持,这个也是个安装可选项,因此不会随框架安装一起安装,如果需要,自行手动安装:
pip install motor>=3.7.1
属性与 RedisInfo 相同。
相关使用参考:https://github.com/aFunnyStrange/scrapy_cffi/blob/main/tests/test_mongodb.py
通过 .collection() 传入一个集合名称,然后返回的对象可以直接调用 motor 相关 api 进行增删查改操作。
3.spider
爬虫拥有普通爬虫和 redis 爬虫两种,有一些公共的属性与方法。
3.1 属性
属性 | 说明 |
---|---|
name | 爬虫名字 |
robot_scheme | robot协议请求的协议方式,http/https |
allowed_domains | 允许爬取的域名(开始请求的url不会被过滤) |
settings | 就是章节 2.1 SettingInfo 的配置信息 |
run_py_dir | 程序允许所在的目录 |
session_id | 默认的 session_id |
ctx_dict | 存储了所有加载好 js 的对象,{文件名: 加载的对象} 格式 |
普通爬虫拥有 start_urls 属性,根据起始 url 列表发出请求。
redis 爬虫拥有 redis_key 属性,根据监听指定 redis 的 key 进行构造请求。
本质上都是起始 url,只不过形式不同。
3.2 方法
3.2.1 use_execjs
调用指定 js 的指定函数,传递参数后,获取执行结果
假设已有一个 js 文件 demo.js:
function count(a, b) {return a + b + 1;}
function rand() {return Math.random();}
在爬虫的代码中能够使用:
# 带有参数的形式
self.use_execjs(ctx_key="demo", funcname="count", params=(1, 2)) # => 4
# 没有参数的形式(params 传递一个元祖)
self.use_execjs(ctx_key="demo", funcname="rand", params=(,)) # => e.g. 0.04188...
3.3 爬虫产出类型
scrapy_cffi 支持爬虫产出多种类型,根据爬虫代码进行区分。
3.3.1 async def 模式
3.3.1.1 基础数据类型
Request, Item, Dict, BaseException、None
示例:
async def parse(self, response: Union[HttpResponse, WebsocketResponse]):
yield HttpRequest(...)
yield Item(...)
yield ValueError(...)
yield {...}
yield None
3.3.1.2 可迭代的数据类型
List, Tuple
async def parse(self, response):
return [HttpRequest(...), Item(...)]
3.3.1.3 异步生成器
types.AsyncGeneratorType, AsyncIterable
async def parse(self, response: Union[HttpResponse, WebsocketResponse]):
if ...:
yield HttpRequest(...)
yield WebsocketRequest(...)
async for req in self.create_req():
yield req
3.3.1.4 协程对象
types.CoroutineType
async def parse(self, response: Union[HttpResponse, WebsocketResponse]):
if ...:
return HttpRequest(...)
return None # 或者不写
3.3.2 def 模式
3.3.2.1 基础数据类型
与 async def 模式一致
3.3.2.2 可迭代的数据类型
与 async def 模式一致
3.3.2.3 生成器
def parse(self, response):
for req in self.create_req():
yield req
3.4 注意事项
在 scrapy_cffi 的设计中,采用会话 session 管理,如果 session_id 是空,采用框架内部维护的一个 session 发送请求,每一个 session 通过一个 session_id 进行标识,而每一个 session 又允许同时进行多个 websocket 操作,因此 websocket 也拥有自己的 websocket_id。这个配置本质上就是一个大字典。
因此在发送的所有请求里,都是通过这个 session_id 进行标识采用哪一个会话,而发送 websocket 请求则需要同时带上 session_id 与 websocket_id(连接请求只需要 session_id),
当采集到指定数据后,确定不在需要这个 session 了,可以在返回的 item 中配置 session_end=True,会返回最新的 cookie 配置到 item 中,配置的 key 由 SettiingInfo.RET_COOKIES 控制。
而对于 websocket 请求,由于他有别于 http 的特殊性,他是一次连接持续监听收发,当你确定监听完毕后,需要主动再发送一个请求,请求对象中配置 websocket_end=True 进行关闭,否则爬虫将持续监听永不退出,除非连接被关闭。
无论 websocket 还是 session 的关闭都由框架内部维护,只有真正的不可用且指定结束标记,才会被真正释放。可以参考 scrapy-cffi demo 所构建的项目执行效果。
3.5 其他
做过 websocket 项目的应该都知道,websocket 项目往往需要一个类,然后定义接收方法,发送方法,监听方法、连接,全局的状态维护等功能,这些在 scrapy_cffi 框架中都已经实现,在爬虫代码中,只需要专注于请求的构造,响应的解析即可,省去这些不必要的麻烦。
4.请求、响应
scrapy_cffi 是基于 curl_cffi 请求库的,所以属性其实就是 curl_cffi 发送请求的参数扩展,封装的对象。
如果你熟悉 requests 请求库,那么接触 curl_cffi 请求库并不难,两者的 api 非常相似,不做赘述,这里只描述框架需要所做的扩展功能。
4.1 请求属性
属性 | 说明 |
---|---|
session_id | 发送请求时所需要的会话 session |
meta | 字典,用于上下文相关参数的传递 |
dont_filter | 不去重,优先级高于全局的 dont_filter 设置 |
callback | 请求的回调函数 |
errback | 请求失败的回调函数 |
desc_text | 可以为请求对象定义一个方便追踪的描述,用于 tracer,debug |
no_proxy | 如果全局配置代理,但是某些指定请求不需要代理是,设为 True |
http 请求专有属性:
- method:字符串的请求方法
- data:请求体
- json:请求体是 json
websocket 请求专有属性:
- websocket_id:每个 websocket 连接的身份标识
- websocket_end:是否停止监听
- send_message:websocket 请求需要发送的消息,允许给定一个列表存放多条消息。
4.3 请求方法
4.3.1 Http
4.3.1.1 protobuf_encode
在 data 是 字典的情况下,对 data 进行编码,返回自身链式调用。protobuf 的使用,自行查阅 blackboxprotobuf 。
yield HttpRequest(
data={...},
).protobuf_encode({...})
4.3.1.2 grpc_encode
grpc 本质上是 protobuf 的扩展
单条消息的情况下编码,data 还是直接传给请求对象,编码的定义传给方法。
yield HttpRequest(
data={...},
).grpc_encode(typedef_or_stream={...}, is_gzip=False)
grpc流式传输情况下的编码,data 不再有意义,通过方法的 typedef_or_stream 中一一对应传入。
yield HttpRequest(
data=None,
).grpc_encode(
typedef_or_stream=[
(segment_data1, typedef1),
(segment_data2, typedef2),
(segment_data3, typedef3),
...
],
is_gzip=False
)
4.3.2 WebSocket
与 http 行为一致,只不过属性是 send_message。
虽然 websocket 请求对象的特殊性,允许一个请求发送多条消息,但是框架内部不提供编码多条流式 grpc 的接口,因为这种复杂对象的情况下,单独定义函数自己构造完毕后直接传递给 send_message ,不管是爬虫代码还是框架内部都会更好维护,而不是一大堆无关请求对象的参数堆在一起。
4.3 响应属性
属性 | 说明 |
---|---|
session_id | 发送请求时所需要的会话 session |
meta | 字典,用于上下文相关参数的传递 |
raw_response | curl_cffi 请求库的原始响应对象 |
desc_text | 可以为请求对象定义一个方便追踪的描述,用于 tracer,debug |
request | 构造的请求对象 |
http 响应专有属性:
- status_code:响应状态码
- content:响应字节内容
- text:响应文本
websocket 响应专有属性:
- websocket_id:每个 websocket 连接的身份标识
- msg:接收到的消息内容
4.4 响应方法
4.4.1 Http
4.4.1.1 xpath(query)
4.4.1.2 css(query)
4.4.1.3 re(query)
是与 scrapy 同款的解析器
示例1:
Main Title
Link 1
Link 2
async def parse(self, response: HttpResponse):
print(response.css("h1::text").get()) # => Main Title
print(response.css("a::text").getall()) # => ['Link 1', 'Link 2']
print(response.css("a::attr(href)").getall()) # => ['/link1', '/link2']
print(response.xpath("//h1/text()").get()) # => Main Title
print(response.xpath("//ul/li/a/text()").getall()) # => ['Link 1', 'Link 2']
示例2:
$123.45
$67.89
async def parse(self, response: HttpResponse):
print(response.css("div.price::text").re(r"\$(\d+\.\d+)")) # => ['123.45', '67.89']
print(response.css("div.price::text").re_first(r"\$(\d+\.\d+)")) # => '123.45'
4.4.1.4 json()
其实就是 curl_cffi 请求库所响应的 .json() 方法,这里提供简短路径调用
4.4.1.5 extract_json
基于正则的 json 导出,对原始是 text / json 都支持(本质只对 text 文本进行提取)。
入参:
key:字符串,需要从响应文本中导入的 key
re_rule:正则规则,其实就是单纯采用 re/regex 模块直接匹配,避免了在爬虫代码导入一堆解析函数
返回值(匹配不到是空列表):
List[Union[Dict, str]],匹配到有多个结果,采用列表包裹
Dict,匹配到单个结果,值是个字典
str,匹配到单个结果,值是字符串
示例:
...
{
"a": 1,
"b": "2",
"c": [0, "3", {"_a": 4, "_b": "5"}],
"d": {"d0": 6, "d1": "7"}
}
{
"a": {"d0": 14, "d2": "15"},
"e": 8,
"f": "9",
"g": [10, "11", {"_a": 12, "_b": "13"}]
}
async def parse(self, response: HttpResponse):
print(response.extract_json(key="a")) # => [{'d0': 14, 'd2': '15'}, 1]
print(response.extract_json(key="_a")) # => [4, 12]
print(response.extract_json(key="c")) # => [[[0, '3', {'_a': 4, '_b': '5'}]]
print(response.extract_json(key="e")) # => 8
4.4.1.6 extract_json_strong
json 特攻,有些时候,响应的内容是个纯文本,而且非常恶心的将需要的 json 数据混合到了乱七八糟的文本中,甚至嵌套层数非常多的情况,导致层层提取 key 也困难,此方法对 extract_json 实现了增强。
拥有与 extract_json 相同的入参,同时还有额外可配置参数 strict_level (整型int,严格程度参数2、1、0)
- strict_level = 2,采用 orjson 解析,速度最快,但要求 json 格式最规范
- strict_level = 1,采用原始 json 解析,速度较快,要求 json 格式规范程度中等
- strict_level = 0,采用原始 json(额外配套 json5)解析,速度最慢,当正常 json 提取不了,会额外采用 json5 解析一次。避免完全采用 json5 也是因为实测有些复杂文本,采用 orjson / json 很快的情况下,全 json5 可以慢了几百倍。所以正常 json 能够解析的情况下,避免采用了 json5 提高性能。
返回值与 extract_json 一致。
示例:
...
"{"
{
"a": 1,
"b": "2",
"c": [0, "3", {"_a": 4, "_b": "5"}],
"d": {"d0": 6, "d1": "7"},
"level1": {
"raw": "{\\"key\\": {\\"deep\\": \\"value\\"}}"
}
}
"{"
{
"a": {"d0": 14, "d2": "15"},
"e": 8,
"f": "9",
"g": [10, "11", {"_a": 12, "_b": "13"}],
"logs": [
"{\\"event\\": \\"click\\", \\"meta\\": {\\"target\\": \\"button\\"}}",
"{\\"event\\": \\"scroll\\", \\"meta\\": {\\"target\\": \\"window\\"}}"
]
}
{
"h": {"d0": 16, "d2": "17"}, // no quotes!
"e": 18,
"i": "19,
"j": [20, "21", {"_a": 22, "_b": "23"}],
"logs": [
"{\\"event\\": \\"click\\", \\"meta\\": {\\"target\\": \\"button\\"}}",
"{\\"event\\": \\"scroll\\", \\"meta\\": {\\"target\\": \\"window\\"}}"
]
}
"}"
{
"k": {"d0": 24, "d2": "25"},
"l": 26,
"m": "27,
"n": [28, "29", {"_a": 30, "_b": "31"}],
"o": '{bad: "json"}',
"}"
async def parse(self, response: HttpResponse):
# extract_json
print(response.extract_json(key="a")) # => [{'d0': 14, 'd2': '15'}, 1]
print(response.extract_json(key="_a")) # => [4, 12, 22, 30]
print(response.extract_json(key="c")) # => [0, '3', {'_a': 4, '_b': '5'}]
print(response.extract_json(key="e")) # => [8, 18]
print(response.extract_json(key="raw")) # => []
print(response.extract_json(key="key")) # => []
print(response.extract_json(key="deep")) # => []
print(response.extract_json(key="event")) # => []
print(response.extract_json(key="target")) # => []
# extract_json_strong
print(response.extract_json_strong(key="a")) # => [1, {'d0': 14, 'd2': '15'}]
print(response.extract_json_strong(key="_a")) # => [4, 12, 22, 30]
print(response.extract_json_strong(key="c")) # => [0, '3', {'_a': 4, '_b': '5'}]
print(response.extract_json_strong(key="e")) # => 8
print(response.extract_json_strong(key="raw")) # => {"key": {"deep": "value"}}
print(response.extract_json_strong(key="key")) # => {'deep': 'value'}
print(response.extract_json_strong(key="deep")) # => value
print(response.extract_json_strong(key="event")) # => ['click', 'scroll']
print(response.extract_json_strong(key="target")) # => ['button', 'window']
- 为什么这个示例中 key="e"时 "18" 没了?
- 因为后面 '"i": "19'这里少了一个引号,所以这一段都不是合法的 json 块,对于 extract_json 而言,不会进行是否合法的判断,就是采用的正则匹配,所以才会出错。
- 事实上,很多时候其实也不会有这种复杂情况,extract_json_strong 只是实现了一种更强大的 json 解析功能,支持深层次的解析提取 json。常规解析如果采用 extract_json 即可提取,那么肯定是效率更高的。对于非 json 文本,extract_json_strong 也是无法解析的。
4.4.1.7 protobuf_decode
对响应内容的 content 进行 protobuf 解码,返回元祖 (data, typedef),没有入参。
4.4.1.8 grpc_decode
对响应内容的 content 进行 grpc 解码。
如果目标响应是单条消息,返回元祖 (data, typedef)
如果目标响应是多条消息,返回列表,里面是元祖 (data, typedef)
没有入参。
4.4.2 WebSocket
拥有 protobuf_decode、grpc_decode 两种方法,行为与 http 一致,只不过操作对象是 msg 消息内容。
5.Intercepor
拦截器行为,对应 scrapy 的中间件
- 公共属性 self.settings,就是 SettingInfo
5.1 下载器拦截器
与 scrapy 不同,scrapy_cffi 采用链式实现,同时为了方便理解而不是区分各种情况,返回值基本上都是统一的允许多种类型。
5.1.1 request_intercept
入参:请求对象 request 、爬虫对象 spider
返回值:
- 请求对象:重新交给调度器调度
- 响应对象:交给 response_intercept 处理
- None:跳过当前拦截器,交给下一个拦截器处理,直到交给下载器
- 异常:交给 exception_intercept 处理
5.1.2 response_intercept
入参:请求对象 request 、响应对象 response 、 爬虫对象 spider
返回值(至少返回一个响应对象):
- 请求对象:重新交给调度器调度
- 响应对象:将当前的最新响应对象交给下一个拦截器处理,直到交给爬虫拦截器/爬虫
- 异常:交给 exception_intercept 处理
5.1.3 exception_intercept
入参:请求对象 request 、异常对象 exception 、爬虫对象 spider
返回值:
- 请求对象:重新交给调度器调度
- 响应对象:给到 response_intercept 调度后返还爬虫
- None:跳过当前拦截器,交给下一个拦截器处理,直到交给爬虫的 errback 回调(如果没有,异常将被丢弃)
- 异常:将当前的最新异常对象交给下一个拦截器处理,直到交给爬虫的 errback 回调(如果没有,异常将被丢弃)
5.2 爬虫拦截器
5.2.1 process_spider_input
入参:响应对象 response 、爬虫对象 spider
返回值:
- None:跳过当前拦截器,交给下一个拦截器处理,直接交给爬虫的 callback 回调(如果没有,任务处理完毕结束)
- 异常:交给爬虫的 errback 回调(如果没有,异常将被丢弃)
5.2.2 process_spider_output
入参:响应对象 response 、爬虫产出对象 result 、爬虫对象 spider
返回值(公共支持所有 章节 3.3 爬虫产出类型,当接收到每一个拦截器对应定义后,会马上在框架内部拆分成下面单个独立对象):
- 请求对象:交给下一个拦截器处理,直到交给调度器
- Item / 字典 dict:交给下一个拦截器处理,直到交给管道
- 响应对象:给到 response_intercept 调度后返还爬虫
- None:跳过当前拦截器,交给下一个拦截器处理,直到没有拦截器被忽略
- 异常:交给 process_spider_exception 处理
5.2.3 process_spider_exception
入参:响应对象 response 、异常对象 exception 、爬虫对象 spider
返回值:
- 请求对象:重新交给调度器调度
- Item / 字典 dict:交给管道
- None:跳过当前拦截器,交给下一个拦截器处理,直到最后被忽略
- 异常:会被忽略
6.pipeline
6.1 属性
属性 | 说明 |
---|---|
settings | 就是 SettingInfo |
logger | 日志对象,按照 SettingInfo.LogInfo 配置 |
redisManager | redis 连接对象,redis.asyncio 的原生方法 |
mysqlManager | mysql 管理对象,基于sqlalchemy[asyncio] 与 aiomysql |
mongodbManager | mongodb 管理对象,拥有 motor 的原生方法,进行了适当封装 |
6.2 方法
6.2.1 open_spider
爬虫启动最先执行的方法,一般用于数据库建立连接。如果不采用框架提供的3种数据库,可自行配置。
- 入参:爬虫对象 spider
6.2.2 process_item
对爬虫传过来的 item 进行处理。
- 入参:item 对象(Item 或 字典)、爬虫对象 spider
- 返回值:item
6.2.3 close_spider
爬虫结束时执行的方法,一般用于数据库断开连接。如果不采用框架提供的3种数据库,可自行配置。
- 入参:爬虫对象 spider
7.程序启动
scrapy_cffi 提供了 run_spider 和 run_all_spiders 两种模式,框架配套提供的 runner.py 即启动入口,4种启动方式,同步版本是框架内部的封装,推荐上层提供一个干净线程调用。异步版本是原生 api,允许进行更深层次的使用,但是需要注意跨 loop 风险。
8.进阶用法
当了解完上面所有操作,基本上能够覆盖大部分情况下使用了,如果需要深入研究,可以深入项目源码:GitHub - aFunnyStrange/scrapy_cffi: An asyncio-style web scraping framework inspired by Scrapy, powered by curl_cffi, supported Http/Websocekt.
9.生态
推荐个人项目使用,简单生产环境可以尝试,当发现 bug 欢迎到 github 提 issues,附上测试代码方便复现。