Scrapy中应拆分为独立Pipeline类分别处理MySQL和Elasticsearch写入,避免单个process_item混用两种逻辑;MySQL用MysqlPipeline配adbapi+pymysql异步写入,ES用EsPipeline调bulk批量提交;各Pipeline通过open_spider初始化、close_spider清理资源,连接不共享以避免冲突。

Scrapy Pipeline 怎么同时写入 MySQL 和 Elasticsearch?
不能靠一个 process_item 方法硬塞两种数据库逻辑。Scrapy 的 Pipeline 是串行执行的,每个 item 会依次过所有启用的 pipeline,所以正确做法是拆成多个独立 pipeline 类,各自负责一种目标。
常见错误是把 MySQL 连接和 Elasticsearch 客户端全塞进同一个类里,在 process_item 里既 cursor.execute() 又调 es.index() —— 一旦任一环节失败(比如 ES 拒绝连接),整个 item 就卡住或丢弃,MySQL 那边也跟着回滚不了。
- 为 MySQL 单独写一个
MysqlPipeline,只管建连接、插数据、commit()或用close_spider清理 - 为 Elasticsearch 单独写一个
EsPipeline,用elasticsearch-py的bulk()批量写入,别用单条index() - 在
settings.py中按需启用,顺序无关紧要:ITEM_PIPELINES = {'myproject.pipelines.MysqlPipeline': 300, 'myproject.pipelines.EsPipeline': 400}
异步写 MySQL 时为什么 adbapi 要配 pymysql 而不是 MySQLdb?
MySQLdb 是 C 扩展,不支持 Twisted 的异步事件循环;pymysql 是纯 Python 实现,能被 adbapi 正确包装成 Deferred。用错会导致 process_item 看似执行了但实际没入库,且无报错 —— 因为 runInteraction 返回的是 Deferred 对象,你没 yield 或 await 它,Twisted 就不会调度。
示例中容易漏的关键点:
-
from_settings类方法里必须传"pymysql"字符串给adbapi.ConnectionPool()第一个参数,不是"MySQLdb" -
do_insert方法签名必须是(self, cursor, item),第一个参数是游标,不是连接 - 别在
do_insert里手动commit()——adbapi会自动 commit 成功事务,rollback 失败事务
多数据库场景下 open_spider 和 close_spider 怎么避免资源冲突?
每个 pipeline 实例生命周期独立,open_spider 不代表“整个爬虫启动”,而是“该 pipeline 被某个 spider 调用时触发”。如果你在两个 pipeline 里都写 self.es = Elasticsearch(...),它们互不影响;但若共用全局变量(比如 ES_CLIENT),就可能被后启动的 pipeline 覆盖前者的连接配置。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
更隐蔽的问题是连接池复用:MySQL 和 Elasticsearch 都有连接池机制,但 Scrapy 默认不帮你跨 pipeline 共享。想复用就得显式提取,比如在 from_crawler 里从 crawler.settings 拿配置,或用模块级单例(慎用,多 spider 并发时可能混用)。
- 推荐方式:每个 pipeline 自己管理连接,靠
open_spider初始化,close_spider关闭 - 如果真要共享(例如多个 pipeline 都要写同一套 ES 集群),把客户端实例挂到
crawler属性上:crawler.es_client = Elasticsearch(...),再在各 pipeline 的from_crawler里取 - 别在
__init__里初始化连接 —— 此时crawler还没注入,拿不到 settings,也容易在单元测试时提前触发
字段映射不一致时 Pipeline 怎么安全跳过脏数据?
MySQL 表字段是 title VARCHAR(200),但 item 里 item["title"] 是个嵌套字典或 None,直接插会报 TypeError 或 IntegrityError。Pipeline 默认不处理这类异常,一旦出错整个 item 就被丢弃,且后续 pipeline 不再执行。
安全做法是在 process_item 内做最小化校验,而不是依赖数据库报错回滚:
- 用
item.get("title", "").strip()替代item["title"],防KeyError和空值 - 对长文本字段(如
content)做长度截断:item["content"][:65535],避免超 MySQLTEXT限制 - 数字字段强制转类型:
int(item.get("price", 0)),别让字符串 "N/A" 进数据库 - 抛
DropItem("invalid title")显式丢弃,比让 pipeline 崩溃更可控
真正难缠的是跨库字段语义差异 —— 比如 MySQL 存 created_at DATETIME,ES 却要 @timestamp 字段且格式严格。这种必须在对应 pipeline 内部转换,没法统一抽象。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










