Scrapy 嵌入 FastAPI 的坑:Asyncio/Twisted 桥接 + 代理池设计

简介: 因为功能需求与复杂度的增加,最近我把一个脚本爬虫重构成了`爬虫网关`。为了达到代码 “更干净” 的目地, 也是遇到了很多难以攻克的难题。

因为功能需求与复杂度的增加,最近我把一个脚本爬虫重构成了爬虫网关
为了达到代码 “更干净” 的目地, 也是遇到了很多难以攻克的难题。
从刚开始的一会儿 asyncio,一会儿 Twisted的凌乱,到给本爬虫服务添加一个代理池。也算是磕磕绊绊。

虽然项目还未重构完成,但是此时我正站在一个值得纪念的转折点上,
故而写本篇博客的目的就是,就是为了回望、记录、总结我的来时路。

  1. apicoreservicescrawler 到底各管什么
  2. raisereturn 什么时候用才不含糊
  3. proxy_service.py 实际上在做哪几件事
  4. 为什么项目里同时出现 asyncioTwisted,而我又是如何解决的。
  5. ASGI 是什么,它跟 FastAPI 是什么关系

以下是本博客的核心:
我遇到的问题:FastAPI(ASGI/asyncio) + Scrapy(Twisted) 同进程集成 + 代理池
我是如何解决的:职责分层 + runner 适配层 + asyncioreactor + Deferred→Future 桥接 + proxy 健康状态机

1. 我先把总图搭起来:进行职责分层

再学习scrapy框架的时候,我就常常在想,我到底该如何设计。
才可以再本地启动爬虫的时候,同时对外提供服务。

后来经过资料查询,发现这条链路其实很固定,而最常用的方式如下:

api -> services -> crawler

  • api:接 HTTP 请求,做参数校验,调用 service,最后把返回值包装成统一响应
  • core:配置、日志、错误体系、鉴权这类“所有人都要用”的底座
  • services:业务编排层(决定“这次要抓什么 / 抓到什么算成功”)
  • crawler:执行层(决定“用什么方式抓 / 失败怎么重试 / 代理怎么上报”)

我一开始读不懂,就是因为把“业务想要什么”和“爬虫怎么跑”同时塞进脑子里。
后来强行按层拆开:先只看 services 在拼什么,再去看 crawler 怎么实现,难度立刻下降。

用 Go 类比一下

如果你写过 Go 的 Web 项目,这条链路几乎就是:

apihandler(Gin/Echo 的 handler:接请求、绑定参数、回响应)

servicesservice(业务编排:调用多个组件,拼装结果)

crawlerworker/job runner(真正干活:跑任务、做 I/O、处理失败重试)

coreconfig/log/middleware/errors(统一配置、日志、鉴权、错误封装)


2. 为什么 raise 不能随便换成 return

在我的 luogu_service.py 里有这种写法:

for row in items:
    error = row.get("_error")
    if error:
        raise UpstreamRequestError(f"luogu practice: {error}")

因为带入了go语言的思想,我当时第一反应也是:return 不也能结束函数吗?

但这里差别不在“能不能结束”,而在上层会怎么理解你这次调用

  • return:更像“正常结束,只是结果是这个”
  • raise:明确告诉上层“这次调用失败了,不是正常结果的一种”

如果这里用 return,上层很可能把它当成“空数据”或者“正常结构但没抓到”。
raise UpstreamRequestError 的好处是,API 层能统一映射成 502,也能进统一日志/监控,语义更干净。

只要这件事不该被当成正常结果,就别 return,直接 raise。


3. proxy_service.py 并不是“代理工具函数集合”

我最初在设计这一模块的时候,只是想设计成最简单 增加/删除/更换 的模式。
但是想到了之前设计敏感词的灵感,便决定设计成代理健康管理中心

  1. 代理池维护sync_replace / remove / get_snapshot
  2. 健康状态机OK / SUSPECT / DEAD 这种分层不是为了好看,是为了避免“一次失败就踢掉”或者“死代理一直占坑”
  3. 按目标站点统计:不同站点(leetcode/luogu/lanqiao)分开记成功率、延迟,不然一个站点把代理打死会误伤所有站点
  4. 主动探活:后台循环定时测一遍代理是否还活着(让 DEAD 有机会回来)
  5. 被动更新:每次请求成功/失败,实时上报更新健康度(让 OK/SUSPECT/DEAD 动起来)

我觉得这里最关键的理解点是:

  • 策略主要在 proxy_service.py(怎么评估代理)
  • 触发点在 crawler/middlewares.py(什么时候上报:请求阶段、响应阶段、异常阶段)

也就是说:proxy_service.py 像“裁判”,middlewares 像“把球踢到裁判那的人”。


4. runner.py 为何我要这样设计

runner.py 表面上像一堆 Scrapy 启动代码,实际上它在做一件很具体的事:

把 Scrapy 封装成一个能 await 的服务接口。

它主要干了这些活:

  1. 单例化 ScrapyRunnerService(避免重复初始化 runner / settings)
  2. 创建 CrawlerRunner(settings=...)
  3. 运行指定 spider
  4. 监听 item_scraped 信号,把抓到的结果收起来
  5. 把超时和异常统一成项目语义(CrawlerTimeoutError / CrawlerExecutionError

一句话总结:
它不是“爬虫逻辑”,而是执行适配层——把 Scrapy 的执行模型,翻译成 services 层能直接调用的接口。


5. 当项目里同时有 asyncioTwisted 时,我该怎么办

为何我会遇到这种问题

我之所以会遇到这种问题,根因是“服务形态变了”:

  • 原生 Scrapy:独立爬虫进程(Twisted 自己玩)
  • 而我要对外提供http接口:Web API 网关(FastAPI/ASGI/asyncio)

当我把 Scrapy 嵌进 FastAPI 时,等于把两种并发模型塞进一个长驻进程里。
此时自然会撞上这些问题:

等待模型不同
1、FastAPI 习惯 await Future/Task
2、Scrapy 给你的是 Twisted Deferred

事件循环不同
1、asyncio 一套 loop
2、Twisted 一套 reactor

生命周期不同
1、API 服务要长期运行、可复用、可测试
2、Scrapy 命令行通常是一次性执行

超时/取消/异常语义要统一
我要把爬虫异常稳定映射成 HTTP 错误(502/504),必须先把底层执行语义对齐

所以我当时卡住的问题,本质上是“架构层跨生态集成”问题。
所以我使用的(reactor + deferred_to_future)的桥接,本质上就是把两种模型统一到一条可维护链路里。

问题点找到了,就容易解决了

后面想明白其实很朴素:

  • FastAPI 跑在 ASGI 的异步生态里,主流是 asyncio
  • Scrapy 底层用的是 Twisted
  • 两边都要跑,就得让它们能“共用一个事件循环”或者至少能互相配合

所以我的项目里做了桥接,最核心就是这一句:

install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")

再用 deferred_to_future(...) 把 Twisted 的 Deferred 转成 asyncio.Future,这样我才能在 FastAPI 的 async defawait 一个 Scrapy 执行结果。

我最初之所以会遇到这种问题
FastAPI 这边负责“接请求并发”,Scrapy 那边负责“跑爬虫 IO”,桥接层负责让两套异步语言能互相翻译。


6. ASGI 是啥

ASGI 可以当成 Python Web 的“异步接口标准”,也就是服务器跟框架怎么对话的一套协议。

  • uvicorn 是 ASGI server
  • FastAPI 是 ASGI app
  • 所以我能写 async def 路由,天然支持高并发 I/O

FastAPI 能异步,是因为它跑在 ASGI 这套协议上。

用 Go 类比一下:ASGI 有点像 Go 的 net/http 约定

在 Go 里,server 和框架之间的“约定”非常清晰:

handler 必须长得像 func(w http.ResponseWriter, r *http.Request)

框架(Gin/Echo)也是把它包装了一层,但最终都能落到这个约定上

7. 简单的记录一下本项目的执行顺序

  1. api/main.py(入口 + 异常映射)
  2. api/routers/*.py(接口分发与参数)
  3. services/*(业务编排:这次想抓什么)
  4. crawler/runner.py(执行入口:怎么把爬虫跑起来)
  5. crawler/spiders/* + parsers/*(具体抓取和解析)
  6. tests/*(用测试反证:我理解的对不对)

8. 结语

用go用习惯的我,在用python写代码时,
最让人崩溃的往往不是语法,而是“层次没拆开”。
一旦每层职责清楚,其实项目就已经清清楚楚了。

目录
相关文章
|
11天前
|
缓存 前端开发 JavaScript
首屏优化实践:如何将 Vue3 + Vite 项目的加载速度提升3倍
本篇博客,将会带着你,走一遍首屏优化实践。手把手给你演示,如何将 Vue3 + Vite 项目的加载速度提升3倍。
159 6
首屏优化实践:如何将 Vue3 + Vite 项目的加载速度提升3倍
|
11天前
|
网络安全 Go Docker
CI/CD全流程
记录 后端go 算法平台 / python 爬虫网关 / 前端vue项目 CI-CD部署流程
209 8
|
13天前
|
机器学习/深度学习 人工智能 编解码
抽烟行为检测数据集(约3000张图片已标注)| YOLO训练数据集 AI视觉检测
本数据集含约3000张多场景抽烟行为图像,YOLO格式标注(单类“smoke”),覆盖室内外、不同光照与人群姿态,支持YOLOv5/v8直接训练。适用于智慧安防、禁烟监管及AI行为识别研究,助力实时检测与自动告警。
抽烟行为检测数据集(约3000张图片已标注)| YOLO训练数据集 AI视觉检测
|
10天前
|
人工智能 弹性计算 自然语言处理
【手把手教你】阿里云OpenClaw部署实操教程,新手小白也能轻松搞定!
想拥有能自动执行任务、处理文件、联网搜索的AI助手?阿里云OpenClaw一键部署教程来了!全程可视化、零代码,10分钟轻松“养龙虾”——本地优先、支持多模型与IM接入,新手小白也能秒变AI玩家!
383 12
|
10天前
|
安全 Java 数据库连接
【反射】Java反射 全方位知识体系(附 应用场景 + 《八股文常考面试题》)
Java反射是运行时动态获取类元信息(构造器、方法、字段等)并操作对象的能力,核心为 Class对象。广泛应用于Spring、MyBatis等框架的IoC、AOP、ORM映射,以及注解处理、动态代理、SPI扩展等场景,兼具灵活性与解耦优势,但存在性能开销和安全风险。
156 10
|
13天前
|
Linux API 数据安全/隐私保护
OpenClaw跨平台协作指南|多端同步+阿里云/本地(Windows11/MacOS/Linux)部署+API配置实战指南
2026年,OpenClaw(Clawdbot)的跨平台协作能力已成为核心竞争力之一——用户不再局限于单一设备使用,通过多端同步机制,可在阿里云服务器、本地桌面设备(Windows11/MacOS/Linux)、移动终端之间实现配置同步、任务接续、数据共享,真正打破设备壁垒。这种“一处部署、多端可用”的协作模式,大幅提升了使用灵活性,适配移动办公、多场景切换等现代工作需求。
608 9
|
6天前
|
运维 Prometheus 监控
阿里云、本地部署OpenClaw 实现全维度监控运维指南:从基础监控到企业级告警体系搭建
OpenClaw 作为开源 AI 智能体执行网关,其稳定运行是自动化任务落地的核心前提。部署后的全维度监控并非单一指标追踪,而是覆盖「网关 - 智能体 - 技能 - 资源」四层架构的全链路管控,核心价值在于提前识别风险、定位故障根因、保障任务执行效率,避免因系统宕机、权限异常、资源耗尽导致业务中断。本文系统讲解 OpenClaw 监控维度、基础与进阶监控工具实操、故障排查方法,同时提供 2026 年阿里云及本地多系统部署流程、阿里云百炼免费大模型配置,所有命令可直接复制执行,助力个人与企业用户搭建稳定可控的运维体系。
574 1
|
10天前
|
IDE 安全 Shell
Agent Computer Interface 的终局,不会是 CLI
本文批判CLI-first范式,指出其本质缺陷在于将“发命令”误等同于“构建工作环境”。CLI仅提供静态快照,导致Agent需耗费大量推理资源在状态对齐与过期信息识别上。真正出路是构建带生命周期、可原地更新、能自动清理陈旧上下文的Agent App——即把IDE级工作空间嵌入Agent上下文,实现状态一致性与对象化操作。
146 3
|
23天前
|
人工智能 安全 Serverless
让 AI Agent 安全“跑”在云端:基于函数计算打造 Agent 代码沙箱
Agent 代码沙箱是保障 AI 智能体安全执行的核心基础设施。依托函数计算构建强隔离、有状态、低成本的 AI 运行时。
|
11天前
|
文字识别 NoSQL API
Go-Zero微服务实战:高并发场景下的学生认证系统设计与实现
在校园社交等垂直领域应用中,"学生身份认证"是构建信任体系的核心基石。本文将会基于 Go-Zero 微服务框架,详细拆解了一个生产级的学生认证系统实现。涵盖了 OCR 双通道故障转移、WebSocket 实时推送、事件驱动架构 (EDA)、敏感数据加密 以及 有限状态机(FSM) 的设计模式。
120 7