-
Notifications
You must be signed in to change notification settings - Fork 0
Quick Start
HadesTop edited this page Aug 21, 2026
·
3 revisions
ipclick init在当前目录生成两个文件:
-
ipclick.toml—— 行为配置。带完整注释,应该进版本库。 -
.env—— 机密。预填一个随机 Web 密码,绝不能进版本库。 POSIX 上会设成 600 权限;Windows 上权限由 NTFS ACL 决定,init不做额外收紧, 请自己确认目录权限。.gitignore只在已存在时才会被追加.env一行——干净的新目录里init只会打一句 提醒,不会替你创建.gitignore,这种情况得自己加。
只想看模板不落盘:
ipclick -e toml # 输出 ipclick.toml 模板到 stdout
ipclick -e env # 输出 .env 模板ipclick run # 只有 gRPC
ipclick run -w # 再带上 Web 管理端
ipclick run -p 9528 --host 0.0.0.0
ipclick run -c /path/to/other.toml启动日志会告诉你几件要紧的事:
IPClick server started on [::]:9528 with 100 workers
传输层:未启用(明文)
未配置鉴权令牌,任何能连到本端口的调用方都可以使用本服务。
服务端监听 [::] 但未启用 TLS,链路为明文——鉴权令牌会被同网段嗅探到。
默认就监听全部网卡([SERVER].host = "[::]"),不是只对本机。所以这三条告警在
默认部署下都会出现,它们是有意的:明文 + 无鉴权的服务端只要网络可达,任何人都能拿它当
跳板。要只对本机开放就显式设 [SERVER].host = "127.0.0.1"。见安全。
带 -w 时还会打出 Web 管理端地址与登录信息:
照上面第 1 步跑过 init 的话,密码已经在 .env 里,横幅不会再打印它:
==============================================================
IPClick Web 管理端: http://127.0.0.1:9527/
用户名: admin
密码: (取自环境变量或配置文件,此处不再打印)
==============================================================
没跑过 init、或者把密码清空了,才会随机生成一个并打印出来:
密码: N8mSKkdPbpiGzB128At3
⚠️ 该密码为本次启动随机生成,重启后失效。
没配密码时随机生成,而不是给个 admin/admin —— 默认弱口令是这类管理界面被打穿的
头号原因。要固定下来就写进 .env。
from ipclick import Downloader
with Downloader() as d: # 不配置就连 127.0.0.1:9528
resp = d.get("https://example.com")
print(resp.status_code) # 200
print(resp.text[:100])
print(resp.trace.node_id) # 谁执行的
print(resp.trace.adapter) # 实际用了哪个适配器
print(resp.trace.attempts) # 内部重试了几次连远程服务端:
Downloader(host="10.0.0.1", port=9528, token="...")或者交给配置决定(推荐,代码里不写死地址):
from ipclick import create_client
with create_client() as d: # 按 [GENERAL].mode 返回单机或集群客户端
resp = d.get("https://example.com")with Downloader() as d:
# 带参数、头、cookie
d.get("https://api.example.com/search",
params={"q": "关键词", "page": 2},
headers={"Referer": "https://example.com/"},
cookies={"session": "abc"})
# POST JSON / 表单 / 任意二进制
d.post("https://api.example.com/items", json={"name": "x"})
d.post("https://api.example.com/form", data={"a": 1})
d.post("https://api.example.com/upload",
data=open("x.bin", "rb").read(),
headers={"Content-Type": "application/octet-stream"})
# 换适配器
d.get("https://example.com", adapter="niquests") # HTTP/3
d.get("https://example.com", adapter="browser") # 浏览器渲染,引擎由服务端定
# 哪些状态码算"正常"(不触发重试)
d.get("https://example.com/maybe-404", allowed_status_codes=[200, 404])
# 走代理
d.get("https://example.com", proxy=True) # 用 [PROXY] 配置
d.get("https://example.com", proxy="http://user:pw@host:8080")# 流式:响应体不进内存。一定要用 with——提前 break 时它会取消服务端那次抓取
with d.stream("https://example.com/big.zip") as s:
print(s.status_code, s.content_length)
with open("big.zip", "wb") as f:
for chunk in s:
f.write(chunk)
# 断点续传(自动 Range + If-Range)
from ipclick.resume import download_to_file
result = download_to_file(d, "https://example.com/big.zip", "big.zip")
print(result.total_bytes, result.attempts, result.restarts)
# 批量:一次 RPC,按完成顺序返回
from ipclick import DownloadTask
tasks = [DownloadTask(uuid=u, url=u) for u in urls]
for resp in d.batch(tasks):
print(resp.request_uuid, resp.status_code) # 靠 uuid 对应,不能靠顺序import asyncio
from ipclick.aio import AsyncDownloader
async def main():
async with AsyncDownloader() as d:
resp = await d.get("https://example.com")
print(resp.status_code)
asyncio.run(main())接口与同步版一一对应。
服务端默认是一请求一线程,异步只在客户端这一侧;想让服务端也跑协程就开
[SERVER].async_mode(实验性),见性能与容量。
resp = d.get("https://does-not-exist.invalid/")
print(resp.status_code) # -1
print(resp.error) # 具体的连接错误只有瞬时传输故障返回 -1。 其余一律抛异常:
from ipclick.exceptions import ValidationError, AdapterError, AuthenticationError
d.get("ftp://example.com") # ValidationError:协议不允许
d.get("https://x.com", adapter="nope") # ValidationError:适配器名拼错
d.get("https://x.com", adapter="camoufox") # AdapterError:服务端没装这个引擎
Downloader(token="wrong").get("https://x.com") # AuthenticationError这条分界是刻意的:把用法错误伪装成网络故障,会让人去查网络,而实际要改的是代码或部署。 完整异常表见 API 参考。