-
Notifications
You must be signed in to change notification settings - Fork 0
Troubleshooting
ipclick config-info它显示的是「什么真生效了」,不是配置文件里写了什么——环境变量会覆盖、 有些项写错了会被忽略、有些有默认值。一大半配置问题在这里一眼能看出来。
IPClick 刻意把四类问题分开报,因为处理动作完全不同:
| 症状 | 类别 | 该做什么 |
|---|---|---|
status_code == -1 |
瞬时传输故障 | 看 resp.error,多半是网络/目标站点 |
ValidationError |
参数写错 | 改代码 |
AdapterError |
服务端能力缺失 | 装依赖 / 下浏览器本体 / 改配置 |
AuthenticationError |
鉴权 | 对令牌 |
ConfigError |
配置写错 | 改配置文件 |
只有瞬时传输故障返回 -1。 把用法错误伪装成网络故障会让人查错方向——
这是刻意的设计。
Failed to bind to port 9528
IPClick 显式关掉了 SO_REUSEPORT,所以端口撞了会直接起不来。
gRPC 默认是开着它的,那样两个进程都能"成功"监听,请求被内核随机分给其中一个—— 症状是"改了配置只有一半生效",极难定位。宁可起不来。
找出占用者:
ss -lptn "sport = :9528"别用
pkill -f ipclick——pkill -f会匹配到它自己的命令行。 用上面的命令拿到 PID 再kill。
按顺序排除:
-
[SERVER].host是不是127.0.0.1而客户端在别的机器上 - 防火墙
- TLS 一边开一边没开
-
http_proxy/HTTPS_PROXY环境变量把 gRPC 连接也劫持了
第 4 条特别隐蔽:机器上设了代理时,gRPC 会尝试用它连内网地址然后失败。
IPClick 的内部 channel 已经带了 grpc.enable_http_proxy = 0,
但你自己建的 channel 要自己加。
传输层:未启用(明文)
未配置鉴权令牌,任何能连到本端口的调用方都可以使用本服务。
这是有意的,不是 bug。 只监听本机的话可以忽略;监听在别的地址上请看安全。
传输层的问题:DNS、连接被拒、TLS 握手失败、超时。resp.error 里有具体原因。
对方封 IP 或封指纹的话,试试换适配器:
d.get(url, adapter="browser") # 真浏览器
d.get(url, impersonate="chrome124") # curl_cffi 指定指纹[SECURITY].allowed_schemes 只放行 http / https。这是 SSRF 防护的一部分,
见安全。
按 host 限流没等到额度。要么放宽:
[DOWNLOADER.concurrency]
per_host_max_concurrent = 20
per_host_wait_timeout = 60要么就是这个限流本来就该生效——你确实在打同一个 host 太猛。
依次看:
-
resp.trace.attempts—— 是不是在反复重试 -
resp.trace.queued_ms—— 是不是卡在按 host 限流的队列里 - 浏览器适配器 → 看下一节
-
timeout是单次尝试的上限,总时长要乘上重试次数,见 性能
DEADLINE_EXCEEDED 刻意不在客户端重试——请求可能已经在服务端执行了,
只是回复没赶上,重发一个 POST 就是重复下单。
服务端这边有对应处理:调用方已经放弃时不再继续执行,避免白干。
pip 包装了,浏览器本体没下。这是两步安装:
python -m camoufox fetch # camoufox
patchright install chromium # patchright
playwright install chromium # playwright刻意不自动下载,见安装。
包本身没装:
pip install "ipclick[camoufox]"按可能性排序:
-
内存不够在换页。 camoufox 尤其吃内存。把
[BROWSER].max_pages调到 1~2。free -m看 swap 有没有在动——在动就是这个原因。 -
wait_until = "networkidle"碰上了轮询页面。 网络永远不会静默, 每个请求都耗满page_load。改成load。 -
没拦资源。 确认
block_resources至少拦了image/media/font。 - 每次都在冷启动。 浏览器实例应该复用;如果日志里频繁出现启动浏览器, 说明实例在被反复丢弃——通常是进程被 OOM killer 杀了,还是回到第 1 条。
正常热路径是 200~300 ms。慢到分钟级的话,基本就是上面四条之一。
[BROWSER]
no_sandbox = true # 同时会带上 --disable-dev-shm-usage容器默认 /dev/shm 只有 64 MB,Chromium 会崩;容器里通常也没有 user namespace。
两种可能:
- 服务端
[BROWSER].allow_scripts = false(默认) - 脚本有语法错误 —— 被判为参数错误,不重试(语法错重试三次还是语法错)
-
[GENERAL].mode是不是还是standalone -
forward = "on"时nodes里有没有列上本机自己 - 看
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 不会——目标站点慢是任务的问题,不是节点的问题。
把它算成节点故障的话,抓一个慢站点就能把整个集群摘空。
机器上设了 http_proxy 之类的环境变量,gRPC 拿它去连内网节点然后失败。
IPClick 的转发 channel 已经带了 grpc.enable_http_proxy = 0,所以这条通常不会再出现;
真遇上就先把这些环境变量清掉复现一次,确认是不是它。
先用集群设置页的「测试连接」。 它能直接告诉你是"连不上"还是 "连上了但鉴权不通过"——这两种的排查方向完全相反,而只看健康检查是分不出来的 (它免鉴权)。见 Web 管理端 · 测试连接。
每台的令牌是派生的,各不相同:
token = HMAC-SHA256(secret, "ipclick-node:" + node_id)
- 所有节点的
IPCLICK_CLUSTER_SECRET必须完全一致 - 每台的
self_id必须和nodes里的id对得上(大小写敏感)
ipclick config-info 会显示密钥是否配了、来自哪里。
- 起服务时带了
-w或[WEB].enabled = true吗 - 默认只监听
127.0.0.1,远程访问要走 SSH 隧道:ssh -L 9527:127.0.0.1:9527 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_errors、record_url
是热生效的;max_workers、监听地址、引擎这些要重启。
节点列表是例外:集群设置页保存即时生效,新节点马上参与转发轮询,不用重启。
探测是每次现查的(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检测到库被别的进程占用时会打一条明确告警,但那是兜底,不是解法。 详见配置体系。
以 / 结尾、或指向一个已存在的目录,都会被正确当成目录处理(文件名用 ipclick.log)。
两者都不满足时,logs 会被当成"缺扩展名的文件名",改写成同级的 logs.log。
所以先去同级目录找一下 logs.log;要写进目录就把路径写成 logs/,或者先把那个目录建出来。
方括号被当成 glob 了:
pip install "ipclick[niquests]"httpx 和 requests 这两个适配器不存在,用 niquests 代替:
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的输出(先把机密涂掉——虽然它本来就不打印机密值) - 相关日志片段
- 一段能复现的最小代码