Skip to content

Adapters

元气码农少女酱 edited this page Aug 20, 2026 · 5 revisions

适配器

适配器决定「这个请求实际怎么发出去」。同一份调用代码,换个 adapter= 就换了底层实现。

一览

名字 装法 适合 特点
curl_cffi 默认自带 绝大多数场景 唯一带 TLS 指纹伪装;HTTP/1.1 + HTTP/2
niquests pip install "ipclick[niquests]" 需要 HTTP/3、或纯 API 调用 HTTP/1.1 + HTTP/2 + HTTP/3 (QUIC);requests 兼容 API
browser 浏览器渲染 内容要 JS 跑完才有 通用名,引擎由服务端 [BROWSER].engine
camoufox / patchright / playwright / DrissionPage 同上 要点名某个引擎时 浏览器渲染

请求级选择:

d.get(url)                        # curl_cffi
d.get(url, adapter="niquests")
d.get(url, adapter="browser")     # 引擎由服务端定 —— 推荐这么写
d.get(url, adapter="camoufox")    # 点名,服务端没装就报 AdapterError

curl_cffi —— 默认

基于 libcurl-impersonate。它的价值在于TLS 指纹:很多站点在 TLS 握手阶段就能看出 「这不是浏览器」,此时改 User-Agent 是没用的,因为 JA3/JA4 指纹对不上。curl_cffi 直接复刻真实浏览器的 ClientHello。

d.get(url, impersonate="chrome124")

不传的话用适配器自己的默认。可选值跟随 curl_cffi 版本(chrome* / safari* / edge* 等)。

只有 curl_cffi 支持 impersonate 别的适配器收到这个参数会报错而不是忽略—— 静默忽略的话你以为伪装开着,实际裸奔,被封了都不知道为什么。

niquests —— HTTP/2 与 HTTP/3

requests 的现代继任者,API 几乎一样。

d.get(url, adapter="niquests")

用它的两种情形:

  1. 目标站点走 HTTP/3。QUIC 在移动网络和高丢包链路上明显更快。
  2. 对方不做指纹检测(自家 API、公开接口),此时不需要 curl_cffi 的伪装开销。

requestshttpx 两个适配器不存在——它们的能力被 niquests 覆盖,且 niquests 还多支持 HTTP/3,维护两套等价实现不划算。传这两个名字会得到一条明确的迁移提示 ("请改用 niquests"),而不是笼统的"不支持"。protobuf 枚举值保留并标了 deprecated, 不会被复用——老客户端连上来时报的是有用的话。

通用 vs 点名

请求 adapter="browser"通用写法:客户端只表达「我要渲染」,具体引擎由服务端的 [BROWSER].engine 决定。好处是客户端代码不用关心每台节点装了什么——Windows 节点用 DrissionPage、Linux 节点用 camoufox,同一份调用代码都能跑。

点名(adapter="camoufox")表达的是「我就要这个引擎」。服务端没装就直接失败—— 这也是对的:你要的是 camoufox 的反检测特性,悄悄换成 playwright 只会让你以为它生效了。

所有适配器共享的行为

不管用哪个适配器,下面这些都一样——它们实现在基类里而不是各写一遍:

重试

[DOWNLOADER.retry]
max_attempts = 3
initial_backoff = 1
backoff_exponent = 2.0
max_backoff = 30                          # 硬上限 300
retry_codes = [429, 500, 502, 503, 504]

等待时间 = initial_backoff × exponent^已重试次数,封顶 max_backoff。 连接层异常总是重试,与 retry_codes 无关。

不会重试的东西AdapterError(依赖缺失、引擎未就绪、脚本语法错、永久性导航错误)。 这些重试多少次都是同一个结果,只会把失败反馈拖慢好几倍。

单次请求可以覆盖:

d.get(url, max_retries=0, timeout=5)

allowed_status_codes

d.get(url, allowed_status_codes=[200, 404])

告诉适配器哪些状态码算"正常结束"。默认下 429/5xx 会触发重试;如果你就是要探测 404, 把它列进来可以省掉三次无谓重试。

超时

timeout单次尝试的上限,不是总时长。带重试时最坏总耗时约 timeout × (max_retries + 1) + 退避总和。集群转发的默认超时就是按这个公式推的 (见集群)。

按 host 限流

并发闸门与 QPS 令牌桶对所有适配器统一生效,见性能

装没装:现查,不用重启

"这台机器上哪些适配器能用"是每次现查的,走 importlib.util.find_spec()—— 不执行模块代码,纯文件系统级探测。带来两个实际差别:

  • 在终端里 pip install "ipclick[niquests]" 之后,Web 管理端点一下「刷新状态」 就能用了,不用重启服务
  • 卸载也能正确反映。这里不能用模块级 try: import——真 import 过的模块留在 sys.modules 里,删掉磁盘上的包也不会让它消失,于是"装"看得见、"卸"看不见。

真正的 import 仍然在执行路径上(第一次构造该适配器时),这一层只负责状态展示。

「试一试」的适配器下拉框也跟着变了:没装的不再从列表里消失,而是置灰并标上安装 命令。消失会让对着文档看的人以为文档和实现对不上,也不知道 IPClick 到底支持哪些。 通用占位值 browser 单独标注"自动选择引擎",不和真实适配器名混排——它不是第六个 可选组件。

装 / 卸也可以直接在 /components 页点按钮完成,见 Web 管理端 · 组件

自定义适配器

from ipclick.adapters.base import DownloaderAdapter
from ipclick.adapters.registry import register_adapter

class MyAdapter(DownloaderAdapter):
    adapter_name = "my_adapter"

    def download(self, task, **kwargs): ...

register_adapter(MyAdapter)

注册后 adapter="my_adapter" 就能用了。注意这是服务端行为——注册要在服务端进程里做, 客户端只是传一个名字。

常见报错

报错 含义
适配器 'httpx' 已移除:请改用 niquests… 这个适配器不存在,用 niquests
适配器 'niquests' 需要额外依赖:pip install "ipclick[niquests]" 包没装
引擎 camoufox 的浏览器本体未就绪,请执行 python -m camoufox fetch 包装了、浏览器本体没下
下载器适配器 'xxx' 尚未支持,当前可用: … 名字拼错了
本节点没有开启服务端转发…无法指定目标节点 Web 端点名了目标节点,但本进程 forward = "off"
curl_cffi 之外的适配器不支持 impersonate=… 见上文

这四种说法刻意区分开:「装一下就能用」「再下个浏览器」「改配置」「改代码」, 对应的处理动作完全不同。

下一步

Clone this wiki locally