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

boolTrue是否遵循 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

boolFalse是否采用严格模式限制下载器的并发行为

TIMEOUT

int30请求超时时间

MAX_REQ_TIMES

int2请求失败后的重试次数

DELAY_REQ_TIME

int3请求失败后进行重试的延时时间

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

boolFalse是否持久化存储,目前只有 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

Noneredis 数据库连接的数据库

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,附上测试代码方便复现。

posted @ 2025-09-09 16:09  yfceshi  阅读(14)  评论(0)    收藏  举报