Skip to content

Quick Start

HadesTop edited this page Aug 21, 2026 · 3 revisions

快速开始

1. 生成配置

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 模板

2. 起服务端

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

3. 发第一个请求

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")

4. 常用请求形态

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")

5. 大文件与批量

# 流式:响应体不进内存。一定要用 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 对应,不能靠顺序

6. 异步

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(实验性),见性能与容量

7. 错误怎么表现

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 参考

下一步

Clone this wiki locally