-
Notifications
You must be signed in to change notification settings - Fork 0
Quick Start
HadesTop edited this page Aug 14, 2026
·
3 revisions
ipclick init在当前目录生成两个文件:
-
ipclick.toml—— 行为配置。带完整注释,应该进版本库。 -
.env—— 机密。600 权限,预填一个随机 Web 密码,绝不能进版本库 (init会自动追加到.gitignore)。
只想看模板不落盘:
ipclick -e toml # 输出 ipclick.toml 模板到 stdout
ipclick -e env # 输出 .env 模板ipclick run # 只有 gRPC
ipclick run -w # 再带上 Web 管理端
ipclick run -p 9527 --host 0.0.0.0
ipclick run -c /path/to/other.toml启动日志会告诉你几件要紧的事:
IPClick server started on 127.0.0.1:9527 with 100 workers
传输层:未启用(明文)
未配置鉴权令牌,任何能连到本端口的调用方都可以使用本服务。
这两条告警是有意的:明文 + 无鉴权的服务端如果监听在非回环地址,任何人都能拿它当 跳板。见安全。
带 -w 时还会打出 Web 管理端地址与登录信息:
==============================================================
IPClick Web 管理端: http://127.0.0.1:9530/
用户名: admin
密码: N8mSKkdPbpiGzB128At3
⚠️ 该密码为本次启动随机生成,重启后失效。
==============================================================
没配密码就随机生成,而不是给个 admin/admin —— 默认弱口令是这类管理界面被打穿的
头号原因。要固定下来就写进 .env。
from ipclick import Downloader
with Downloader() as d: # 不配置就连 127.0.0.1:9527
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=9527, 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")# 流式:响应体不进内存
s = d.stream("https://example.com/big.zip")
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.resumed)
# 批量:一次 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())接口与同步版一一对应。注意服务端仍是同步的(一请求一线程),异步只在客户端这一侧。
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 参考。