在 macOS 上用 venv 干净地安装 Camoufox:绕开 PEP 668 的正确姿势

发布于 2026年06月24日 23:42 #CLI

在 macOS 上用 venv 干净地安装 Camoufox:绕开 PEP 668 的正确姿势 封面图
  • PEP 668 强制要求使用虚拟环境安装 Python 包,避免污染系统环境
  • 创建虚拟环境命令为 python3 -m venv .venv,然后调用 .venv/bin/pip 安装依赖
  • 安装 Camoufox 后需运行 .venv/bin/camoufox fetch 下载浏览器二进制和 GeoIP 数据库
  • 使用 Camoufox 时注意 goto 后需等待页面加载,异步 API 必须在协程函数内使用

最近想试一下 Camoufox——一个基于 Firefox 的反检测浏览器,专为爬虫和 AI Agent 设计,每次启动都会生成一份新的浏览器指纹。听起来很美好,但在 macOS 上第一次安装就撞了一堵墙:pip install 直接被拒绝。

这篇文章记录完整的安装过程,并把每一步命令拆开讲清楚。Camoufox 只是引子,真正想讲的是 macOS 上 Python 包管理的标准动作

一、问题:externally-managed-environment

按官方文档的推荐姿势,安装命令应该是这样:

pip install -U camoufox[geoip]

但在最新版 macOS(或任何使用 Homebrew Python 的系统)上,你会直接拿到这样一个报错:

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to install.
    ...

这不是 Camoufox 的锅,而是 PEP 668 在拦你。从 2023 年开始,所有由系统包管理器(Homebrew、apt 等)安装的 Python 都会带一个 EXTERNALLY-MANAGED 标记,告诉 pip:「这个 Python 是操作系统管的,你别往里乱装东西,免得污染系统环境导致系统工具坏掉。」

报错信息本身会给你三条出路:

  1. brew install xyz——只对 Homebrew 已经收录的包有效,第三方库基本没戏
  2. --break-system-packages——硬上,不推荐,等于自废武功
  3. 使用虚拟环境(venv 或 pipx)——这就是正解

二、解决方案:python3 -m venv

虚拟环境的思路很简单:在每个项目目录下复制一份独立的 Python 沙盒,所有依赖都装在这个沙盒里,系统 Python 保持纯净。Python 3.3 以后,venv 已经是标准库的一部分,开箱即用,不需要额外装任何东西。

完整命令

python3 -m venv .venv && .venv/bin/pip install -U pip 'camoufox[geoip]'

这条命令用 && 串了两段,含义逐段拆解如下。

第一段:python3 -m venv .venv

  • python3 -m venv —— 通过 -m 加载 Python 标准库的 venv 模块来创建虚拟环境(而不是调用某个独立的 venv 命令,这是 Python 工具链的通用模式)
  • .venv —— 虚拟环境的目录名。.venv 是社区约定俗成的名字,前面带点表示隐藏目录,VS Code、PyCharm 等编辑器会自动识别并激活

执行完后,当前目录会出现一个 .venv/ 文件夹,结构大致如下:

.venv/
├── bin/         # python、pip、activate 等可执行文件
├── lib/         # 独立的 site-packages,所有依赖都装在这里
└── pyvenv.cfg   # 标识这是一个虚拟环境

第二段:.venv/bin/pip install -U pip 'camoufox[geoip]'

  • .venv/bin/pip —— 直接调用虚拟环境里的 pip,不依赖 source activate,更稳妥,也方便在脚本和 CI 里使用
  • install —— 安装包
  • -U —— --upgrade 的简写,已装则升级到最新版
  • pip —— 先升级 pip 自己,避免后续装包时遇到旧版 pip 的兼容性问题
  • 'camoufox[geoip]' —— 装 camoufox 包,并启用名为 geoipextra(可选附加依赖)

这里的方括号语法对应 pyproject.toml 里的 [project.optional-dependencies]。对 Camoufox 来说,geoip extra 会额外装上 geoip2,配合代理使用时可以根据 IP 自动匹配地理位置指纹,强烈建议带上。

为什么要加单引号? 因为 zsh 会把 [geoip] 当作通配符去匹配文件名,导致 no matches found。用单引号包起来,shell 就会原样传给 pip。Bash 用户不加引号也没事,但加了更保险。

为什么用 && 而不是 ;

&& 表示前一条成功才执行后一条。如果 venv 创建失败(比如目录已存在或权限问题),就不会去执行 pip,避免污染系统环境。; 则是不管成败都执行。除非你明确想忽略错误,否则一律用 &&

三、下载浏览器二进制:camoufox fetch

pip 装好的只是 Python 绑定,Camoufox 真正的浏览器二进制(一个魔改版 Firefox)需要单独下载:

.venv/bin/camoufox fetch

这一步是首次运行的硬性要求,缺它 Camoufox 会因为找不到浏览器而启动失败。下载内容包括两部分:

  • 浏览器本体——大概几十 MB 的压缩包,解压后放在 ~/.cache/camoufox(macOS 默认路径)
  • GeoIP 数据库——66 MB 左右的 MaxMind 数据,让 geoip extra 真正能用

下载过程有进度条,耐心等就行。以后 Camoufox 版本更新时,这条命令也会负责增量更新。

四、运行:venv 内 vs venv 外

接下来就可以跑了。两种姿势:

方式 A:直接用虚拟环境的 Python

.venv/bin/python test1.py

最直接,不依赖 shell 状态,适合脚本和 CI。

方式 B:激活虚拟环境

source .venv/bin/activate
python test1.py
deactivate   # 用完退出

激活后 pythonpip 都会指向虚拟环境内的版本,命令行提示符前会显示 (.venv)。适合长时间开发。

五、Camoufox 的几个常见坑

环境装好后,写代码时还有几个坑容易踩。这里一并记下。

坑 1:goto 之后没有等待,浏览器瞬间关闭

下面这段代码看起来没毛病:

from camoufox.sync_api import Camoufox

with Camoufox() as browser:
    page = browser.new_page()
    page.goto("https://mp.weixin.qq.com/s/xxxxx")

with 块一退出,浏览器立即被关闭。你什么都看不到,也会怀疑是不是装错了。解决方式是加一行等待:

page.wait_for_load_state("networkidle")  # 等网络空闲
# 或调试时直接显示窗口
with Camoufox(head=False) as browser:
    ...

坑 2:async with 写在顶层会 SyntaxError

Camoufox 提供了同步和异步两套 API。异步版本写起来很自然,但下面这样会直接报语法错误:

from camoufox.async_api import AsyncCamoufox

async with AsyncCamoufox() as browser:   # SyntaxError
    page = await browser.new_page()
    await page.goto("https://example.com")

原因是 async with 必须在协程函数内部使用,不能写在模块顶层。正确写法是包一层 async def,然后用 asyncio.run 驱动:

import asyncio
from camoufox.async_api import AsyncCamoufox


async def main() -> None:
    async with AsyncCamoufox() as browser:
        page = await browser.new_page()
        await page.goto("https://mp.weixin.qq.com/s/xxxxx")
        await page.wait_for_load_state("networkidle")
        print("title:", await page.title())


if __name__ == "__main__":
    asyncio.run(main())

坑 3:Python 版本太新

Camoufox 依赖的 Playwright、numpy、lxml 都需要预编译的 wheel。如果你用的是刚发布不久的 Python 版本(比如 Python 3.14 刚出那阵子),可能某些依赖还没有对应的 wheel,pip 会回退到本地编译,缺工具链时直接失败。出问题前先确认一下 Python 版本是否在 Camoufox 官方支持的范围内。

六、一些延伸:venv vs pipx vs uv

虚拟环境这条路不止 venv 一种选择,按场景挑工具:

工具适用场景特点
python3 -m venv项目级依赖标准库自带,零额外依赖
pipx安装独立的 CLI 工具每个工具一个隔离环境,全局可调用
uv新兴的全功能工具Rust 写的,速度比 pip 快 10–100 倍,集 venv + pip + pipx 于一体

如果你只是想用 Camoufox 写爬虫脚本,venv 完全够用。如果你已经在用 uv,等价的命令更短:

uv venv
uv pip install 'camoufox[geoip]'

结语

PEP 668 不是 Python 在刁难人,而是社区在补 20 年来「pip 全局装包搞坏系统」的老账。venv 也就三五行命令的事,但第一次撞墙确实容易卡住。把流程走通一遍之后,这套隔离思路会一直受用——不止 Camoufox,未来装任何 Python 项目都是同一套动作。

Camoufox 本身的故事也值得一读。它是一个 Firefox fork,专门针对指纹检测做了大量改造(Canvas、WebGL、字体、屏幕分辨率等等),配合 Playwright 的 API 用起来非常顺手。下次再单独写一篇讲它的实战用法。

参考资料

评论互动

© 2026 王若风的技术博客 · Powered by Astro