ipclick 0.3.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ipclick might be problematic. Click here for more details.

Files changed (89) hide show
  1. ipclick-0.6.0/PKG-INFO +564 -0
  2. ipclick-0.6.0/README.md +518 -0
  3. {ipclick-0.3.0 → ipclick-0.6.0}/pyproject.toml +13 -9
  4. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/__init__.py +1 -1
  5. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/base.py +54 -8
  6. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/browser_engines.py +140 -43
  7. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/drission_adapter.py +38 -14
  8. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/niquests_adapter.py +62 -19
  9. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/registry.py +57 -15
  10. ipclick-0.6.0/src/ipclick/cli/agent.py +1076 -0
  11. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cli/main.py +141 -12
  12. ipclick-0.6.0/src/ipclick/cli/output.py +148 -0
  13. ipclick-0.6.0/src/ipclick/cli/skill_cmd.py +101 -0
  14. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/discovery.py +2 -1
  15. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/forwarder.py +137 -2
  16. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/node.py +9 -0
  17. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/pool.py +52 -0
  18. ipclick-0.6.0/src/ipclick/cluster/probe.py +242 -0
  19. ipclick-0.6.0/src/ipclick/components.py +239 -0
  20. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/config_loader/loader.py +23 -4
  21. ipclick-0.6.0/src/ipclick/config_loader/placeholders.py +81 -0
  22. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/config_loader/writer.py +133 -6
  23. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/configs/default_config.toml +99 -18
  24. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/proto/task.proto +83 -0
  25. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/proto/task_pb2.py +17 -7
  26. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/proto/task_pb2.pyi +74 -0
  27. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/proto/task_pb2_grpc.py +91 -0
  28. ipclick-0.6.0/src/ipclick/ports.py +61 -0
  29. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/sdk.py +38 -2
  30. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/secrets.py +34 -6
  31. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/server.py +334 -22
  32. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/services/task_service.py +264 -27
  33. ipclick-0.6.0/src/ipclick/skill.py +121 -0
  34. ipclick-0.6.0/src/ipclick/skills/ipclick/SKILL.md +184 -0
  35. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/trace.py +313 -194
  36. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/utils/log_util.py +3 -4
  37. ipclick-0.6.0/src/ipclick/utils/module_probe.py +105 -0
  38. ipclick-0.6.0/src/ipclick/utils/path_util.py +89 -0
  39. ipclick-0.6.0/src/ipclick/web/assets.py +935 -0
  40. ipclick-0.6.0/src/ipclick/web/curl_parser.py +274 -0
  41. ipclick-0.6.0/src/ipclick/web/deploy.py +327 -0
  42. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/web/editable.py +286 -10
  43. ipclick-0.6.0/src/ipclick/web/installer.py +802 -0
  44. ipclick-0.6.0/src/ipclick/web/pages.py +1491 -0
  45. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/web/server.py +216 -30
  46. ipclick-0.6.0/src/ipclick/web/templates.py +2032 -0
  47. ipclick-0.3.0/PKG-INFO +0 -1286
  48. ipclick-0.3.0/README.md +0 -1240
  49. ipclick-0.3.0/src/ipclick/utils/path_util.py +0 -37
  50. ipclick-0.3.0/src/ipclick/web/pages.py +0 -564
  51. ipclick-0.3.0/src/ipclick/web/templates.py +0 -966
  52. {ipclick-0.3.0 → ipclick-0.6.0}/LICENSE +0 -0
  53. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/__main__.py +0 -0
  54. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/__init__.py +0 -0
  55. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/browser_adapter.py +0 -0
  56. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/browser_settings.py +0 -0
  57. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/curl_cffi_adapter.py +0 -0
  58. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/adapters/settings.py +0 -0
  59. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/aio.py +0 -0
  60. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/auth.py +0 -0
  61. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cli/__init__.py +0 -0
  62. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/__init__.py +0 -0
  63. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/balancer.py +0 -0
  64. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/client.py +0 -0
  65. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/status_page.py +0 -0
  66. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/cluster/tokens.py +0 -0
  67. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/compression.py +0 -0
  68. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/config_loader/__init__.py +0 -0
  69. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/config_loader/dotenv.py +0 -0
  70. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/configs/__init__.py +0 -0
  71. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/__init__.py +0 -0
  72. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/models.py +0 -0
  73. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/proto/__init__.py +0 -0
  74. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/proto/generate.py +0 -0
  75. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/dto/response.py +0 -0
  76. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/exceptions.py +0 -0
  77. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/factory.py +0 -0
  78. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/health.py +0 -0
  79. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/limiter.py +0 -0
  80. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/py.typed +0 -0
  81. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/resume.py +0 -0
  82. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/services/__init__.py +0 -0
  83. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/tls.py +0 -0
  84. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/utils/__init__.py +0 -0
  85. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/utils/config_util.py +0 -0
  86. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/utils/secure_util.py +0 -0
  87. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/utils/url_util.py +0 -0
  88. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/web/__init__.py +0 -0
  89. {ipclick-0.3.0 → ipclick-0.6.0}/src/ipclick/web/auth.py +0 -0
ipclick-0.6.0/PKG-INFO ADDED
@@ -0,0 +1,564 @@
1
+ Metadata-Version: 2.4
2
+ Name: ipclick
3
+ Version: 0.6.0
4
+ Summary: IPClick Tool
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: 元气码农少女酱
8
+ Author-email: 1276162429@qq.com
9
+ Requires-Python: >=3.11
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Provides-Extra: camoufox
17
+ Provides-Extra: drissionpage
18
+ Provides-Extra: linux
19
+ Provides-Extra: niquests
20
+ Provides-Extra: patchright
21
+ Provides-Extra: playwright
22
+ Provides-Extra: win
23
+ Requires-Dist: camoufox (>=0.5.4,<1.0.0) ; extra == "camoufox"
24
+ Requires-Dist: camoufox (>=0.5.4,<1.0.0) ; extra == "linux"
25
+ Requires-Dist: click (>=8.4.2,<9.0.0)
26
+ Requires-Dist: curl-cffi (>=0.16.0,<0.17.0)
27
+ Requires-Dist: drissionpage (>=4.1.1,<5.0.0) ; extra == "drissionpage"
28
+ Requires-Dist: drissionpage (>=4.1.1,<5.0.0) ; extra == "win"
29
+ Requires-Dist: fake-useragent (>=2.2.0,<3.0.0)
30
+ Requires-Dist: grpcio (>=1.83.0,<2.0.0)
31
+ Requires-Dist: grpcio-health-checking (>=1.83.0,<2.0.0)
32
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
33
+ Requires-Dist: niquests (>=3.21.0,<4.0.0) ; extra == "niquests"
34
+ Requires-Dist: patchright (>=1.61.2,<2.0.0) ; extra == "linux"
35
+ Requires-Dist: patchright (>=1.61.2,<2.0.0) ; extra == "patchright"
36
+ Requires-Dist: playwright (>=1.60.0,<2.0.0) ; extra == "playwright"
37
+ Requires-Dist: protobuf (>=6.33.2,<8.0.0)
38
+ Requires-Dist: python-box[all] (>=7.4.1,<8.0.0)
39
+ Requires-Dist: typing-extensions (>=4.16.0,<5.0.0)
40
+ Requires-Dist: uuid-utils (>=0.17.0,<0.18.0)
41
+ Project-URL: Bug Tracker, https://github.com/yuanqimanong/IPClick/issues
42
+ Project-URL: Homepage, https://github.com/yuanqimanong/IPClick
43
+ Project-URL: Repository, https://github.com/yuanqimanong/IPClick
44
+ Description-Content-Type: text/markdown
45
+
46
+ # IPClick
47
+
48
+ ![IPClick Logo](https://raw.githubusercontent.com/yuanqimanong/IPClick/master/docs/logo.png)
49
+
50
+ > IPClick 名字灵感来源于动画《Link Click》(时光代理人)。正如时光代理人穿梭于不同的时空执行任务,IPClick 帮助您将 HTTP 请求分发到不同的节点高效执行。
51
+
52
+ IPClick 是一个轻量级、高性能的分布式 HTTP 请求代理工具,基于 gRPC 协议构建。它提供了统一的请求接口,支持多种 HTTP
53
+ 客户端适配器,帮助开发者更高效地处理网络请求。
54
+
55
+ > 📚 **完整文档在 [Wiki](https://github.com/yuanqimanong/IPClick/wiki)**。本页只讲"这是什么、怎么跑起来",
56
+ > 深入用法都在 Wiki 里。
57
+
58
+ | | |
59
+ |---|---|
60
+ | [安装](https://github.com/yuanqimanong/IPClick/wiki/Installation) | extras、可选组件、两级安装 |
61
+ | [快速开始](https://github.com/yuanqimanong/IPClick/wiki/Quick-Start) | 从零到第一个请求 |
62
+ | [SDK 用法](https://github.com/yuanqimanong/IPClick/wiki/SDK-Usage) | GET/POST、请求头、代理、流式、断点续传、批量、异步 |
63
+ | [命令行](https://github.com/yuanqimanong/IPClick/wiki/CLI) | 全部命令与选项,`--json` 契约与退出码 |
64
+ | [配置体系](https://github.com/yuanqimanong/IPClick/wiki/Configuration) | 优先级、环境变量、每一节的含义 |
65
+ | [适配器](https://github.com/yuanqimanong/IPClick/wiki/Adapters) | 六个适配器怎么选 |
66
+ | [浏览器渲染](https://github.com/yuanqimanong/IPClick/wiki/Browser-Rendering) | 四个引擎、反检测、两级安装 |
67
+ | [集群](https://github.com/yuanqimanong/IPClick/wiki/Cluster) | 转发与客户端分发、节点鉴权、部署材料 |
68
+ | [Web 管理端](https://github.com/yuanqimanong/IPClick/wiki/Web-Console) | 每个页面能干什么 |
69
+ | [安全](https://github.com/yuanqimanong/IPClick/wiki/Security) | 令牌、TLS/mTLS、SSRF 准入 |
70
+ | [性能与容量](https://github.com/yuanqimanong/IPClick/wiki/Performance) | 线程、连接池、按 host 限流 |
71
+ | [链路记录](https://github.com/yuanqimanong/IPClick/wiki/Observability) | 记了什么、怎么查 |
72
+ | [故障排查](https://github.com/yuanqimanong/IPClick/wiki/Troubleshooting) | 连不上、被拦、装不上 |
73
+ | [API 参考](https://github.com/yuanqimanong/IPClick/wiki/API-Reference) | 类型与签名 |
74
+
75
+ ## ✨ 特性
76
+
77
+ - **多适配器**:默认 `curl_cffi`(带 TLS 指纹伪装),按需加装 `niquests`(HTTP/3)
78
+ 或四个浏览器引擎之一;也可注册自定义适配器
79
+ - **轻量安装**:`pip install ipclick` 只带 curl_cffi,其余走 extras
80
+ - **浏览器渲染**:起真实浏览器执行 JS,camoufox / patchright / playwright /
81
+ DrissionPage 四选一,默认按平台挑
82
+ - **集群与故障转移**:轮询 / 随机 / 加权均衡、健康探测、自动换节点;服务端转发与
83
+ 客户端分发两种形态
84
+ - **按 host 限流**:按目标域名限制并发与 QPS,不会把单个站点打爆
85
+ - **流式下载 · 断点续传 · 批量 · 异步客户端**
86
+ - **令牌鉴权 · TLS/mTLS · SSRF 准入 · `grpc.health.v1` 健康检查**
87
+ - **链路记录**:每个请求记一条(谁执行的、重试几次、排队多久),内存环形缓冲 +
88
+ 可选 SQLite,零第三方依赖
89
+ - **Web 管理端**:带登录的界面——总览、实时请求流、就地发请求看源码、配置改完写回
90
+ `ipclick.toml`、集群节点增删、可选组件装卸。前端零构建链路
91
+ - **AI 可调用**:一组结构化输出、退出码分类的命令,外加随包分发的技能包
92
+ - **完整类型标注**:随包提供 `py.typed`
93
+
94
+ ## 📦 安装
95
+
96
+ ```bash
97
+ pip install ipclick # 只带 curl_cffi,17 个包
98
+ pip install "ipclick[niquests]" # 加 HTTP/2 + HTTP/3
99
+ pip install "ipclick[camoufox]" # 加浏览器渲染(还需下载浏览器本体)
100
+ ```
101
+
102
+ **没有 `[all]`**:四个浏览器内核全装是 70+ 个包和上 G 的浏览器本体,绝大多数部署
103
+ 只需要其中一个。装哪个、怎么下浏览器本体,见 [安装](https://github.com/yuanqimanong/IPClick/wiki/Installation)。
104
+
105
+ 需要 Python 3.11+。
106
+
107
+ ## 🚀 快速开始
108
+
109
+ ### 启动服务端
110
+
111
+ > ⚠️ **0.5.0 换了两个默认端口**:Web 管理端 `9530 → 9527`,gRPC 服务端 `9527 → 9528`。
112
+ > 从 0.4 升上来的部署,请在 `ipclick.toml` 的 `[SERVER].port` 里把你原来的端口显式写
113
+ > 出来;否则客户端仍然连 9527 时会连到 Web 端口上(那是个 HTTP 服务,gRPC 握手会失败
114
+ > 并报一个和端口毫无关系的错误——所以连不上时会附一句端口提示)。
115
+
116
+ ```bash
117
+ # 使用默认配置启动
118
+ ipclick run
119
+
120
+ # 指定端口和地址
121
+ ipclick run --host 0.0.0.0 --port 9528
122
+
123
+ # 使用自定义配置文件(TOML 格式)
124
+ ipclick run --config /path/to/ipclick.toml
125
+
126
+ # 显示详细日志
127
+ ipclick run --verbose
128
+
129
+ # 同一目录起第二个实例:gRPC 与 Web 两个端口都要岔开
130
+ ipclick run --port 9528 --web-port 9531 -w
131
+ ```
132
+
133
+ ### 吞吐上不去?先加进程,不是加线程
134
+
135
+ 服务端是一请求一线程的 CPython 进程,**GIL 才是吞吐天花板**。实测 16 核机器上
136
+ 单进程只能用出 1.5 个核,把 `[SERVER].max_workers` 从 32 调到 256 对吞吐
137
+ **没有任何影响**(279.8 / 275.9 / 277.9 QPS)。
138
+
139
+ 0.6.0 起可以起多个工作进程,靠 SO_REUSEPORT 共享同一个端口——分发由内核做,
140
+ 不需要中间件,对调用方完全透明(仍然只有一个地址一个端口):
141
+
142
+ ```toml
143
+ [SERVER]
144
+ processes = 4 # 0 = 按 CPU 核数自动(上限 8);1 = 单进程(默认)
145
+ ```
146
+
147
+ 并发上去之后大面积失败、而服务端 CPU 却很空闲,是另一件事——那是准入上限:
148
+
149
+ ```toml
150
+ [SERVER]
151
+ max_concurrent_rpcs = 0 # 0 = max_workers × 8。这一项决定"排队能排多长",
152
+ # 和 max_workers("同时能干多少活")是两件事
153
+ ```
154
+
155
+ 实测并发 100 时:单进程 196.6 → 331.1 QPS,四进程 → 544.6 QPS;
156
+ 1000 并发的成功率从 25.8%(异步客户端)回到 100%。详见 CHANGELOG 的 0.6.0 一节。
157
+
158
+ 初始化配置(推荐):
159
+
160
+ ```bash
161
+ ipclick init
162
+ ```
163
+
164
+ 一次生成两份文件:`ipclick.toml`(行为配置)和 `.env`(机密)。`.env` 用 **600**
165
+ 权限创建、预填一个随机 Web 密码、自动追加进 `.gitignore`;文件已存在时拒绝覆盖
166
+ (要覆盖加 `--force`)。
167
+
168
+ 也可以只要模板:
169
+
170
+ ```bash
171
+ ipclick -e > ipclick.toml # 配置模板(-e 等价于 -e toml)
172
+ ipclick -e env > .env # 机密模板(注意自己 chmod 600)
173
+ ```
174
+
175
+ ### 什么放哪儿
176
+
177
+ | | 放什么 | 进版本库? |
178
+ |---|---|---|
179
+ | **`ipclick.toml`** | 行为配置:超时、重试、限流、浏览器引擎、SSRF 策略 | ✅ 应该 |
180
+ | **`.env`** | **只放机密**:令牌、密码、带凭据的连接串 | ❌ 绝不 |
181
+
182
+ `.env` 里只有这 6 项:
183
+
184
+ ```
185
+ IPCLICK_AUTH_TOKEN gRPC 鉴权令牌
186
+ IPCLICK_WEB_USER Web 管理端用户名
187
+ IPCLICK_WEB_PASSWORD Web 管理端密码
188
+ IPCLICK_PROXY_AUTH_KEY 代理账号
189
+ IPCLICK_PROXY_AUTH_PASSWORD 代理密码
190
+ IPCLICK_CLUSTER_SECRET 集群共享密钥(所有节点一致;每台机器的令牌由它派生)
191
+ ```
192
+
193
+ **机密写在 `ipclick.toml` 里仍然生效**——受信环境里图省事是合理的。只是启动时会
194
+ 点名提醒(`ipclick.toml` 通常要进版本库,机密会跟着进 git、备份、CI 日志)。
195
+ 确实想这么放就设 `[SECURITY].allow_secrets_in_config = true` 关掉提醒。
196
+ 两边都写时**环境变量优先**。
197
+
198
+ `ipclick config-info` 会逐项显示每个机密**来自哪里**(环境变量 / 配置文件 / 未配置)
199
+ ——配错了地方是最难自己发现的一类问题。
200
+
201
+ 查看当前**实际生效**的配置——TLS、鉴权、限流、浏览器引擎、运行模式一目了然:
202
+
203
+ ```bash
204
+ ipclick config-info
205
+ ```
206
+
207
+ ### 客户端使用
208
+
209
+ ```python
210
+ from ipclick import Downloader, HttpMethod
211
+
212
+ # 创建下载器实例(建议用 with,退出时自动关闭 gRPC 连接)
213
+ with Downloader() as downloader:
214
+ # 发送 GET 请求
215
+ response = downloader.get("https://httpbin.org/ip")
216
+ print(response.text)
217
+ print(response.json())
218
+
219
+ # 发送带参数的请求
220
+ response = downloader.request(
221
+ method=HttpMethod.GET,
222
+ url="https://httpbin.org/get",
223
+ headers={"User-Agent": "IPClick/1.0"},
224
+ params={"key": "value"},
225
+ timeout=30,
226
+ )
227
+
228
+ # 检查请求是否成功
229
+ if response.is_success():
230
+ print(f"请求成功,耗时: {response.elapsed_ms}ms")
231
+ else:
232
+ print(f"请求失败: {response.error}")
233
+ ```
234
+
235
+ `request()` 及各便捷方法不会因为网络问题抛异常,失败时返回 `status_code == -1`
236
+ 且 `error` 非空的 `DownloadResponse`。参数本身非法(如 URL 为空)会抛 `ValidationError`。
237
+
238
+ 更多用法——代理、流式、断点续传、批量、异步、指定适配器——见
239
+ [SDK 用法](https://github.com/yuanqimanong/IPClick/wiki/SDK-Usage)。
240
+
241
+ ## 🖥️ Web 管理端
242
+
243
+ ```bash
244
+ ipclick run --web # 或 -w
245
+ ```
246
+
247
+ 启动时会把访问地址和登录信息打到控制台:
248
+
249
+ ```
250
+ ==============================================================
251
+ IPClick Web 管理端: http://127.0.0.1:9527/
252
+ 用户名: admin
253
+ 密码: N8mSKkdPbpiGzB128At3
254
+
255
+ ⚠️ 该密码为本次启动随机生成,重启后失效。
256
+ ==============================================================
257
+ ```
258
+
259
+ **没配密码就随机生成**,而不是给个 admin/admin 之类的默认值——默认弱口令是这类
260
+ 管理界面被打穿的头号原因。要固定下来用环境变量(推荐,密码不该进版本库):
261
+
262
+ ```bash
263
+ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
264
+ ```
265
+
266
+ 或写进 `.env`,或配 `[WEB].username` / `password`。
267
+
268
+ ### 六个页面
269
+
270
+ **左侧导航 + 主内容 + 右侧状态栏**的 CSS Grid,左下角**亮 / 暗**主题切换
271
+ (记在 `localStorage`,刷新不丢)。
272
+
273
+ | 页面 | 干什么 |
274
+ |---|---|
275
+ | **总览** `/` | 吞吐、成功率、在途与峰值、状态码分布、各适配器耗时与流量、集群拓扑、最近请求;右栏常驻服务端 / 安全 / 链路 / 限流 / 组件状态,**gRPC 与 Web 两个端口分别列出**(取运行时实际值) |
276
+ | **请求流** `/trace` | **实时**看请求打进来,刷新频率可选 1 秒 / 5 秒 / 30 秒或关闭。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
277
+ | **试一试** `/test` | 填一个网址(或直接**粘一条 curl**)就地发一次请求,看链路信息与返回的**源码**。参数与 SDK 的 `request()` 一一对应;可**点名**打到某个节点 |
278
+ | **组件** `/components` | 五个可选 extras 的两级安装状态,可以就地**安装 / 卸载 / 下载浏览器本体**(带真进度条)。可以**点名装到集群里某台机器**——省掉逐台 SSH |
279
+ | **配置** `/config` | 两个分页:**基础设置**(端口、线程、超时、日志…)与**集群设置**(转发开关、节点增删、子节点部署材料)。写回 `ipclick.toml`,可一键**生成凭据** |
280
+ | **AI 接入** `/skill` | 给 AI 代理用的技能包:装它的命令、全文、以及 `SKILL.md` 下载 |
281
+
282
+ > `/nodes` 已并进 `/config?tab=cluster`——节点管理本来就是集群配置的一部分。老地址会自动跳过去。
283
+
284
+ 页面上的**时间一律按你浏览器所在时区显示**。服务端跑在 UTC 的容器里时,
285
+ 东八区的人看到的仍是自己的钟点。
286
+
287
+ 「试一试」走的是本进程 `TaskService` 的**同一条**代码路径——SSRF 准入、限流、
288
+ 以及开了转发时的分发全都照常生效,请求也会像真实请求一样出现在请求流里。
289
+
290
+ **装到集群里某台机器**:组件页顶部有个「装到哪台机器」下拉,和「试一试」是同一个
291
+ 心智模型。对端跑的是**同一个** `InstallManager`——同一份白名单、同一条命令规划、
292
+ 同一个"一次只跑一个任务"的约束。**默认关闭**,要被操作的那台自己打开
293
+ `[CLUSTER].allow_remote_install`:它等于允许调用方在那台机器上跑 pip,
294
+ 是从"能代发 HTTP 请求"到"能改本机 Python 环境"的实质提权。
295
+
296
+ **从 curl 导入**:浏览器 DevTools 里对着请求「复制为 cURL」,粘进输入框就能自动
297
+ 填好 URL / 方法 / 请求头 / 请求体。认不出的参数会明确列出来,不静默丢弃。
298
+
299
+ **指定目标节点**:配了 `[CLUSTER].nodes` 就有一个"目标节点"下拉,选中后强制打到
300
+ 那一台。开了服务端转发时走转发器(跳过负载均衡),**没开转发时由这一页直连**那台
301
+ 机器发一次 gRPC(用集群内部令牌)。
302
+
303
+ **测试连接**:集群设置里每张节点卡片一个按钮,只验**连通性**与**集群内部鉴权**,
304
+ 不发业务请求。失败时区分"连不上"(查进程和网络)和"鉴权不通过"(核对各节点 `.env` 里的
305
+ `IPCLICK_CLUSTER_SECRET`)——这两种的排查方向完全相反。
306
+
307
+ **生成凭据**:`/config` 可以为鉴权令牌、Web 密码、集群共享密钥各生成一个随机值,
308
+ **只显示一次**(服务端不保存、不写进任何文件,取完即弃),并明确区分"本机独有"
309
+ 和"必须复制到所有其他节点的 `.env`"——集群共享密钥每台各自生成一个就全对不上了。
310
+
311
+ ### 能改什么、不能改什么
312
+
313
+ - **能改**(写回 toml,保留注释与格式,改动前留 `.bak`):69 项,分 12 组——服务端、
314
+ 日志、下载行为、重试、连接池、按 host 限流、浏览器渲染、代理、集群、链路记录、
315
+ Web 管理端、客户端与压缩。
316
+ - **不能改**:`[SECURITY]` 全部(令牌、TLS、SSRF 三个开关)、Web 自己的登录凭据、
317
+ 集群共享密钥与各节点 token、`[BROWSER].allow_scripts`、`[BROWSER].executable_path`、
318
+ `[CLUSTER].allow_remote_install`。后三项不是"配置"是"授权"——它们能让调用方在
319
+ 服务端跑任意东西,不该和超时、线程数摆在同一个表单里让人顺手划过去。
320
+ - **只报真正变了的项**:改一个日志级别就只说这一项,不会每次都报"20 项已写回、
321
+ 12 项需要重启"——那样人会开始无视这句提示,而它在真需要重启时是唯一的信号。
322
+ - **能装依赖**:`/components` 可以装 / 卸那五个 extras,以及下载浏览器本体。
323
+ 包名走白名单常量,命令以列表交给 `subprocess`(`shell=False`)并绑定当前解释器;
324
+ `pip` 与 `uv pip` 自动探测。长任务(`camoufox` 要下约 1 GB)跑后台,页面轮询进度。
325
+ **卸载只卸 Python 包**,浏览器本体不动——界面会把它的路径和体积摆出来,让人自己决定。
326
+
327
+ 写回配置是**定点文本替换**:只换被改动那一行等号右边的值,注释与排版原样保留;
328
+ 写入前会先校验结果仍是合法 TOML,用临时文件 + `os.replace` 落盘。
329
+
330
+ 前端没有构建链路——没有模板引擎、框架、打包工具,也没有任何外部资源。
331
+ CSP 的 `script-src` 用的是内联脚本的 **sha256 哈希**而不是 `'unsafe-inline'`,
332
+ 所以注入进来的 `<script>` 执行不了。
333
+
334
+ 安全措施:会话 cookie 带 `HttpOnly` + `SameSite=Strict`,所有写操作校验 CSRF
335
+ token,登录失败按来源 IP 限速(5 次后锁定 5 分钟,锁定期间正确密码也不放行),
336
+ 密码用常量时间比对,`X-Frame-Options: DENY` + 严格 CSP。
337
+
338
+ > ⚠️ 界面本身是**明文 HTTP**。默认只监听 `127.0.0.1`;要远程访问请用 SSH 隧道,
339
+ > 或放在做了 TLS 终止的反向代理之后。直接把 `[WEB].host` 改成 `0.0.0.0` 会让
340
+ > 登录密码在网络上裸奔(启动时会打告警)。
341
+
342
+ ### 让局域网内的其他设备也能打开
343
+
344
+ ```bash
345
+ ipclick run -w --web-lan # 等价于 --web-host 0.0.0.0
346
+ ipclick run -w --web-host 192.168.1.5 # 或者只绑某一张网卡
347
+ ```
348
+
349
+ 也可以写进配置(`[WEB].host = "0.0.0.0"`),命令行优先。
350
+
351
+ 在可信的局域网里自用是合理的(拿手机看看跑得怎么样),但要清楚代价:
352
+
353
+ - **明文 HTTP**,登录密码会在网线上裸奔。跨网段暴露请放在做了 TLS 终止的反向代理后面。
354
+ - 更要紧的是 **gRPC 那一侧**。管理端的「试一试」能代发任意请求,而同一网段的人绕过
355
+ 网页直连 gRPC 端口是更省事的那条路——所以开局域网访问之前先把
356
+ `[SECURITY].auth_token` 配上。启动前和启动时各会告警一次。
357
+
358
+ ### 主题
359
+
360
+ 左下角「亮 / 暗」两态,选择记在 `localStorage`。服务端默认值:
361
+
362
+ ```toml
363
+ [WEB]
364
+ theme = "light" # light / dark
365
+ ```
366
+
367
+ 优先级是**浏览器里点过的那一下 > `[WEB].theme`**。反过来的话,用户每刷新一次页面,
368
+ 自己刚选的主题就会被配置文件推翻一次。
369
+
370
+ > 0.5.0 去掉了「跟随系统」。那一档靠 CSS 的 `prefers-color-scheme`,取决于浏览器读不
371
+ > 读得到桌面偏好——Linux 上 Chrome/Firefox 要 GTK 或 xdg-desktop-portal 配好才认,
372
+ > 读不到就静默按亮色处理。一个在半数机器上不生效、失败时又毫无迹象的选项,比没有这个
373
+ > 选项更糟。配置里留着旧的 `auto` 也能启动,按 `light` 处理。
374
+
375
+ ## 🤖 命令行(给人,也给 AI)
376
+
377
+ 除了部署用的 `init` / `run` / `health`,还有一组**结构化输出**的命令,供脚本和 AI 代理调用。
378
+ 全部支持 `-J/--json`。
379
+
380
+ ```bash
381
+ ipclick status --json # 服务端在不在、这台机器能用哪些适配器
382
+ ipclick fetch <URL> --json # 发一次请求
383
+ ipclick trace list -n 20 --json # 查链路记录
384
+ ipclick node probe --json # 探集群节点:连得上吗、令牌配对吗
385
+ ipclick component list --json # 五个可选组件的安装状态
386
+ ipclick config show --json # 生效配置(机密脱敏)
387
+ ```
388
+
389
+ ### 输出契约
390
+
391
+ 加 `--json` 后,**stdout 上有且只有一个 JSON 文档**,成功失败都是;日志与进度走
392
+ stderr。所以 `ipclick ... --json | jq` 永远安全。每个文档都带 `ok` 和 `exit_code`,
393
+ 和进程退出码一致。
394
+
395
+ | 退出码 | 含义 | 往哪儿查 |
396
+ |---|---|---|
397
+ | 0 | 成功 | — |
398
+ | 1 | 拿到响应但不理想(HTTP ≥ 400、探测不通、装包失败) | 目标本身 |
399
+ | 2 | 命令行参数写错了 | `--help` |
400
+ | 3 | 连不上 IPClick **服务端**(请求根本没发出去) | 进程 / 地址端口 / 防火墙 |
401
+ | 4 | 鉴权失败 | 令牌(`IPCLICK_AUTH_TOKEN`) |
402
+ | 5 | 参数被服务端拒绝,或本地配置不合法 | 调用参数或 `ipclick.toml` |
403
+
404
+ `status: -1` 表示没拿到 HTTP 响应,这时看 `reached_server`:`false` 是没连上 IPClick
405
+ 本身(退出码 3),`true` 是 IPClick 正常、连不上**目标站点**(退出码 1,原因在 `error`)。
406
+
407
+ ### 响应体
408
+
409
+ 最容易踩的一处。`--json` 时 `body` 默认截断到 64 KiB,并给出 `body_truncated: true`:
410
+
411
+ ```bash
412
+ ipclick fetch <URL> --json --max-body 0 # 不截断
413
+ ipclick fetch <URL> -o page.html # 写文件,不截断
414
+ ipclick fetch <URL> > page.html # 不加 --json:正文进 stdout、元信息进 stderr
415
+ ```
416
+
417
+ 响应不是合法 UTF-8(图片、压缩流)时 `body_encoding` 是 `base64`。小的完整给出,
418
+ 超过上限的**给空串**——半截 base64 解不出任何东西,这时用 `-o` 存文件。
419
+
420
+ ### 给 AI 代理装技能包
421
+
422
+ ```bash
423
+ ipclick skill install # 写到 .claude/skills/ipclick/SKILL.md
424
+ ipclick skill show # 打印全文
425
+ ipclick skill path # 只打印路径
426
+ ```
427
+
428
+ 技能包是一份随 wheel 分发的 Markdown,讲清楚什么时候该用 IPClick、上面那套输出契约、
429
+ 以及几个最容易踩的坑。装完之后直接对代理说"用 ipclick 抓一下 …"即可,不必再逐条解释。
430
+ Web 端的 `/skill` 页也能看全文、复制命令、下载 `SKILL.md`。
431
+
432
+ 升级 IPClick 之后重装一次,用法说明会跟着版本走;已经存在且被改过的副本不会被覆盖,
433
+ 要覆盖加 `--force`。
434
+
435
+ ## 📂 项目结构
436
+
437
+ ```
438
+ IPClick/
439
+ ├── src/
440
+ │ └── ipclick/
441
+ │ ├── __init__.py # 包入口,导出公共 API
442
+ │ ├── __main__.py # 模块入口
443
+ │ ├── sdk.py # 同步 SDK 客户端
444
+ │ ├── aio.py # 异步 SDK 客户端(grpc.aio)
445
+ │ ├── server.py # gRPC 服务端实现
446
+ │ ├── auth.py # 服务端令牌鉴权拦截器
447
+ │ ├── health.py # grpc.health.v1 健康检查
448
+ │ ├── trace.py # 链路记录(内存环形缓冲 + 可选 SQLite)
449
+ │ ├── compression.py # 请求压缩策略(自动化脚本压缩收益最大)
450
+ │ ├── components.py # 五个可选 extras 的清单、两级安装状态、分类
451
+ │ ├── skill.py # 随包分发的 AI 技能包(CLI 与 Web 端共用)
452
+ │ ├── ports.py # 默认端口的唯一事实来源(9528 gRPC / 9527 Web)
453
+ │ ├── tls.py # TLS / mTLS 配置与凭据构造
454
+ │ ├── secrets.py # 机密只从 .env / 环境变量读,不进 toml
455
+ │ ├── resume.py # 断点续传(Range 请求)
456
+ │ ├── factory.py # 按 [GENERAL].mode 选单机或集群客户端
457
+ │ ├── limiter.py # 按 host 的并发与 QPS 闸门
458
+ │ ├── exceptions.py # 异常层次
459
+ │ ├── py.typed # 类型标注标记
460
+ │ ├── adapters/ # 下载器适配器
461
+ │ │ ├── base.py # 适配器基类、重试装饰器、脚本与导航错误分类
462
+ │ │ ├── settings.py # [DOWNLOADER] 配置
463
+ │ │ ├── browser_settings.py # [BROWSER] 配置
464
+ │ │ ├── browser_engines.py # 引擎选择、两级安装检测、启动
465
+ │ │ ├── curl_cffi_adapter.py # 默认适配器(唯一有指纹伪装的)
466
+ │ │ ├── niquests_adapter.py # HTTP/2 + HTTP/3
467
+ │ │ ├── browser_adapter.py # playwright / patchright / camoufox
468
+ │ │ ├── drission_adapter.py # DrissionPage(CDP 直连)
469
+ │ │ └── registry.py # 适配器注册表(含已移除适配器的指引)
470
+ │ ├── cluster/ # 集群
471
+ │ │ ├── node.py # 节点模型与 [CLUSTER] 配置
472
+ │ │ ├── balancer.py # 轮询 / 随机 / 加权
473
+ │ │ ├── pool.py # 节点池与健康探测
474
+ │ │ ├── client.py # ClusterDownloader(客户端分发 + 故障转移)
475
+ │ │ ├── forwarder.py # ForwardingTaskService(服务端转发)
476
+ │ │ ├── tokens.py # 由共享密钥派生每节点独立令牌
477
+ │ │ ├── discovery.py # static / dns 节点发现
478
+ │ │ ├── probe.py # 节点探测:连得上吗、鉴权配对吗
479
+ │ │ └── status_page.py # 只读状态页
480
+ │ ├── web/ # Web 管理端(仅标准库,无前端构建链路)
481
+ │ │ ├── server.py # HTTP 服务、会话、CSRF、路由、CSP
482
+ │ │ ├── pages.py # 各页面的数据与操作
483
+ │ │ ├── templates.py # HTML 渲染(每处插值都要过 esc)
484
+ │ │ ├── assets.py # CSS 与 JS(CSP 用脚本哈希放行)
485
+ │ │ ├── installer.py # 装 / 卸可选组件(白名单 + 后台任务 + 进度解析)
486
+ │ │ ├── deploy.py # 为子节点生成 toml / .env / 启动命令 / zip
487
+ │ │ ├── curl_parser.py # 把 curl 命令解析成「试一试」表单
488
+ │ │ ├── editable.py # 可编辑配置项白名单(按分页归组)
489
+ │ │ └── auth.py # 凭据、会话、登录限速
490
+ │ ├── skills/ipclick/ # SKILL.md 本体(随 wheel 分发)
491
+ │ ├── cli/ # 命令行工具
492
+ │ │ ├── main.py # 部署命令:init / run / health / config-info
493
+ │ │ ├── agent.py # 给程序 / AI 调用:fetch / status / trace / node / …
494
+ │ │ ├── output.py # JSON 输出契约与退出码分类
495
+ │ │ └── skill_cmd.py # ipclick skill show / install / path
496
+ │ ├── config_loader/ # 配置加载
497
+ │ │ ├── loader.py # 优先级与环境变量覆盖
498
+ │ │ ├── dotenv.py # .env 解析
499
+ │ │ ├── placeholders.py # 路径里的 {port} 占位符
500
+ │ │ └── writer.py # 写回 toml(定点替换,保留注释)
501
+ │ ├── configs/ # 默认配置文件
502
+ │ ├── dto/ # 数据传输对象
503
+ │ │ ├── models.py # 数据模型定义
504
+ │ │ ├── response.py # 统一响应对象
505
+ │ │ └── proto/ # Protobuf 定义与生成脚本
506
+ │ ├── services/ # gRPC 服务实现
507
+ │ └── utils/ # 工具模块(日志、配置、URL 安全校验等)
508
+ │ └── module_probe.py # 不执行模块代码的"装没装"探测(find_spec)
509
+ ├── docker/ # Docker 相关文件
510
+ ├── tests/ # 测试代码
511
+ ├── pyproject.toml # 项目配置
512
+ └── README.md
513
+ ```
514
+
515
+ ## 🐳 Docker
516
+
517
+ ```bash
518
+ docker build -f docker/Dockerfile -t ipclick:latest .
519
+ docker run -d -p 9528:9528 --name ipclick ipclick:latest # gRPC
520
+ docker run -d -p 9528:9528 -p 9527:9527 --name ipclick ipclick:latest # 再带上 Web 管理端
521
+ ```
522
+
523
+ 镜像多阶段构建、非 root 运行。详细说明见 [`docker/构建.md`](docker/构建.md)。
524
+
525
+ ## 🚧 已知限制
526
+
527
+ 配置文件里出现、但改了不会有效果的项,这里如实列出。
528
+
529
+ - **服务发现只支持 `static` 与 `dns`**。etcd / Consul 的原生 API 没接(Consul 可以走它的 DNS 接口)。
530
+ - **分块下载与临时存储**未实现。流式通路已经就绪,这两项可以在其上做,目前还没做。
531
+ - **集群流式只有建流这一步会故障转移**。流建立之后中途断掉不会自动重连——那需要
532
+ Range 请求才能不重复数据。批量请求整批发给同一个节点,不跨节点拆分。
533
+ - **没有文件上传字段**。要发 multipart 就自己拼好请求体,用 `data=<bytes>` 加上
534
+ `Content-Type: multipart/form-data; boundary=...` 发出去。
535
+ - **请求之间不共享 cookie jar**,每次请求相互独立。
536
+
537
+ ## 🛠️ 开发
538
+
539
+ ```bash
540
+ uv sync --all-groups # 安装含开发依赖
541
+ uv run pytest # 运行测试
542
+ uv run pytest --cov=ipclick # 带覆盖率
543
+ uv run ruff check src/ tests/ # 代码检查
544
+ uv run ruff format src/ tests/# 代码格式化
545
+ uv run basedpyright src/ # 类型检查
546
+ ```
547
+
548
+ 修改 `src/ipclick/dto/proto/task.proto` 后需重新生成代码:
549
+
550
+ ```bash
551
+ uv run python src/ipclick/dto/proto/generate.py
552
+ ```
553
+
554
+ ## 🤝 贡献
555
+
556
+ 欢迎贡献代码、报告问题或提出建议!请通过 [GitHub Issues](https://github.com/yuanqimanong/IPClick/issues)
557
+ 或 [Pull Requests](https://github.com/yuanqimanong/IPClick/pulls) 参与项目开发。
558
+
559
+ ## 📄 许可证
560
+
561
+ 本项目采用 [MIT License](https://github.com/yuanqimanong/IPClick/blob/master/LICENSE) 开源许可证。
562
+
563
+ Copyright (c) 2025 元气码农少女酱
564
+