Skip to content

Troubleshooting

HadesTop edited this page Aug 17, 2026 · 7 revisions

故障排查

第一步永远是这个

ipclick config-info

它显示的是「什么真生效了」,不是配置文件里写了什么——环境变量会覆盖、 有些项写错了会被忽略、有些有默认值。一大半配置问题在这里一眼能看出来。

读懂报错

IPClick 刻意把四类问题分开报,因为处理动作完全不同:

症状 类别 该做什么
status_code == -1 瞬时传输故障 resp.error,多半是网络/目标站点
ValidationError 参数写错 改代码
AdapterError 服务端能力缺失 装依赖 / 下浏览器本体 / 改配置
AuthenticationError 鉴权 对令牌
ConfigError 配置写错 改配置文件

只有瞬时传输故障返回 -1 把用法错误伪装成网络故障会让人查错方向—— 这是刻意的设计。

启动

端口被占用

Failed to bind to port 9527

IPClick 显式关掉了 SO_REUSEPORT,所以端口撞了会直接起不来

gRPC 默认是开着它的,那样两个进程都能"成功"监听,请求被内核随机分给其中一个—— 症状是"改了配置只有一半生效",极难定位。宁可起不来。

找出占用者:

ss -lptn "sport = :9527"

别用 pkill -f ipclick —— pkill -f 会匹配到它自己的命令行。 用上面的命令拿到 PID 再 kill

起来了但客户端连不上

按顺序排除:

  1. [SERVER].host 是不是 127.0.0.1 而客户端在别的机器上
  2. 防火墙
  3. TLS 一边开一边没开
  4. http_proxy / HTTPS_PROXY 环境变量把 gRPC 连接也劫持了

第 4 条特别隐蔽:机器上设了代理时,gRPC 会尝试用它连内网地址然后失败。 IPClick 的内部 channel 已经带了 grpc.enable_http_proxy = 0, 但你自己建的 channel 要自己加。

启动就打两条告警

传输层:未启用(明文)
未配置鉴权令牌,任何能连到本端口的调用方都可以使用本服务。

这是有意的,不是 bug。 只监听本机的话可以忽略;监听在别的地址上请看安全

请求

-1 加一句连接错误

传输层的问题:DNS、连接被拒、TLS 握手失败、超时。resp.error 里有具体原因。

对方封 IP 或封指纹的话,试试换适配器:

d.get(url, adapter="browser")      # 真浏览器
d.get(url, impersonate="chrome124")  # curl_cffi 指定指纹

ValidationError: 不允许的协议

[SECURITY].allowed_schemes 只放行 http / https。这是 SSRF 防护的一部分, 见安全

RESOURCE_EXHAUSTED

按 host 限流没等到额度。要么放宽:

[DOWNLOADER.concurrency]
per_host_max_concurrent = 20
per_host_wait_timeout = 60

要么就是这个限流本来就该生效——你确实在打同一个 host 太猛。

请求特别慢或直接超时

依次看:

  1. resp.trace.attempts —— 是不是在反复重试
  2. resp.trace.queued_ms —— 是不是卡在按 host 限流的队列里
  3. 浏览器适配器 → 看下一节
  4. timeout单次尝试的上限,总时长要乘上重试次数,见 性能

客户端超时了但服务端还在跑

DEADLINE_EXCEEDED 刻意不在客户端重试——请求可能已经在服务端执行了, 只是回复没赶上,重发一个 POST 就是重复下单。

服务端这边有对应处理:调用方已经放弃时不再继续执行,避免白干。

浏览器渲染

FAILED_PRECONDITION: 引擎 X 的浏览器本体未就绪

pip 包装了,浏览器本体没下。这是两步安装

python -m camoufox fetch        # camoufox
patchright install chromium     # patchright
playwright install chromium     # playwright

刻意不自动下载,见安装

AdapterError: 适配器 'camoufox' 需要额外依赖

包本身没装:

pip install "ipclick[camoufox]"

请求慢到像卡死(几分钟)

按可能性排序:

  1. 内存不够在换页。 camoufox 尤其吃内存。把 [BROWSER].max_pages 调到 1~2。 free -m 看 swap 有没有在动——在动就是这个原因。
  2. wait_until = "networkidle" 碰上了轮询页面。 网络永远不会静默, 每个请求都耗满 page_load。改成 load
  3. 没拦资源。 确认 block_resources 至少拦了 image / media / font
  4. 每次都在冷启动。 浏览器实例应该复用;如果日志里频繁出现启动浏览器, 说明实例在被反复丢弃——通常是进程被 OOM killer 杀了,还是回到第 1 条。

0.3.0 修完之后热路径是 200~300 ms。还慢到分钟级的话,基本就是上面四条之一。

容器里 Chromium 起不来

[BROWSER]
no_sandbox = true      # 同时会带上 --disable-dev-shm-usage

容器默认 /dev/shm 只有 64 MB,Chromium 会崩;容器里通常也没有 user namespace。

automation_scriptINVALID_ARGUMENT

两种可能:

  1. 服务端 [BROWSER].allow_scripts = false(默认)
  2. 脚本有语法错误 —— 被判为参数错误,不重试(语法错重试三次还是语法错)

集群

请求全打在一台上

  1. [GENERAL].mode 是不是还是 standalone
  2. forward = "on"nodes有没有列上本机自己
  3. resp.trace.node_id —— 它是实际执行的节点
with Downloader(host="10.0.0.1") as d:
    for _ in range(6):
        print(d.get("https://example.com").trace.node_id)

日志说"无法识别本节点身份"

self_id 没配又自动识别失败。后果是本节点只转发不执行任务——安全但浪费一台机器。

IPCLICK_CLUSTER_SELF_ID=n1

节点被反复摘除又加回

  • probe_timeout 太短(默认 3 秒),网络抖动就判失败
  • failure_threshold / recovery_threshold 太小

注意什么才算节点故障:只有 UNAVAILABLE(连接压根没建起来)会摘节点。 DEADLINE_EXCEEDED 不会——目标站点慢是任务的问题,不是节点的问题。 把它算成节点故障的话,抓一个慢站点就能把整个集群摘空。

转发全部失败,报 UNAVAILABLE

机器上设了 http_proxy 之类的环境变量,gRPC 拿它去连内网节点然后失败。 IPClick 的转发 channel 已经带了 grpc.enable_http_proxy = 0;如果还有这个现象, 确认用的是 0.3.0 及以上。

集群令牌对不上

0.4 起先用 /nodes 页的「测试连接」。 它能直接告诉你是"连不上"还是 "连上了但鉴权不通过"——这两种的排查方向完全相反,而只看健康检查是分不出来的 (它免鉴权)。见 Web 管理端 · 测试连接

每台的令牌是派生的,各不相同:

token = HMAC-SHA256(secret, "ipclick-node:" + node_id)
  • 所有节点的 IPCLICK_CLUSTER_SECRET 必须完全一致
  • 每台的 self_id 必须和 nodes 里的 id 对得上(大小写敏感

ipclick config-info 会显示密钥是否配了、来自哪里。

Web 管理端

打不开

  1. 起服务时带了 -w[WEB].enabled = true
  2. 默认只监听 127.0.0.1,远程访问要走 SSH 隧道: ssh -L 9530:127.0.0.1:9530 user@server

密码每次重启都变

没配 IPCLICK_WEB_PASSWORD,所以每次随机生成。写进 .env 就固定了。

请求流页面空的

[TRACE]
memory_size = 500        # 别设成 0
sqlite_enabled = true    # 要查历史必须开

不开 SQLite 的话只有内存里最近 500 条,进程重启即丢。

日志刷"链路记录队列已满"

写盘跟不上请求速率。三个方向:

[TRACE]
queue_size = 20000       # 加大队列(治标)
only_errors = true       # 只记失败(最有效)
sqlite_enabled = false   # 干脆关掉落盘

或者把 db 放到更快的盘上。记录丢弃永远不会反压业务请求——这条日志是提醒你 数据不完整,不是服务出问题了。

改了配置没生效

配置页每项都标了是否需要重启。日志级别、调试模式、only_errorsrecord_url 是热生效的;max_workers、监听地址、引擎这些要重启。

节点列表是例外:0.4 起 /nodes 保存即时生效,新节点马上参与转发轮询。 0.3 需要重启,页面上也是那么写的。

装了依赖,Web 端还显示"未装"

0.3 的探测结论固化在进程启动那一刻(模块级 try: import),所以在终端里装完, 刷新页面多少次都没用,只能重启。

0.4 改成每次现查(importlib.util.find_spec),但缓存在一次"刷新"之间是有效的 ——去 /components 页点一下「刷新状态」即可,不用重启。装 / 卸完成后也会自动刷。

如果点了刷新还是"未装",多半是装到别的环境去了。核对一下:

ipclick config-info      # 看它跑在哪个解释器上

/components 页点按钮装则不会有这个问题——那条命令绑定当前解释器。

同一目录起了多个实例,链路记录 / 日志混在一起

多进程写同一个 SQLite 库或同一个日志文件不报错(WAL 模式下多写者是合法的), 所以你看到的是多个实例合并后的数据,而界面上完全看不出来。

给两个路径加 {port} 占位符:

[TRACE]
sqlite_path = "ipclick-trace.{port}.db"

[LOG]
output = "logs/{port}/app.log"

再用 --port / --web-port 把两个端口都岔开:

ipclick run --port 9528 --web-port 9531 -w

0.4 起检测到库被别的进程占用时会打一条明确告警,但那是兜底,不是解法。 详见配置体系

日志文件没出现在我指定的目录里

[LOG].output = "logs/"0.3 是坏的logs 被当成"缺扩展名的文件名", 改写成同级logs.log,而 logs/ 目录里空空如也,且不报任何错。

去它的同级目录找一下 logs.log,然后升级到 0.4——那个版本以 / 结尾或指向已存在 目录都会被正确当成目录处理。

依赖与安装

zsh 报 no matches found: ipclick[niquests]

方括号被当成 glob 了:

pip install "ipclick[niquests]"

适配器 'httpx' 已移除

0.3.0 移除了 httpxrequests 适配器:

d.get(url, adapter="niquests")   # 能力覆盖 httpx,还多支持 HTTP/3
d.get(url)                       # 要指纹伪装就用默认的 curl_cffi

还是定位不了

打开 debug 日志(会打完整请求内容,包括头和 cookie):

[GENERAL]
debug = true

或者 IPCLICK_LOG_LEVEL=debug

开 issue 时请附上:

  • ipclick --version
  • ipclick config-info 的输出(先把机密涂掉——虽然它本来就不打印机密值)
  • 相关日志片段
  • 一段能复现的最小代码

Clone this wiki locally