-
Notifications
You must be signed in to change notification settings - Fork 0
Browser Rendering
当页面内容要 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 = 完全不等| 值 | 什么时候算加载完 | 备注 |
|---|---|---|
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 上的默认引擎。
只要 HTML 的话,拦掉图片、字体、媒体能省掉大量时间和带宽。默认已经拦了这三类。
可选值:image media font stylesheet script xhr fetch websocket other。
后四类(xhr / fetch / websocket / other)只在 playwright / patchright /
camoufox 上有效——那三个按 Playwright 的 resource_type 精确拦截。DrissionPage
走的是 URL 后缀通配,只认前五类,没有扩展名的请求拦不到;填了后四类能通过校验,
但一条都拦不掉。(DrissionPage 是 Windows 上的默认引擎。)
别拦
script和xhr——除非你确定不需要 JS 渲染的内容。拦了它们等于把 "用浏览器"这件事本身的意义拿掉了。
同时打开的页面上限。无头浏览器每个页面几十到上百 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 固定串行, 一次只渲染一个页面,调它没有任何效果。
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 |
这几个数字靠下面几件事撑着:
-
浏览器实例复用,并在每次使用前用
is_connected()检查存活;死了就丢弃重建, 而不是拿着一个死连接反复超时。 -
超时预算随任务算:冷启动额外给 60s 宽限、常规开销给 15s,
而不是用一个固定值同时套在冷热两条路径上。
(以上两项是 playwright / patchright / camoufox 专属。DrissionPage 复用浏览器前不检查
is_connected(),超时预算固定是page_timeout + script_timeout + 60,没有冷热区分。) -
永久性导航错误不重试:
ERR_UNSAFE_PORT、ERR_INVALID_URL、ERR_UNKNOWN_URL_SCHEME、ERR_DISALLOWED_URL_SCHEME、ERR_BLOCKED_BY_CLIENT——都是"这个 URL 本身就不该访问",重试三次还是同样的结果。 注意ERR_NAME_NOT_RESOLVED、ERR_CONNECTION_REFUSED不在这个名单里: DNS 和连接失败可能是暂时的,照常重试。 -
browser先解析成具体引擎再做缓存键——否则adapter="browser"和adapter="camoufox"会各自起一个浏览器,内存翻倍。 - 调用方已经放弃就不再执行: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。 - 关沙箱意味着页面里的代码更容易影响宿主进程,所以默认是关的,要由部署方明确选择。
[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
指过去即可。
/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 管理端 · 组件。