“fox_charon”是我自己折腾了大半年、前后重构过三轮的一个小项目。最初它只是针对 Firefox 的私用辅助脚本集合,封装之后慢慢变成一个带命令行交互的请求转发与数据采集工具。项目核心思路很简单:给 Firefox 加一个“摆渡人”,负责把浏览器内的请求安全地转发到指定服务端,同时在本地完成基本的过滤、染色和任务分发。这半年里我用它批量处理过不少重复性网页操作,也在开发调试里省下了大量复制粘贴的功夫。如果你经常要跟浏览器请求打交道、觉得手写测试脚本到处补环境很烦,这篇基于复盘整理的实战总结应该对你有用。我会把当时的设计思路、踩过的坑、改进过的细节代码和命令行用法一并写清楚,方便你直接照着改造出适合自己版本的“fox_charon”。
1. 项目整体设计与诉求拆解
1.1 为什么会有 fox_charon:核心需求与开发动机
最早有这个念头,不是因为它有多高级,而是我实在受够了每天在 Firefox 里打开开发工具、手动点开网络面板、复制请求头、再跑去终端里粘贴 curl 命令的生活。平时给内部业务做接口联调,或者在开源数据页面上抓些结构化字段时,重复动作实在太多了。刚开始我也只是想写一个扩展来减轻这类重复劳动,真正让我决定以独立项目方式来做,是因为发现纯扩展路线处理跨域调用和数据落盘非常别扭,而把请求转发到本地“中转站”,再用命令行工具统一调度,效率会高很多。
fox_charon 的名字含义很直接:fox 指 Firefox,charon 是摆渡人。它要完成的动作就是把从浏览器里发出的网络请求“摆渡”到开发者自己指定的目标上。实际运行时,你可以把它当成一个本地请求路由器,可以接收浏览器扩展发送来的任务,做格式归一化、缓存去重、失败重试,最后把结果交回命令行界面。
既然定位是偏个人效能开发的工具,我没有把代码写得过于重量级。整体架构采用浏览器扩展 + 本地代理进程 1+1 的模式,浏览器端只负责采集和发送,本地进程才负责核心业务逻辑。这种拆分有几个明显好处:
- 浏览器端和本地端数据结构分离,换浏览器时不会重写业务逻辑;
- 处理逻辑在 Node.js 或 Python 这类脚本环境里更容易调试;
- 可以脱离浏览器界面单独跑测试集。
方案选型上,最终本地端我用了 Python 3.10,原因是项目里包含了大量文本清洗和 JSON 字段归一化工作,Python 处理这类任务最快。浏览器端则使用 Firefox 的 WebExtensions API,通过原生消息接口和本地守护进程通信。通信协议用的是 JSON-RPC 风格的自定义简化版本,一条消息就是一个 JSON 对象,内部包含 action、payload、request_id 三个字段。
1.2 核心模块与架构选型解析
fox_charon 的模块不到十个,我按功能拆成四层:入口层、任务层、执行层、数据层。
入口层主要是命令行的参数解析和配置装载。我用的是 Python 标准库里的 argparse,没有引入 Click 或 Typer,因为项目早期阶段依赖越少越好,部署到新的 Linux 或 macOS 机器上时,不用先拉一堆包才能跑。配置文件则采用 TOML 格式,因为 Firefox 扩展本身和 Python 3.11 以上版本对 TOML 支持都比较好,不过我在 3.10 上调试时自己用 tomli 兼容读了一下。
任务层负责把浏览器扩展送来的请求包装成标准任务对象:
python复制@dataclass
class CharonTask:
request_id: str
url: str
method: str = "GET"
headers: dict = field(default_factory=dict)
body: str = ""
priority: int = 5
retries: int = 3
这样一个任务对象完整描述了一笔需要转发的请求,后续的调度、去重、结果回传都是围绕这个数据结构展开的。任务层里我还加了一个简单的优先级队列,优先级数字越小越先执行。实际用下来,批量任务里偶尔需要插队,这个字段救过我好几次。
执行层是 fox_charon 最核心的部分,我把它做成了一组 handler,每个 handler 负责一种请求动作。默认的 http_handler 处理最常见的 GET/POST 请求,download_handler 处理文件下载,wait_handler 则专门用来在任务流之间做延时。这种设计看着简单,但对任务编排特别重要。比如我需要先去页面 A 登录获取 Cookie,再带着 Cookie 请求页面 B,那么 wait_handler 能让我精确控制节奏,避免触发目标的限流策略。
数据层管理“出”和“入”两侧的数据。入方向,收到的响应体统一按 UTF-8 解码后做成 JSON 返回;出方向,支持把结果写到 JSONL、CSV 或者直接落 SQLite。我用 SQLite 作为默认存储,因为临时查询和二次过滤写 SQL 最方便。
架构定下来后,我给自己画了几条红线:浏览器端不碰业务逻辑、跨域问题不在扩展里解决、不把重试机制写死到扩展端。后续所有调试过程中的问题,几乎都指向这仨原则中的某一条,前置约束确实省了很多事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节与实操要点
2.1 浏览器扩展端:采集请求与消息通信的实现方式
Firefox 扩展的代码路径很简单,核心就是一个 background script,用来监听浏览器发出的请求,以及一个 content script,用来在页面里注入自定义按钮或劫持 DOM 事件。大部分情况下我们用 webRequest 监听就够了。
我监听的是 onBeforeSendHeaders 和 onCompleted 两个事件。为什么不用 onBeforeRequest?因为这个阶段拿不到完整的请求头,转发给本地服务时,目标端可能会因为缺少 User-Agent 或 Cookie 而拒绝,导致和浏览器请求不一致。所以必须在 onBeforeSendHeaders 阶段截取,这时候浏览器已经完成 DNS 解析和连接建立,请求头已经完整。
这里有一个小坑,Firefox 出于隐私考虑,对 certain sensitive headers 做了过滤。像 Cookie 和 Authorization 这类字段,在 webRequest 里默认是拿不到的。我在最开始测试时用 onBeforeSendHeaders 抓请求,复制出来的请求头总是比真实请求少几个字段,返回结果当然也对不上。
解决方案有两种:第一种,通过扩展配置申请 extraHeaders 权限;第二种,在 about:config 里调整网络安全相关的开关,让扩展能够读取敏感请求头。我路径选的是第一种,在 manifest.json 里加入:
json复制{
"permissions": ["webRequest", "webRequestBlocking", "storage", "nativeMessa
