Skip to content

Browser Rendering

Hades edited this page Aug 23, 2026 · 6 revisions

浏览器渲染

当页面内容要 JS 跑完才有时,普通 HTTP 请求拿到的只是一个空壳。浏览器适配器会真的起一个 浏览器、加载页面、等渲染完成,然后把渲染后的 DOM 当作响应体返回。

resp = d.get("https://spa.example.com", adapter="browser")
print(resp.text)          # 渲染后的 HTML,不是初始 HTML

四个引擎怎么选

引擎 内核 反检测 浏览器本体 什么时候用
camoufox Firefox 最彻底(改的是内核层) 自己那份 Firefox,python -m camoufox fetch 单独下,约 1 GB Linux/macOS 默认;对方检测严
patchright Chromium 打过补丁的 Playwright patchright install chromium 要 Chromium 又要反检测
playwright 任意三选一 playwright install chromium 行为最可预期;自家页面、调试
DrissionPage Chromium 中等(CDP 直连) 用本机已装的 Chrome Windows 默认;不想再下浏览器

engine = "auto"(默认)按平台选:Windows → DrissionPage,其余 → camoufox

camoufox 最吃内存。 它是 Firefox 加一整套扩展,单个 context 的开销明显高于 Chromium 系。≤4 GB 内存的机器上把 max_pages 设成 1~2,否则会开始换页—— 请求从几秒变几分钟,看起来完全像卡死。

两步安装

pip install 只给 Python 包。浏览器本体要单独准备,详见安装

漏了第二步的话,请求会直接失败并告诉你该跑哪条命令:

FAILED_PRECONDITION: 浏览器引擎 'camoufox' 不可用(包已装,浏览器本体未就绪):
~/.cache/camoufox/... 不存在,需要 python -m camoufox fetch。
安装:pip install "ipclick[camoufox]" && python -m camoufox fetch

不会自动下载——那会让第一个请求卡着下 1 GB 然后超时,而下载还在后台继续跑。

配置

[BROWSER]
enabled = true                 # 总开关。关掉后即使装了引擎也拒绝浏览器请求
engine = "auto"
browser = "chromium"           # 只对 playwright / patchright 有意义
headless = true
executable_path = ""           # 指向系统浏览器可省掉 150 MB 下载
args = []
no_sandbox = false             # 容器里通常要打开
user_agent = ""                # 留空则每次随机。camoufox 会忽略这一项
viewport = { width = 1920, height = 1080 }   # 同上,camoufox 忽略
wait_until = "networkidle"     # load / domcontentloaded / networkidle / commit
block_resources = ["image", "media", "font"]
max_pages = 4
allow_scripts = false

# 只对 camoufox 生效
locale = ""
humanize = false
geoip = false

[BROWSER.proxy]
gateway = ""
bypass_list = []

[BROWSER.timeout]
page_load = 30
script_exec = 60
settle = 5                     # wait_until = "networkidle" 时,load 之后额外等网络
                               # 空闲的预算上限(秒)。等不到不算失败,按 load 时的
                               # 内容返回。设 0 = 完全不等

wait_until —— 最影响耗时的一项

什么时候算加载完 备注
commit 收到响应头 最快,但 DOM 基本还是空的
domcontentloaded HTML 解析完 JS 还没跑完
load 所有资源加载完 快,但 load 之后才由 JS 填进来的内容会静默少掉
networkidle 网络静默 500ms(默认) 最慢,但抓得最全

默认是 networkidle 而不是 load,因为 load 的失败方式最难发现:页面骨架有了、 状态码 200、正文却少一块——由 JS 在 load 之后填进来的那部分被丢掉了,而没有任何报错。

networkidle 的经典风险(长连接页面永远等不到静默)已经兜住了。 实现是两段式: 先按 load 完成导航,再额外等网络空闲,预算是 [BROWSER.timeout].settle(默认 5 秒) 而不是 page_load。等不到只打一条 warning,按 load 时的内容正常返回。所以 WebSocket / SSE / 定时轮询的页面最坏就是多花 5 秒,不会失败也不会卡满 30 秒。 想省掉这几秒:把 settle 设 0,或把 wait_until 改回 load

DrissionPage 不支持这套 wait_until / networkidle 两段式等待。 它没有 settle 阶段, 页面何时算加载完全由 DrissionPage 自身的 tab.get() 超时决定,[BROWSER].wait_until 对它 不生效——而 DrissionPage 正是 Windows 上的默认引擎。

block_resources —— 最有效的提速手段

只要 HTML 的话,拦掉图片、字体、媒体能省掉大量时间和带宽。默认已经拦了这三类。 可选值:image media font stylesheet script xhr fetch websocket other

后四类(xhr / fetch / websocket / other只在 playwright / patchright / camoufox 上有效——那三个按 Playwright 的 resource_type 精确拦截。DrissionPage 走的是 URL 后缀通配,只认前五类,没有扩展名的请求拦不到;填了后四类能通过校验, 但一条都拦不掉。(DrissionPage 是 Windows 上的默认引擎。)

别拦 scriptxhr ——除非你确定不需要 JS 渲染的内容。拦了它们等于把 "用浏览器"这件事本身的意义拿掉了。

max_pages

同时打开的页面上限。无头浏览器每个页面几十到上百 MB,不设上限很容易把机器打满。 超出的请求排队等待。

max_pages = 0 是按可用内存自动推导,不是"不限"。按单页预算算——camoufox 400 MB、 chromium 系 250 MB——再给系统留 1 GB,硬上限 16。容器里优先读 cgroup 的内存限制而不是 /proc/meminfo,否则会按宿主机的内存算出一个远超容器配额的数字。

取的是内存预算、CPU 核数与硬上限三者的最小值,不是只看内存:resolve_max_pages() 把 按内存算出的页数和 os.cpu_count() 一起送进 min(),避免页面数超过机器能并行跑的核数。

这一项只对 playwright / patchright / camoufox 生效。DrissionPage 固定串行, 一次只渲染一个页面,调它没有任何效果。

在页面里执行 JS

resp = d.get(
    "https://example.com/product",
    adapter="browser",
    automation_script="return document.querySelector('#price').innerText",
)
print(resp.headers["x-ipclick-script-result"])

automation_script 是在页面里执行的 JavaScript,不是 Python。 返回值经 x-ipclick-script-result 响应头带回。

几点要知道的:

  • 默认关闭allow_scripts = false)。页面里的 JS 会自己发请求, [SECURITY] 那套 URL 策略(禁云元数据、禁内网)对它完全不起作用—— 放开它等于把 SSRF 防线让开一整条。只在确认调用方全部可信时打开。
  • 脚本写错(SyntaxError / ReferenceError)被判为参数错误, 直接 INVALID_ARGUMENT不重试——语法错重试三次还是语法错。
  • 脚本会自动规范化:可以写表达式、也可以写带 return 的函数体,两种都认。
  • 执行超时由 [BROWSER.timeout].script_exec 控制(默认 60s)。
  • 脚本文件较大时会被自动压缩传输,实测 8158 字节压到 350 字节,见 性能

其它渲染参数

这几项只能从 automation_config 里传,不是顶层参数

import json

d.get(url, adapter="browser", automation_config=json.dumps({
    "wait_for_selector": "#content",   # 等某个元素出现
    "wait_for_timeout": 2000,          # 额外固定等待(毫秒)
    "screenshot": True,                # 返回截图
    "scroll_to_bottom": True,          # 滚到底,触发懒加载
    "block_resources": ["image"],      # 按请求覆盖全局配置
    "wait_until": "load",              # 同上
}))

wait_for_selector 比拉长 wait_for_timeout 好——前者一出现就继续,后者不管好坏都等满。

⚠️ 写成顶层参数不会报错,但会被静默忽略。 d.get(url, screenshot=True) 这种写法 里,未列名的参数会被塞进一个 passthrough 字段,而浏览器适配器压根不读它——请求照常成功, 只是既不等元素也不截图。同理,automation_config 里的键名拼错也不会有任何提示。

性能

浏览器路径的实测耗时:

路径 耗时
热路径(浏览器已起来) 200~300 ms
冷启动(首次拉起浏览器) ~1.5 s
永久性导航错误(非法 URL / 被禁协议等) 0.2 s

这几个数字靠下面几件事撑着:

  1. 浏览器实例复用,并在每次使用前用 is_connected() 检查存活;死了就丢弃重建, 而不是拿着一个死连接反复超时。
  2. 超时预算随任务算:冷启动额外给 60s 宽限、常规开销给 15s, 而不是用一个固定值同时套在冷热两条路径上。 (以上两项是 playwright / patchright / camoufox 专属。DrissionPage 复用浏览器前不检查 is_connected(),超时预算固定是 page_timeout + script_timeout + 60,没有冷热区分。)
  3. 永久性导航错误不重试ERR_UNSAFE_PORTERR_INVALID_URLERR_UNKNOWN_URL_SCHEMEERR_DISALLOWED_URL_SCHEMEERR_BLOCKED_BY_CLIENT ——都是"这个 URL 本身就不该访问",重试三次还是同样的结果。 注意 ERR_NAME_NOT_RESOLVEDERR_CONNECTION_REFUSED 不在这个名单里: DNS 和连接失败可能是暂时的,照常重试。
  4. browser 先解析成具体引擎再做缓存键——否则 adapter="browser"adapter="camoufox" 会各自起一个浏览器,内存翻倍。
  5. 调用方已经放弃就不再执行:gRPC 的 deadline 到了之后继续渲染纯属浪费, 结果没人接收。

细节见性能

容器里跑

[BROWSER]
no_sandbox = true      # 同时会带上 --disable-dev-shm-usage
executable_path = "/usr/bin/chromium"
  • 容器默认 /dev/shm 只有 64 MB,Chromium 会崩——no_sandbox = true 会自动加 --disable-dev-shm-usage
  • 容器里通常没有 user namespace,需要 no_sandbox = true--cap-add=SYS_ADMIN
  • 关沙箱意味着页面里的代码更容易影响宿主进程,所以默认是关的,要由部署方明确选择。

camoufox 专属

[BROWSER]
locale = "zh-CN"     # 伪装的语言环境,留空则由 camoufox 生成
humanize = false     # 模拟人类鼠标移动;true 用默认时长,也可写秒数如 1.5
geoip = false        # 让时区/语言/经纬度与代理出口 IP 对上

humanize显著拖慢每次请求,默认关闭。

geoip 只有 [BROWSER.proxy].gateway 能生效——按请求指定的代理是在 context 上设的, 那时指纹早就生成完了。

排查

浏览器相关问题的定位路径见故障排查。快速自查:

ipclick config-info     # 看"引擎:"与"浏览器本体:"两行

同一段还会打"页面上限:"(max_pages = 0 时带 auto 标注,能看出自动推导出了几页) 和"允许页内 JS:",排查浏览器问题时同样有用。

Web 管理端的总览页显示每个引擎的四态未装 / 缺本体 / 本体未知 / 可用。 「本体未知」多半是 DrissionPage 在 PATH 上没找到 Chrome——用 [BROWSER].executable_path 指过去即可。

装引擎:也可以在 Web 端点按钮

/components 页列出四个渲染引擎的两级安装状态(Python 包 / 浏览器本体), 可以就地装包、下载本体、卸载。

DrissionPage 是个例外:它用本机已装的 Chrome,没有"下载本体"这一步,页面上只有 装包 / 卸载两个按钮,本体状态是探测 PATH 上有没有 Chrome / Chromium / Edge。装完不用重启进程。

两级必须分开看:pip install "ipclick[camoufox]" 只装几 MB 的 Python 包,浏览器本体 (约 1 GB)是 python -m camoufox fetch 下的。只报一级的话,装了包没 fetch 的机器会 显示"已安装",而第一次请求会卡几分钟去下 1 GB 然后超时。

卸载只卸 Python 包——那 1 GB 本体还在磁盘上,界面会把路径和体积告诉你, 要回收空间自己删那个目录。

细节见 Web 管理端 · 组件

下一步

Clone this wiki locally