Files
2026-10-06 18:54:03 +08:00

82 KiB
Raw Permalink Blame History

ProxyCat 接口文档

本文是 ProxyCat 对外 HTTP 接口的唯一权威文档,覆盖两套接口:

  • 面板接口(/api/*)—— 面板自身的接口,由 app.py 提供。
  • 代理池接口(/api/pool/*)—— 由 modules/proxypool_api.py 注册的 Flask 蓝图。

两套接口共用同一个端口、同一套鉴权,但错误响应的约定不同(面板多为 HTTP 200 + status: "error",代理池用真实状态码 + error_code)。两套约定都在本文中 写明,调用前请先读 一、通用约定。

历史版本把代理池接口的字段细节放在 modules/proxypool/API.md,该文件现已并入本文, 只剩一个指向本页的指针。

目录

分组导航

组 端点数 说明
1. 通用约定 — 地址、鉴权、两套错误约定、参数解析规则、通用上限
2. 面板接口 33 面板自身的 /api/*,含状态、配置、日志、访问记录
3. 代理池接口 38 代理池的 /api/pool/*,含抓取、验证、任务与数据库维护

按用途快速定位:

我想…… 去这里
看服务当前在用什么代理 获取运行状态
改配置 保存配置
看某个请求为什么失败 查询访问记录
看某个域名走哪个上游成功率如何 查询域名统计
拿一个可用代理给别的程序用 取一个代理
手动触发一次抓取 立即执行插件
看一个跑得久的任务到哪了 查询任务状态

一、通用约定

服务地址与端口

  • 默认地址:http://localhost:5001,端口取自 config/config.ini 的 [Server] web_port。
  • 面板与代理池没有各自独立的进程或端口,都运行在 ProxyCat 进程内,随进程一起启停。
  • 代理监听端口(默认 1080)是另一个端口,只接受 HTTP 与 SOCKS5 代理请求, 不提供任何 HTTP 接口。

鉴权

除下文明确标注「免鉴权」的端点外,所有接口都需要带上 token:

GET http://localhost:5001/api/status?token=<你的token>

token 在 config/config.ini 的 [Server] token。校验只读查询串参数 token, 不读请求头,也不读 Cookie。

token 留空时一律放行,即不启用鉴权。此时任何能连上面板端口的人都可以读写配置 (包括上游凭据)、查看访问记录、启停服务。仅在本机使用时可这样配置;要暴露到网络上 必须设置 token。

仓库自带的 config/config.ini 里 token 非空,克隆下来直接用会得到 401。 见 README 的快速部署。

免鉴权的 API 端点只有 4 个(/ 与 /static/<path> 也不校验 token,见附录 B):

方法 路径
GET /api/version
GET /api/ads
POST /api/ads/dismiss
POST /api/ads/reopen

鉴权失败统一返回 HTTP 401:

{
  "status": "error",
  "message": "无效的访问令牌"
}

两套错误约定

这是本文最容易踩的地方:两套接口的错误表达方式不同,必须分别对待。

情形 面板 /api/* 代理池 /api/pool/*
成功 200 + status: "success";少数只读端点直接返回数据、没有 status 字段 200 + 数据;错误时才带 status
业务失败 200 + status: "error" + message 真实状态码(4xx/5xx)+ status: "error" + message + error_code
参数错误 400,仅 POST /api/config 会这样返回 400 + error_code: "invalid_parameter"
未鉴权 401 + status: "error" 401 + status: "error"(同一个钩子)
池未运行 / 池太慢 不适用 503 pool_unavailable / 504 pool_timeout

由此得出两条硬规则:

  1. 永远同时判断 HTTP 状态码与 status 字段。 面板侧只看状态码会漏掉业务失败, 只看 status 又会漏掉鉴权与池侧错误。
  2. 程序化分支只能依赖 error_code(池侧)或 HTTP 状态码 + 端点自有标志(面板侧), 绝不能用 message。 message 的文案跟随当前界面语言([Server] language), 同一个错误在不同语言下返回不同文本。

参数解析规则(仅代理池接口)

代理池适配层按处理函数的签名解析参数,规则如下:

规则 说明
查询参数是白名单 只有出现在处理函数签名里的查询参数才会被解析,未知参数被静默忽略。写错参数名不会报错,只会得到未筛选的全量结果
类型强制 依据类型注解,只识别 bool / int / float,其余按字符串原样传递
空串 非字符串类型的参数传空串(如 ?status=)等同于不传该参数
请求体 必须是 JSON 对象;缺失必填字段返回 400 invalid_parameter,并在 message 里列出字段名
路径参数 如 /api/pool/proxies/{id}/favorite 中的 {id},由路由本身解析

通用上限与截断

位置 上限 / 默认 说明
GET /api/logs 的 limit 钳制到 1-2000,默认 200 超出被静默钳制,不报错
GET /api/logs/file 的 lines 钳制到 1-5000,默认 500 同上
GET /api/logs/records 的 limit 钳制到 1-500,默认 100 同上;since 默认「1 小时前」
访问记录导出 单次最多 50000 条 被截断的标记只有 JSON 格式的元信息里有(truncated,条数达到上限即为 true),CSV 没有该标记
代理池 page_size / limit / 导出 统一受 50000 约束 limit 存在时 page 被强制为 1,等效「取前 limit 条」
GET /api/version 只读缓存,不发起请求 检查由进程启动时与每 24 小时的后台任务完成
POST /api/version/check 会阻塞当前 Web 线程至本次检查结束(最长约 15 秒) 并发调用合并到正在跑的那一次;会重置 24 小时计时

阻塞与并发

面板由 waitress 以 16 个线程提供服务。每次调用代理池,当前 Web 线程会一直被占用 到池返回结果,最长可达 60 秒(HEAVY 档)。因此 16 个并发的大批量操作就会把面板 拖到无响应。调用重接口请控制并发,并给客户端留足够长的超时。

代理池的三档内部超时(会体现在错误码上):

档 时限 用于
QUERY 10 秒 查询、统计、状态
MUTATION 20 秒 启停、单条修改
HEAVY 60 秒 导入、导出、数据库优化、手动执行插件、批量验证/删除

二、面板接口

页面与静态资源

打开面板(GET /web)

鉴权:需要 token。

GET /web 返回面板单页。token 校验与其他接口一致,所以设置了 token 时, 直接访问 http://127.0.0.1:5001/ 会在跳转后得到 401。正确地址是:

http://127.0.0.1:5001/?token=<你的token>

GET / 本身不校验,只把 ?token= 原样带上去跳转到 /web:

请求 响应
GET /?token=abc 302 → /web?token=abc
GET /(无 token) 302 → /web

GET /static/<path> 提供 web/static/ 下的前端资源。带 ?v=<版本> 查询参数的请求 返回 Cache-Control: public, max-age=31536000, immutable(页面里的每个静态引用都带该 参数,改版后参数变化、浏览器自然取新文件);不带参数的请求返回 Cache-Control: no-cache, 每次回源校验。

运行状态

获取运行状态(GET /api/status)

无请求参数。返回出口代理服务的实时快照。

{
  "current_proxy": "http://1.2.3.4:8080",
  "mode": "cycle",
  "port": 1080,
  "interval": 300,
  "time_left": 123.4,
  "switch_countdown_mode": "time",
  "total_proxies": 10,
  "active_proxies": [
    { "url": "http://1.2.3.4:8080", "inflight": 3, "active": 2, "capacity": 0, "failures": 0, "suspect": false, "failure_ratio": 0.0, "last_used": 12.5 }
  ],
  "proxy_source_mode": "local",
  "exit_count": 5,
  "elastic_active": true,
  "auth_required": false,
  "display_level": 1,
  "service_status": "running",
  "language": "cn",
  "request_interval": 0,
  "pool_running": true,
  "source_error": null
}
字段 说明
current_proxy 最近一次分配出去的上游出口(已脱敏),不是「当前那个代理」—— 多出口下请求是按负载摊到整组出口上的;本地来源没有可用出口时是该语言的「无」文案,非本地来源此时为空串。整组状态见 active_proxies
mode 运行模式。同一个键在两种来源下是两件事:本地来源问「挑谁」——cycle(按清单顺序,忽略负载)/ loadbalance(谁压力小先用谁);API 与维护池问「什么时候换」——continuous(持续更换)/ request(触发更换)。按来源归一后的值,换来源后不属于新那组的值会退回该组默认值
port 实际绑定的代理端口(取自监听套接字),服务未运行时回落到配置值
interval 时间轮换间隔(秒)
time_left 最近到期的那个出口还剩多久,单位随 switch_countdown_mode 变化;名册为空时为 0。改出口由请求驱动还是后台主动做,取决于 mode:continuous(持续更换)下后台每拍都在换,空闲时这个数照样走;request(触发更换)与本地来源下空闲时到期的出口会被移除,池子可能为空
switch_countdown_mode 倒计时口径:time(秒,按出口各自的寿命;interval 与 request_interval 都填时按这个报 —— 秒数能画进度条,剩余请求数由每个出口的 requests_left 单独给出)/ request_count(只填了 request_interval,报还剩多少次请求)/ per_request(两项都没填,只在缺人或失效时补货)。不会再返回 none:寿命对所有来源、四种运行模式都生效
total_proxies 当前在用的出口条数。目标是 exit_count([Server] exit_count),某个出口被判失效、或还没补上时这个数会小于目标;高峰自动扩容期间会大于它(同时 elastic_active 为真)
active_proxies 各上游出口的实时状态:url(已脱敏)、active 正在用这个出口的连接数(请求在途 + 最近有中继数据的隧道,面板与 CLI 的「在用」取它)、inflight 还占着名额的连接数(含空闲隧道,恒 ≥ active)、capacity 单出口活跃连接额度(0 表示不限制,空闲隧道不计入)、failures 连续失败次数、suspect 近期失败是否居多(居多的会被优先让路,但仍然可用)、failure_ratio 近期失败占比、expires_in 剩余寿命秒数、requests_left 剩余可服务的请求数(两个都是 null 表示没有启用该口径)、last_used / added_at / expires_at / requests_served(time.monotonic() 与计数,仅供同进程内比较)
proxy_source_mode 代理来源:local / api / pool
exit_count 出口池的目标容量([Server] exit_count),与 total_proxies 成对读。换的时机由运行模式决定:持续更换(continuous)模式下空闲时也保持这么多;触发更换(request)与本地来源由请求驱动,空闲时到期的出口会被移除、池子可能空掉,下一个请求到达时才重新取货填满
elastic_active 出口数是否高于目标:突发扩容/高峰自动扩容中,或扩容已停止但多出来的出口还没到寿命(它们用满自己的寿命才逐个撤下)
auth_required 出口代理是否要求用户名密码认证
display_level 控制台详细程度([Server] display_level)
service_status running / stopped
language 当前界面语言
request_interval 按请求数切换的阈值(分配次数,不是「服务成功」的次数)。它与 interval 同时生效,谁先到谁换
pool_running 代理池是否在运行
source_error 最近一次从来源取出口失败的原因与时间({"message": ..., "at": ...});没有失败时为 null

配置管理

读取配置(GET /api/config)

返回 [Server] 与 [Pool] 两段的全部键值。

{
  "status": "success",
  "server": { "port": "1080", "web_port": "5001", "mode": "cycle" },
  "pool": { "validator.timeout_seconds": "10" }
}
  • token 键已被剔除,不会出现在响应里 —— 面板不需要回显它,避免它出现在页面源码、 浏览器缓存或前端日志中。
  • [Users] 与 [api_credentials] 不在这里返回,各有独立端点。
  • 布尔项已被归一化成 "true" / "false":config.ini 里写 yes / on / 1 在运行期 都算真,而界面只认 "true",不归一化会让复选框显示成未勾选 —— 用户随手保存一次就把 开关关掉了。

保存配置(POST /api/config)

请求体中的两段都可省略,但至少要有一段非空:

{
  "server": { "mode": "loadbalance", "interval": "600" },
  "pool": { "validator.timeout_seconds": "15" }
}

键名经白名单校验,未知键与非法取值会被拒绝,返回 HTTP 400。

token 也是合法的 server 键:面板不提供它的编辑入口,但本接口接受它, 因此改 token 可以走 POST /api/config(立即生效),也可以直接改 config.ini 后重启进程。

{
  "status": "success",
  "port_changed": false,
  "web_port_changed": false,
  "service_status": "running",
  "applied": { "proxy": true, "pool": true }
}
字段 说明
port_changed 代理监听端口是否变了。配置热重载本身不重新绑定端口;面板会据此自动重启出口服务(POST /api/service 的 restart)以换到新端口,用命令行入口时需重启进程
web_port_changed 面板端口是否变了。必须重启整个进程才生效,当前面板仍运行在旧端口
applied.proxy 新配置是否成功推送给出口代理服务
applied.pool 新配置是否成功推送给代理池

applied 里的某一项为 false 表示配置已落盘、但推送失败,应答本身仍是 success。 此时应以 applied 为准判断是否真的生效。

校验失败时(HTTP 400)除 message 外还带 details,指明是哪个键、以及按界面语言渲染 好的原因,面板据此把提示落到具体输入框上:

{
  "status": "error",
  "message": "配置项 port 非法(当前值: 70000):不能大于 65535",
  "details": { "key": "port", "reason": "不能大于 65535" }
}

details 在 key 与 reason_key 齐备时才会出现([Server] 与 [Pool] 的校验异常 都带这两个属性),其余异常只有 message。message 始终保留,老客户端不受影响。

保存配置时,若当前代理来源为 pool、未配置外部池地址(pool_remote_url)且池未运行, 会顺带自动拉起池服务。

语言

切换界面语言(POST /api/language)

语言是服务端设置,不是浏览器设置:它写回 config.ini 的 [Server] language, 并切换整个进程的文案(含面板、控制台横幅、接口 message、导出文件的表头与状态列)。

{ "language": "en" }
字段 类型 必填 取值
language string 否 cn / en,缺省按 cn 处理
{ "status": "success", "language": "en" }

取值不在 cn / en 之内时仍返回 HTTP 200,靠 status 判断:

{ "status": "error", "message": "不支持的语言" }

该端点走的是面板侧的「业务失败返回 200」约定,不要只看状态码。

本地代理与名单

读写本地代理列表(GET/POST /api/proxies)

GET 返回 config/ 下代理列表文件的内容。

{ "proxies": ["http://1.2.3.4:8080", "socks5://5.6.7.8:1080"] }

读失败时返回错误,不是空列表——空列表会让界面显示成「一个代理都没有」, 用户随手一保存就把代理文件清空了:

{ "status": "error", "message": "加载代理文件失败: ..." }

POST 整份覆盖写入,并立即热载入出口服务(不重新检测可用性):

{ "proxies": ["http://1.2.3.4:8080"] }

proxies 必须是数组(传字符串会被逐字符拆行、传对象只会写进键名,两种都会 把文件覆盖成垃圾),否则:

{ "status": "error", "message": "代理列表必须是数组" }

文件被其他进程占用时(Windows 上常见)可能失败:

{ "status": "error", "message": "..." }

检测代理可用性(GET /api/check_proxies)

参数 必填 默认 说明
test_url 否 https://www.baidu.com 存活检测的目标地址

对源清单逐个检测,并发数取自 [Server] check_concurrency:

  • local 来源:proxy_file 里的全部条目 —— 故意不查活名册,因为出口因失败被停用后 就从名册里消失了,而那恰恰是最需要复查的那些。
  • api / pool 来源:没有可读的本地清单,检测当前在用的出口。
{
  "status": "success",
  "valid_proxies": ["http://1.2.3.4:8080"],
  "checked": 3,
  "total": 1,
  "message": "代理检查完成,有效代理:1个"
}
字段 说明
valid_proxies 检测通过的地址
checked 本次实际检测了几个(源清单的条数)
total 检测通过的数量

检测只报告结论,不改变活名册 —— 想把新验证过的出口投入使用,等下一次批次刷新即可。

检测耗时随代理数量增长,请求可能等待较久(内部超时 30 秒)。

读写 IP 白名单 / 黑名单(GET/POST /api/ip_lists)

GET:

{ "whitelist": ["127.0.0.1"], "blacklist": ["1.2.3.4"] }

POST:

{ "type": "whitelist", "list": ["127.0.0.1"] }
字段 说明
type whitelist 或 blacklist,其他取值返回错误
list 完整名单,整份覆盖写入

写盘后立即热载入出口服务,无需重启。两份名单校验的是客户端来源 IP(连到代理端口 的那一端),在每个新连接建立时先判定,未通过直接返回 403 并记录「未授权的IP尝试访问」, 连接不会被转发。同时命中两份名单时的判定优先级由 [Server] ip_auth_priority 决定 (whitelist 放行 / blacklist 拦截)。

读写绕过名单(GET/POST /api/bypass_whitelist)

GET:

{ "list": ["192.168.0.0/16"] }

POST:

{ "list": ["192.168.0.0/16"] }

名单内的目标地址不做任何上游转发,直接由本机连接(不参与出口调度)。它与上面 按客户端来源 IP 判定的黑白名单是两回事:把某个目标加进本名单,不会跳过对客户端 IP 的黑白名单校验。

服务控制

启停出口代理服务(POST /api/service)

{ "action": "start" }
action 说明
start 服务未运行时启动;已在运行则直接返回成功
stop 停止服务
restart 先停后起
{ "status": "success", "message": "服务启动成功", "service_status": "running" }

start / stop 的响应会带 service_status(取值 running / stopped);restart 成功时不带该字段。

这个接口是串行且带超时的:启动要等旧线程退出(最多 10 秒)再等服务就绪(最多 5 秒), 停止最多等 10 秒。超时即返回 status 为 error,不会静默改用其他端口。因此调用方 应留出足够长的请求超时。

端口被占用时返回错误,且不会启动服务:

{
  "status": "error",
  "message": "端口 1080 已被其他程序占用,请更换其他端口后重试",
  "service_status": "stopped"
}

手动切换上游代理(GET /api/switch_proxy)

丢开现有候补、立刻调一次来源:池子不满就补满;启用了出口寿命(interval 或 request_interval 非 0)时,满员则换掉最近到期的那个出口(只填了 request_interval 时各出口没有时间寿命,实际换掉的是名册里的第一个)。 不是「换到下一个 IP」(所有出口本来就同时在用),也不是整批替换。响应有四种形态:

切换成功

{
  "status": "success",
  "current_proxy": "http://1.2.3.4:8080",
  "active_proxies": [
    { "url": "http://1.2.3.4:8080", "inflight": 0, "active": 0, "capacity": 0, "failures": 0, "suspect": false, "failure_ratio": 0.0, "last_used": 0.0 }
  ],
  "total_proxies": 1,
  "message": "出口批次已刷新"
}
字段 说明
current_proxy 当前出口(最近一次分配出去的出口,已脱敏);它被换掉时回退到名册里的第一个
active_proxies 当前名册的快照,字段含义同 /api/status
total_proxies 当前名册里的出口数量

「成功」按刷新后名册是否非空判定 —— 已经是满员且这次只轮换了一个出口,同样算成功。

处于冷却期(距上次切换过近,[Server] switch_cooldown 内不再切换)

{
  "status": "error",
  "cooldown": true,
  "cooldown_remaining": 12,
  "message": "..."
}

已有切换在进行中

{ "status": "error", "switching": true, "message": "..." }

切换失败

{ "status": "error", "current_proxy": "...", "message": "..." }

调用方应据 cooldown / switching 字段区分「暂时不切」与「真的失败」,两者都是 status: error。

日志

日志按用途分为四个类别:access(代理访问)、proxy(代理生命周期)、main(主程序)、 pool(代理池)。其中 access / proxy / main 三个类别有对应的日志文件,pool 只进 内存环形缓冲与 error.log,其自身文件在 modules/proxypool/logs/ 下,不通过本组接口浏览。

查询内存日志(GET /api/logs)

参数 必填 默认 说明
category 否 all all / access / proxy / main / pool,其他取值返回错误
start 否 0 从最新一条往前跳过的条数
limit 否 200 返回条数,被限制在 1-2000
level 否 ALL 按级别过滤
search 否 空 关键词过滤
{
  "logs": [
    {
      "seq": 8821,
      "time": "2026-09-30 12:00:00",
      "level": "INFO",
      "category": "main",
      "message": "..."
    }
  ],
  "total": 1234,
  "status": "success"
}
  • seq 是跨类别唯一的全局递增序号,多个类别合并返回时按它还原真实先后。 条目按时间正序排列。
  • total 是过滤后的总数,不是本次返回的条数。
  • start 的语义与常规分页相反:它是从最新一条往回数的偏移量,配 limit 用于 「翻页看更早的日志」。

日志统计(GET /api/logs/stats)

{
  "status": "success",
  "stats": {
    "total": 170,
    "levels": { "INFO": 150, "WARNING": 20 },
    "categories": { "main": 100, "access": 50, "proxy": 20, "pool": 0 },
    "capacity": { "main": 20000, "access": 5000, "proxy": 20000, "pool": 20000 },
    "first_time": "2026-09-30 10:00:00",
    "last_time": "2026-09-30 12:00:00"
  }
}

total 是四个环加起来的条数,capacity 是各环容量(access 环更小)。 first_time / last_time 在缓冲为空时不出现。

列出日志文件(GET /api/logs/files)

{
  "status": "success",
  "files": [
    { "name": "main.log", "category": "main", "size": 20480,
      "modified": "2026-10-02 15:30:00", "current": true, "rotated": false }
  ]
}
字段 说明
name 文件名(面板拿它调 /api/logs/file)
category main / proxy / access / error。error 是 ERROR 及以上的汇总文件,不能当作 GET /api/logs 的 category 参数传
size / modified 字节数 / 最后修改时间
current 是不是当前在写的那个;rotated 是它的反面(轮转出来的历史份)

只列出 logs/ 目录下的白名单文件(含轮转份),不含代理池自己的 proxy_pool.log。

读取日志文件(GET /api/logs/file)

参数 必填 默认 说明
name 是 空 文件名,取白名单内的名字
lines 否 500 读取末尾行数,被限制在 1-5000
{ "status": "success", "name": "main.log", "lines": ["..."], "truncated": true }

truncated 为 true 表示文件的行数不少于请求的行数(恰好相等时也为 true), 本次只返回了末尾部分。文件名不在白名单内返回错误(按白名单校验而非路径拼接, 杜绝目录穿越)。

导出日志(GET /api/logs/export)

参数 必填 说明
file 否 指定文件名时下载该文件的附件;省略则导出内存日志
category 否 省略 file 时生效,同 GET /api/logs
level / search 否 同上

省略 file 时返回纯文本附件,文件名为 proxycat_export.log。

清空日志(POST /api/logs/clear)

{ "category": "all" }

清空对应类别的内存环形缓冲,并截断对应的日志文件。category 为 all 时清空全部。

{ "status": "success", "message": "日志已清除" }

会删掉磁盘上的日志内容,不可恢复。

访问记录

逐条记录每一次代理请求。与「日志」的区别是它面向查询与统计:按列存储、可过滤、可导出。

查询访问记录(GET /api/logs/records)

参数 必填 默认 说明
since 否 1 小时前 起始时间,接受 YYYY-MM-DD、YYYY-MM-DD HH:MM、YYYY-MM-DD HH:MM:SS,也接受 T 分隔
until 否 不限 结束时间,粒度到分钟或日期时自动补到该时刻的最后一秒
outcome 否 不限 success / failure / aborted,其他取值返回错误
upstream 否 不限 按上游代理过滤
host 否 不限 按目标主机过滤
search 否 空 关键词过滤
limit 否 100 被限制在 1-500
offset 否 0 分页偏移
order 否 desc 时间排序方向
{
  "status": "success",
  "enabled": true,
  "range": { "since": "2026-09-30 11:00:00", "until": "" },
  "outcome_counts": { "success": 100, "failure": 3, "aborted": 1 },
  "total": 104,
  "export_limit": 50000,
  "records": [
    {
      "id": 12345,
      "ts": "2026-09-30 12:00:00",
      "kind": "http",
      "method": "GET",
      "client": "127.0.0.1/-",
      "client_ip": "127.0.0.1",
      "client_user": "-",
      "host": "example.com",
      "port": 443,
      "target": "example.com:443",
      "upstream": "http://1.2.3.4:8080",
      "upstream_label": "http://1.2.3.4:8080",
      "real_ip": "203.0.113.7",
      "outcome": "success",
      "status_code": 200,
      "elapsed_ms": 123,
      "reason": ""
    }
  ]
}
  • id 是自增主键;client(client_ip/client_user)与 target(host:port)是派生 字段,只是把相邻两列拼成界面直接可用的形态,与 client_ip / host 同源。
  • upstream 为特殊值时用 upstream_label 给出可读文案:直连(direct)与未知上游 (unknown)各有一种取值。
  • 失败(outcome=failure)时 status_code 一律为空:隧道先建好、之后才失败的行不会 带着建连那一下的 200 出现在这里(历史库里存过这种行,读取时统一收敛)。
  • real_ip 是这个出口实际用的出口 IP(目标站看到的那个),网关型出口的地址与它 不是一回事。它是展示字段:代理池来源由池收录时验证得出,API 来源在 [Server] access_records_real_ip_probe 打开时由后台探测得出,本地来源恒为空串。 探不到或没开启时是 "",界面显示成 --;直连的请求也是 ""。 它不参与出口调度、寿命与故障判定,老库里的历史记录同样是 ""。
  • 响应里另有三个落库状态字段与 records 平级返回:pending(尚未落盘的缓冲条数)、 dropped(因缓冲写满而丢弃的条数)、oldest_ts(库里最早一条记录的时间,空串表示没有)。 dropped 非零说明落盘跟不上,记录有丢失。

导出访问记录(GET /api/logs/records/export)

过滤参数与查询接口相同,额外一个:

参数 必填 默认 说明
format 否 csv csv 或 json,其他取值返回错误
  • CSV:带 UTF-8 BOM,Excel 直接打开不乱码;表头按当前界面语言渲染。
  • JSON:UTF-8,含 exported_at、过滤条件、count 与 truncated 元信息。

两种格式都受 export_limit(50000)限制;被截断的标记只有 JSON 的元信息里有 (truncated,取到的条数达到 50000 即为 true),CSV 只有正文、没有该标记。响应为 附件下载,文件名形如 access_records_20260930_120000.csv。

列出过滤候选项(GET /api/logs/records/options)

返回当前记录里出现过的上游代理与目标主机取值,供前端渲染下拉框。

{ "status": "success", "upstreams": [{ "value": "http://1.2.3.4:8080", "label": "..." }] }

清空访问记录(POST /api/logs/records/clear)

{ "status": "success", "removed": 1234, "message": "已清除 1234 条访问记录" }

不可恢复。

域名统计

按「目标域名 × 上游代理」累计请求的成败次数。与访问记录的区别:这里是聚合计数, 访问记录是逐条明细;因此访问记录可以清空而这里的累计值仍在,反之亦然。

查询域名统计(GET /api/logs/domains)

参数 必填 默认 说明
upstream 否 空 指定时下钻到该上游的域名明细,省略时按上游汇总
sort 否 last_seen 排序字段
order 否 desc 排序方向
search 否 空 关键词过滤
limit 否 100 被限制在 1-1000
offset 否 0 分页偏移

省略 upstream——按上游汇总:

{
  "status": "success",
  "enabled": true,
  "summary": { "pairs": 42, "hosts": 12, "upstreams": 5, "success": 3400, "failure": 56 },
  "mode": "proxies",
  "proxies": [
    {
      "upstream": "http://1.2.3.4:8080",
      "upstream_label": "http://1.2.3.4:8080",
      "host_count": 7,
      "success": 100,
      "failure": 2,
      "total": 102,
      "rate": 98,
      "first_seen": "2026-09-30 10:00:00",
      "last_seen": "2026-09-30 12:00:00",
      "last_error": ""
    }
  ],
  "total": 1
}

指定 upstream——下钻到域名:

{
  "status": "success",
  "enabled": true,
  "summary": { "pairs": 42, "hosts": 12, "upstreams": 5, "success": 3400, "failure": 56 },
  "mode": "domains",
  "upstream": "http://1.2.3.4:8080",
  "upstream_label": "http://1.2.3.4:8080",
  "domains": [
    {
      "host": "example.com",
      "upstream": "http://1.2.3.4:8080",
      "success": 50,
      "failure": 1,
      "first_seen": "2026-09-30 10:00:00",
      "last_seen": "2026-09-30 12:00:00",
      "last_status": 200,
      "last_error": ""
    }
  ],
  "total": 1
}
  • summary 的 pairs 是「域名 × 上游」的组合数,rate 是成功率百分比(取整)。
  • 用返回的 mode 字段区分两种形态,不要靠猜。
  • enabled 为 false 表示域名统计功能未开启([Server] domain_stats_enabled), 此时没有数据可查。

清空域名统计(POST /api/logs/domains/clear)

{ "status": "success", "removed": 42, "message": "已清除 42 个域名的访问统计" }

用户与 API 凭据

读写出口代理账号(GET/POST /api/users)

GET:

{ "status": "success", "users": { "neko": "123456" } }

读失败时返回 status: "error" 与 users: null,不是空对象——空对象会让界面 显示成「一个用户都没有」,管理员加一个用户再保存就用空表覆盖原账号,等于把出口 认证关掉。响应里是明文密码。

POST 整份覆盖 [Users] 段:

{ "users": { "neko": "123456" } }

传空对象即可关闭出口认证(账号表为空时 auth_required 为 false)。

{ "status": "success", "message": "用户保存成功" }

管理上游 API 凭据(GET/POST /api/api_credentials)

存多套「取代理接口」的凭据,并指定当前生效的一套。用 set_active 切换生效项、 或删除的正是生效项时,会同步改写 [Server] api_proxy_url / proxy_username / proxy_password,因此代理来源为 api 时会立即用上新凭据;save 不同步这三键 —— 它只更新凭据集合与生效项标记,要让新保存的值真正被代理来源使用,需要对它再调一次 set_active(或由删除生效项触发的自动切换)把三键写回 [Server]。

GET:

{
  "status": "success",
  "sets": [{ "name": "闪臣", "url": "https://...", "username": "", "password": "" }],
  "active_credential": "闪臣"
}

响应含明文凭据。

POST:

{ "action": "save", "name": "闪臣", "url": "https://...", "username": "", "password": "" }
action 需要字段 说明
save name、url、username、password 按 name 存在则更新,否则新增;name 为空返回错误。若当前没有生效项,或生效项正是这一条,则把它设为生效项
delete name 按 name 删除;若删掉的正是生效项,则把第一条剩余项设为生效项、并把它的三个键同步写回 [Server](没有剩余则清空这三键)—— 不换的话运行中的服务会继续用已删除的接口
set_active name 切换生效项,并把它的 url/username/password 写进 [Server];name 不在凭据集合里时返回错误,不会写一个「未选用」的中间态

save 与 delete 返回更新后的 sets 与 active_credential;set_active 返回 active_credential 与生效项的 credential,不返回 sets。未知 action 与空 name 返回错误({ "status": "error", "message": "..." }),set_active 的 name 不在凭据集合 里时也返回错误。save 传一个不存在的 name 是新增;delete 不校验名字是否存在, 删除一个不存在的凭据名照常返回 success(sets 原样返回)。

版本检查与广告

除 POST /api/version/check 外,本节端点无鉴权,配置了 token 也无需携带。

检查版本(GET /api/version)

返回最近一次版本检查的结果。本端点只读缓存,不会发起任何外部请求 —— 请求它永远不会产生网络等待。

{
  "status": "success",
  "is_latest": true,
  "current_version": "ProxyCat-V3.0.0",
  "latest_version": "ProxyCat-V3.0.0"
}
字段 说明
is_latest 远端版本是否不高于当前版本;每次读取时按当前的 CURRENT_VERSION 重算
current_version 本进程的版本
latest_version 最近一次检查取到的远端版本

检查的节奏由进程自己掌握,不由本端点触发:

  • 进程启动时查一次,之后每 24 小时一次。
  • 24 小时内重启不会重复检查:上次检查的时间写在 logs/version_check.json 里, 重启后读回来继续用。两个入口(app.py 与 ProxyCat.py)都是这个节奏。
  • 无论成败都记录时间,因此对外最多 24 小时发起一次请求;断网时不会再出现 「每次打开面板都要等一次超时」的情况。
  • 检查在后台线程里做,不阻塞启动,也不占用 Web 工作线程。
  • 需要立刻知道结果时用下面的 POST /api/version/check。

版本取自 GitHub 仓库的发布记录,默认并发请求三个地址,谁先返回可用的版本号就用谁:

地址 说明
github.com/…/releases.atom 官方发布源,无需认证、无频率限制
gh-proxy.com/https://api.github.com/… 第三方镜像,国内可达性更好;会看到请求 IP,且可能返回缓存的旧版本
api.github.com/repos/…/releases 官方 API,未认证时每 IP 每小时限 60 次

并发而非顺序重试:被墙或超时的地址不会拖慢整体,可用时通常 1 秒内返回;三者全部失败时 约 8 秒返回。请求沿用 httpx 默认行为,读取 HTTP(S)_PROXY 环境变量。

配置项 version_check_url 非空时只用该地址, 内置的三个源不再请求。版本号从标签里取 ProxyCat[-_]v?数字.数字[.数字] 这一段前缀, -测试版V5 之类的后缀被丢弃。

立即检查版本(POST /api/version/check)

需鉴权(与上面只读缓存的 GET 相反:本端点会真的发起外部请求)。

无参数、无请求体。当前 Web 线程会等到本次检查结束才返回(最长约 15 秒), 同一时刻只有一次检查在跑 —— 并发调用会等正在跑的那一次,而不是各自再发一轮请求。

{
  "status": "success",
  "current_version": "ProxyCat-V3.0.0",
  "latest_version": "ProxyCat-V3.0.0",
  "is_latest": true,
  "source": "github.com",
  "checked_at": "2026-10-06 18:39:42"
}
字段 说明
status 本次检查的成败,这是与 GET 的关键差别
source 本次胜出的地址主机名;仅成功时出现
checked_at 本次检查的时间
latest_version / is_latest 只要内存里有成功的版本号就会出现
message 本次失败的原因,仅失败时出现

本次检查失败、但此前成功过时,status 为 error、带 message,同时仍带上一次成功 的 latest_version。这正是它与 GET 的区别:GET 只报「最后已知状态」,本端点报「这一次 成不成」。两者都会把本次检查的时间写进 logs/version_check.json,因此手动检查同样会 重置 24 小时的计时。

既没有成功结果、也没有失败记录时(启动后第一次检查还没跑完)返回:

{
  "status": "error",
  "message": "尚未完成版本检查"
}

检查失败时(断网、远端页面结构变化)返回 status 为 error,message 说明原因; 但若此前成功过,则继续返回上一次成功的结果 —— 面板不是监控工具,展示最后已知的 版本状态比展示错误更有用。失败本身记在日志里。

升级到新版本后,即使距上次检查不足 24 小时也会立即重查:状态文件里存着 「当时检查的当前版本」,与进程里的版本不一致即视为过期。

获取广告(GET /api/ads)

读取 config/ads/ 目录下所有 *.json,返回 enabled 不为 false 的条目。 解析失败的文件静默跳过。

{
  "status": "success",
  "ads": [
    {
      "id": "example",
      "title": "...",
      "body": "...",
      "image_url": "",
      "link_url": "",
      "link_text": "",
      "display_time": 15
    }
  ],
  "total": 1,
  "dismissed": false
}

dismissed 是进程内的关闭状态,重启即丢失。

关闭 / 重新开启广告(POST /api/ads/dismiss、POST /api/ads/reopen)

两个端点都无参数、无鉴权,响应体是 { "status": "success", "dismissed": <布尔值> }, 关闭置 true、重新开启置 false:

{ "status": "success", "dismissed": true }

三、代理池接口

全部挂在 /api/pool 前缀下,鉴权与面板一致(同一个 before_request 钩子)。

池侧约定

成功响应没有统一外壳

适配层把处理函数的返回值直接序列化成响应体,因此:

  • GET /api/pool/get 返回一个 JSON 数组(不是 {"status": ..., "data": [...]})。
  • GET /api/pool/count 返回 {"count": 12},没有 status 字段。
  • GET /api/pool/random?format=text 与导出接口返回纯文本或附件,不是 JSON。
  • 只有部分端点会在返回值里自带 status 与 message(如生命周期端点);插件操作返回的是 success 与 message,没有 status 字段。

判断成功请看 HTTP 状态码与响应内容本身,不要假设一定有 status 字段。

错误响应

{
  "status": "error",
  "message": "错误描述",
  "error_code": "invalid_parameter"
}
error_code HTTP 含义
invalid_parameter 400 请求参数非法(含缺失必填字段、取值越界)
api_error 4xx / 5xx 业务错误,如代理不存在、规格不匹配。HTTP 状态码随错误自带(如插件操作失败、备份恢复失败为 500,归属地解析服务未启用为 503),不要按 4xx 写死分支
pool_running 409 池运行中,不允许该操作(仅备份恢复会返回)
pool_unavailable 503 代理池未运行或正在重启
pool_timeout 504 代理池响应超时
queue_full 503 写入队列已满,请求被拒(背压)
internal_error 500 服务端内部错误

两个例外要知道:

  • POST /api/pool/start|stop|restart 失败时返回 500 且不带 error_code, 只有 status / message / is_running。
  • message 跟随界面语言,不可用于程序判断。

池未运行时

池未启动时,除状态查询、生命周期端点、备份列表/恢复与 /schema 外,其余端点一律返回 503 pool_unavailable。调用方应先查 GET /api/pool/status 确认 is_running。

任务模型

所有耗时操作(批量验证、导入、抓取、归属地更新/重算)都立即返回 task_id, 真正的执行在后台,进度按 task_id 轮询。

  • 任务状态是进程内内存态:进程重启即全部丢失。
  • 已完成/失败的任务保留 1 小时(TTL),超过即被淘汰;状态表上限 200 条。
  • 创建时返回的 status(queued / running)只是创建瞬间的快照,不代表执行结果; 任务表里的条目从创建起就直接写 running,不存在 queued 状态的条目。
  • 任务表里实际出现的状态:running / completed / failed / cancelled (queued 只在部分创建响应里出现,用它过滤任务永远筛不到条目)。

没有上报使用反馈的接口

「使用期反馈」是出口代理在真实转发过程中在进程内部回报给池的(见 功能特性里的「代理池机制」),没有任何 HTTP 端点可以上报使用结果, 不必去找。

池状态与生命周期

获取池状态(GET /api/pool/status)

{
  "status": "success",
  "is_running": true,
  "last_error": null,
  "stats": {
    "total_proxies": 120,
    "valid_proxies": 86,
    "invalid_proxies": 34,
    "anonymity_fallback_proxies": 5,
    "protocol_distribution": { "http": 60, "socks5": 26 },
    "region_distribution": { "中国": 40, "美国": 12 }
  }
}
字段 说明
is_running 池是否在运行
last_error 最近一次错误文案,无错误时为 null
stats 全局统计;池未运行时为 null。统计本身失败也不影响本端点返回成功,只是 stats 为 null
anonymity_fallback_proxies 匿名度被判成兜底档的条数
region_distribution 只取数量最多的前 10 个地区

获取池配置字段结构(GET /api/pool/schema)

返回 [Pool] 段的字段分组、类型、说明与取值区间,面板的「池设置」表单就是按它渲染的。 文案跟随当前界面语言。

{
  "status": "success",
  "groups": [
    {
      "title": "数据库",
      "sub": "数据存在哪、怎么备份",
      "id": "database",
      "fields": [
        { "key": "database.path", "doc": "代理池所有数据存放的文件(SQLite 格式)", "type": "text",
          "title": "数据库文件路径", "hint": "改动需重启池服务;相对路径从 proxypool 目录起算" },
        { "key": "database.pool_max_readers", "doc": "...", "type": "int", "low": 1, "high": 64,
          "title": "读连接数上限", "tier": "advanced" },
        { "key": "database.backup_interval_hours", "doc": "...", "type": "int", "low": 1,
          "title": "备份间隔", "unit": "小时", "tier": "advanced", "visible_when": "database.backup_enabled=true" }
      ]
    }
  ]
}
字段 说明
type 字段类型,取值 bool / int / float / list / text(字符串字段一律是 text,没有 str)
doc 完整说明,与 config.ini 里的注释逐字一致
title 短标题,直接当表单的字段标签用
hint 控件下方的一行提示;是否出现取决于字段是否写了提示
unit 计量单位,由面板渲染在输入框右侧。单位不写进 title,语言包无对应值时该键不输出
tier 为 "advanced" 时归入「高级选项」折叠区
visible_when 声明式显隐规则 键=值1|值2;键2!=值(分号是「与」、竖线是「或」)。带此规则的字段内联显示、不折叠。隐藏只影响显示,字段照常收集与保存
sub(分组级) 分组副标题,显示在卡片标题右侧

low / high 只在配置项有取值区间时出现。本端点不返回当前值 —— 当前值走 GET /api/config 的 pool 字段。

启动池(POST /api/pool/start)

无参数。

{ "status": "success", "message": "维护池启动成功", "is_running": true }

停止池(POST /api/pool/stop)

无参数。会等写队列排空,可能耗时数秒。

{ "status": "success", "message": "维护池停止成功", "is_running": false }

重启池(POST /api/pool/restart)

无参数。先停后起,停止与启动各最多等 30 秒。

失败时(三个生命周期端点相同):

{ "status": "error", "message": "...", "is_running": false }

HTTP 状态码为 500,且不带 error_code。

代理查询与单条操作

查询代理列表(GET /api/pool/get)

返回一个 JSON 数组,元素为代理对象。

参数 类型 默认 取值与语义
protocol string 不限 http / https / socks5,其他取值返回 400
region string 不限 归属地模糊匹配(中、英两列都匹配);以 - 开头表示排除,如 -美国 表示排除归属地含「美国」的代理
min_delay int 不限 最小延迟(毫秒),负数报错
max_delay int 不限 最大延迟(毫秒),负数报错;与 min_delay 顺序颠倒报错
status string 不限 valid / invalid,其他取值返回 400
source string 不限 来源插件名
sort_by string delay_ms 见下方白名单
sort_order string asc asc / desc
limit int 不限 只取前 N 条;设置后 page 被强制为 1。上限 50000
page int 1 页码,必须 ≥ 1
page_size int 50 每页条数,1 到 50000
format string json json 或 text;text 返回每行一个代理地址的纯文本
ip string 不限 按 IP 模糊匹配
is_favorite bool 不限 只看收藏 / 只看未收藏
anonymity_level string 不限 transparent / anonymous / elite / unverified
min_health_score float 不限 最低健康分,负数报错
supports_https bool 不限 只看支持 / 不支持 HTTPS 的代理

sort_by 白名单:delay_ms、protocol、region、validated_at、created_at、 health_score、anonymity_level、success_count、total_checks、avg_delay_ms、 failure_count、is_favorite、supports_https、success_rate、region_en。

写错 sort_by 不会报错:不在白名单内会静默回落到 delay_ms;sort_order 不是 asc / desc 时静默回落到 asc。排序结果不对时先检查参数名拼写。

未知查询参数被静默忽略(见 参数解析规则)。 参数名写错时不会报错,只会返回未筛选的结果。

取一个代理(GET /api/pool/random)

随机返回一个符合条件的代理,筛选参数与 /get 相同(没有 sort_by / sort_order / limit / page / page_size),另有:

参数 类型 默认 说明
format string json json 返回完整代理对象;text 只返回 协议://ip:端口 一行
{
  "id": 42,
  "proxy_url": "http://1.2.3.4:8080",
  "protocol": "http",
  "ip": "1.2.3.4",
  "port": 8080,
  "delay_ms": 320.5,
  "health_score": 87.3,
  "anonymity_level": "elite",
  "region": "中国 广东 深圳"
}

(上例为节选,完整字段见附录 C。)

纯文本形态的 format=text 响应体就是一行:

http://1.2.3.4:8080

错误与边界:

  • 没有符合条件的代理 → 404 api_error。
  • format 不是 json / text → 400 api_error。

随机不是严格等概率:实现是「先计数、随机挑一页(每页 100 条)、再随机挑一条」, 以页为粒度。池子很小时各条被取到的概率略有差异,但对取用没有实际影响。

这个端点的 format=text 正是 pool_remote_url 期望的响应形态 —— 可以用另一个 ProxyCat 实例的 /api/pool/random?format=text&token=... 作为外部池地址。

统计代理数量(GET /api/pool/count)

筛选参数与 /get 相同(去掉排序与分页)。

{ "count": 86 }

导出代理(GET /api/pool/export)

筛选参数与 /get 相同(去掉排序与分页),另有:

参数 类型 默认 说明
format string txt txt / json / csv,其他取值返回 400
  • txt:每行一个代理地址,附件名 proxies_YYYYMMDD_HHMMSS.txt。
  • json:附件,字段为 protocol、ip、port、username、password、url、 region、region_en、delay_ms、is_valid、real_ip、source_plugin、validated_at。
  • csv:附件,带 UTF-8 BOM;表头列数与顺序是对外契约(协议、IP、端口、用户名、 密码、代理地址、归属地、延迟、状态、真实 IP、来源、验证时间),表头与状态列文案跟随 界面语言。英文归属地列刻意不在导出里。

一次最多导出 50000 条。

错误与边界: 没有符合条件的代理 → 404 api_error。

导入代理(POST /api/pool/import)

请求体:

{ "proxies": ["http://1.2.3.4:8080", "socks5://user:[email protected]:1080"] }

导入与插件抓取走同一条流水线:先端口初筛、再做完整验证,只有验证通过的才入库, 来源固定记为 manual_import。本端点为 HEAVY 档。

响应有三种结果,调用方必须同时看 message 与 proxy_count,不能只看 success:

情形 响应
正常受理 { "success": true, "message": "正在后台验证 N 个代理,通过后才入库", "proxy_count": N, "task_id": "..." }
全部解析失败 { "success": false, "message": "...", "proxy_count": 0 }
全部已存在 { "success": true, "message": "所有代理已存在,未导入新代理", "proxy_count": 0 }
  • 空列表 → 400 invalid_parameter。
  • 「已存在」的判据是逐条按 ip:port 查库,不走去重缓存。
  • 受理后进度按返回的 task_id 轮询。

切换收藏状态(POST /api/pool/proxies/{id}/favorite)

无请求体。

{
  "success": true,
  "is_favorite": true,
  "message": "代理已收藏"
}

这是「切换」而不是「设置」,重复调用会在两个状态之间来回翻转,没有幂等参数。 代理不存在 → 404 api_error。收藏状态代表用户选择,抓取与验证写入不会覆盖它。

统计与主机信息

获取池统计(GET /api/pool/stats)

{
  "total_proxies": 120,
  "valid_proxies": 86,
  "invalid_proxies": 34,
  "anonymity_fallback_proxies": 5,
  "protocol_distribution": { "http": 60, "socks5": 26 },
  "region_distribution": { "中国": 40, "美国": 12 }
}

响应没有 status 字段,直接就是统计对象(与 /status 里的 stats 同构)。

获取来源列表(GET /api/pool/sources)

{ "sources": ["geonode_plugin", "github_proxy_plugin", "manual_import"] }

返回库里出现过的全部来源插件名,包含手动导入用的伪插件名 manual_import。

获取本机出口 IP(GET /api/pool/host-ip)

返回本机公网出口 IP,它是判定「透明代理」的对比基准。

{
  "host_public_ip": "203.0.113.7",
  "host_public_ipv4": "203.0.113.7",
  "host_public_ipv6": "240e:37a:2563:cb00:9424:56ad:5734:d8",
  "source": "detected",
  "check_anonymity": true
}
字段 说明
host_public_ip 本机出口 IP 的单值形态,优先 IPv4,只有 IPv6 时给 IPv6;拿不到时为 null,这是正常状态而非故障
host_public_ipv4 该族的出口 IP,拿不到时为 null
host_public_ipv6 该族的出口 IP,拿不到时为 null
source 该值的来源(configured 手动指定 / detected 自动查询),拿不到时为空串
check_anonymity 匿名度检测是否开启

双栈主机两族各有一个出口 IP,两族都会参与匿名度比对,所以这里分族给出。

基准值优先取 [Pool] validator.host_public_ip(可填逗号分隔的一到两个,IPv4 与 IPv6 各至多一个);留空时由程序自行查询 —— 查询会钉死地址族各问一次,不是取第一个应答的端点,否则拿到的族别会随端点可用性漂移。每 30 分钟最多一次。

插件管理

列出插件(GET /api/pool/plugins)

[
  {
    "name": "geonode_plugin",
    "enabled": true,
    "interval_minutes": 60,
    "last_run": "2026-09-30T11:00:00",
    "next_run": "2026-09-30T12:00:00",
    "last_error": null,
    "is_loaded": true,
    "test_url": "",
    "reval_enabled": true,
    "reval_interval_minutes": 0,
    "skip_validation": false
  }
]
字段 说明
name 插件名,等于插件文件的文件名(不含扩展名)
enabled 是否参与自动抓取调度
interval_minutes 抓取周期(分钟)
last_run / next_run ISO 8601 字符串或 null
last_error 最近一次错误;若是内置文案键则按当前语言渲染,若是插件自己抛的异常文本则原样给出
is_loaded 模块是否已成功加载。响应只包含加载成功的插件,因此恒为 true;加载失败的插件不会出现在列表里(错误只记在池的日志中)
test_url 该插件专用的存活检测地址,空串表示继承全局 [Server] test_url
reval_enabled 是否参与自动重验证
reval_interval_minutes 重验证周期,0 表示继承全局
skip_validation 是否跳过入库验证(高危,见下)

响应是 JSON 数组,没有 status 字段。

启用 / 禁用插件(POST /api/pool/plugins/{name}/enable、POST /api/pool/plugins/{name}/disable)

无请求体。

{ "success": true, "message": "插件 geonode_plugin 已启用" }

失败时返回 500 api_error。

设置抓取周期(PUT /api/pool/plugins/{name}/interval)

{ "minutes": 30 }

minutes 必须 ≥ 1,否则 400 invalid_parameter。

{ "success": true, "message": "插件 geonode_plugin 运行周期已设置为 30 分钟" }

设置插件检测地址(PUT /api/pool/plugins/{name}/test-url)

{ "test_url": "https://example.com" }

test_url 为空串或全空白表示继承全局 [Server] test_url:

{ "success": true, "message": "插件 geonode_plugin 的测试地址已恢复为继承全局地址" }

设置插件验证策略(PUT /api/pool/plugins/{name}/validation)

{ "reval_enabled": true, "reval_interval_minutes": 0, "skip_validation": false }
字段 类型 默认 说明
reval_enabled bool true 是否参与自动重验证
reval_interval_minutes int 0 重验证周期(分钟);0 表示继承全局。传字符串也能接受,非整数返回 400
skip_validation bool false 跳过入库验证
{ "success": true, "message": "插件 geonode_plugin 的验证策略已更新" }

skip_validation 是危险开关:打开后该插件抓到的代理不经验证直接入库, 池的质量评分与自动清理都会失去意义。它只对已加载的真实插件开放,对伪插件名 manual_import 会返回 404 —— 否则一次性打开就会让此后所有手动导入都不再验证。

立即执行插件(POST /api/pool/plugins/{name}/run)

无请求体。只创建后台任务便返回,被抓取本身受 [Pool] plugins.execution_timeout_seconds 约束。

{
  "task_id": "8f14e45f-ea6b-4c1e-9c1a-0f3b2d5e7a91",
  "status": "running",
  "message": "插件 geonode_plugin 抓取任务已创建"
}

status 只是创建瞬间的快照,进度与结果按 task_id 轮询。插件未加载 → 404 api_error。

热重载插件(POST /api/pool/plugins/{name}/reload)

无请求体。改完插件源码后无需重启进程。

可预期的失败返回 200 + success: false,只有未预期异常才报 500:

{ "success": true, "message": "插件 geonode_plugin 热重载成功" }
{ "success": false, "message": "插件 geonode_plugin 热重载失败(可能正在执行中或文件不存在)" }

插件文件不存在、正在执行、或加载返回空,都会走 success: false。

批量操作

本节所有端点都立即返回 task_id(除两个删除端点),进度按 任务管理轮询。

重新验证有效代理(POST /api/pool/validate/all-valid)

无参数。对当前所有 is_valid = true 的代理做一轮重验证。

{ "task_id": "...", "status": "queued", "message": "验证任务已创建" }

重新验证失效代理(POST /api/pool/validate/all-invalid)

无参数。对当前所有 is_valid = false 的代理做一轮重验证。

这是把「被使用反馈标记失效」的代理立刻重验的入口:被标记失效时 total_checks 会一并清零(日志记为「列入下一轮重验证候选」),而自动重验证每轮都会把 total_checks = 0 的代理补充进候选(不受 auto_revalidation.only_valid_proxies 限制,但仍按各自的重验证间隔排期),所以这些代理不依赖本端点也会被重新验证。

{ "task_id": "...", "status": "queued", "message": "验证任务已创建" }

全面检测并补归属地(POST /api/pool/update-geo/all)

无参数。对全部代理做完整评估(延迟、出口 IP、匿名度、协议支持),顺带补齐归属地。 比日常重验证重得多。

{ "task_id": "...", "status": "queued", "message": "代理全面检测任务已创建" }

删除全部失效代理(DELETE /api/pool/delete/invalid)

无参数。删完不可恢复。

{ "success": true, "message": "成功删除 34 个失效代理", "deleted_count": 34 }

没有失效代理时同样返回成功,deleted_count 为 0。本端点为 HEAVY 档。

批量验证指定代理(POST /api/pool/proxies/batch/validate)

{ "proxy_ids": [1, 2, 3], "mode": "liveness" }
字段 类型 必填 默认 说明
proxy_ids int[] 是 — 代理 id 列表;为空返回 400
mode string 否 auto auto / liveness / full,其他取值返回 400
{ "task_id": "...", "status": "running", "message": "正在验证 3 个代理" }
  • 不存在的 id 会被静默忽略,只有一个都不存在时才返回 404 api_error。
  • 三种模式:liveness 只判通不通;full 做完整评估;auto 按代理已有信息自动选。

批量删除指定代理(DELETE /api/pool/proxies/batch/delete)

注意:这是 DELETE 请求,但参数在请求体里。

{ "proxy_ids": [1, 2, 3] }
{ "success": true, "message": "成功删除 3 个代理", "deleted_count": 3 }

proxy_ids 为空返回 400;不存在的 id 被静默忽略,deleted_count 取实际删除行数。

任务管理

列出任务(GET /api/pool/tasks)

参数 类型 默认 说明
status string 不限 按状态过滤:running / completed / failed / cancelled(任务表里没有 queued,传它筛不到条目)
{
  "tasks": [
    { "task_id": "8f14e45f-...", "status": "running", "progress": 40, "total": 120, "message": "..." }
  ],
  "total": 1
}

查询任务状态(GET /api/pool/tasks/{task_id})

响应就是任务条目本身,没有 status 外层字段(条目内自带 status 表示任务状态)。

{
  "status": "running",
  "message": "正在验证代理...",
  "stage": "validate",
  "progress": 40,
  "total": 120,
  "stage_done": 40,
  "stage_total": 120,
  "rate": 12.5,
  "eta_seconds": 6,
  "started_at": "2026-09-30T12:00:00",
  "valid_count": 22,
  "invalid_count": 10
}

字段随任务类型不同:只有导入/抓取类任务带 stage 与全套计数;批量验证与全面检测类 任务另有 inconclusive_count。任务不存在或已被 TTL 淘汰 → 404 api_error。

任务条目是内存态:进程重启后查不到任何历史任务。

取消任务(POST /api/pool/tasks/{task_id}/cancel)

无请求体。

{ "success": true, "message": "任务已取消" }

任务已经结束时返回 success: false,这不是错误:

{ "success": false, "message": "任务已经结束,无需取消" }

任务 id 不存在 → 404 api_error。

归属地维护

归属地解析用离线库(GeoLite2 与纯真库),不发外部查询请求。本节三个端点都是维护动作。

查询离线库状态(GET /api/pool/geo/status)

{
  "available": true,
  "files": [ { "filename": "GeoLite2-City.mmdb", "exists": true,
               "size": 65383808, "modified_at": "2026-10-02 12:00:00" } ],
  "pending_recompute": 42
}

modified_at 在文件不存在时为 null。

字段 说明
available 离线库是否可用(两个数据文件都在才算可用)
files 各数据文件的状态
pending_recompute 待重算归属地的记录数;查不到时按 0 返回,不报错

更新离线库(POST /api/pool/geo/update)

无参数。会下载约 100 MB 的数据文件,做成后台任务。

{ "task_id": "...", "status": "queued", "message": "离线归属地库更新任务已创建" }
  • 两个文件合计约 105 MB(GeoLite2 约 65 MB、纯真库约 40 MB)。
  • 不校验磁盘空间,空间不足会在下载中途失败。
  • 一个文件失败不阻断另一个,失败项在任务结果的 message 里如实回报。

重算归属地(POST /api/pool/geo/recompute)

无参数。按离线库重算存量记录的归属地。

{ "task_id": "...", "status": "queued", "message": "归属地重算任务已创建" }

错误与边界:

  • 只重算 country_code 为空的记录(即从未用离线库解析过的出口 IP)。已经解析过的 记录不会被重算;更新数据文件后没有专门的立即刷新操作,但 POST /api/pool/update-geo/all 的全面检测会把各出口 IP 重新交给解析器、可间接写回新归属地(受进程内查询缓存与耗时 限制,并非即时生效)。
  • 归属地解析服务未启用时直接返回 503 api_error,不创建任务。

数据库与备份

列出备份(GET /api/pool/backups)

{
  "backups": [
    { "filename": "proxies_backup_20260930_030000.db", "size": 1048576, "created_at": "2026-09-30 03:00:00" }
  ]
}

备份由 [Pool] database.backup_enabled 控制的定时循环创建,本端点只列出已有备份。 备份功能未启用时返回 404 api_error。 本组备份列表与恢复是纯文件操作, 池停止时同样可用。

从备份恢复(POST /api/pool/backups/restore)

{ "filename": "proxies_backup_20260930_030000.db" }
{ "success": true, "message": "备份恢复成功,请重启代理池以加载恢复后的数据" }

语义约定(三条都很重要):

  1. 池运行中返回 409 pool_running —— 覆盖正在打开的数据库文件会造成损坏, 必须先停池。这是唯一会返回 pool_running 的端点。
  2. 恢复只覆盖库文件,不校验备份与当前库的版本兼容性。
  3. 恢复成功后必须重启池才会加载新数据。端点本身不重启池;面板的 「数据库维护」页会在恢复完成后自动重启(恢复前先停池、恢复后按原状态启回), 直接调接口的客户端要自己调 POST /api/pool/start。

filename 缺失 → 400 invalid_parameter。

数据库统计(GET /api/pool/database/stats)

{ "stats": { "total_proxies": 120, "valid_proxies": 96, "invalid_proxies": 24,
             "db_size_mb": 1.05 } }

需要池在运行。维护功能未启用时返回 404 api_error。

优化数据库(POST /api/pool/database/optimize)

无参数。执行 ANALYZE + REINDEX + VACUUM,HEAVY 档,且 VACUUM 会长时间独占 数据库锁,期间其他数据库操作会被阻塞。

{ "success": true, "message": "数据库优化完成" }

代理池错误码

见池侧约定 → 错误响应。汇总表见 附录 A。


四、端到端调用示例

Python

import requests

HOST = "http://localhost:5001"
TOKEN = "你的token"
PARAMS = {"token": TOKEN}

# 面板:看运行状态
status = requests.get(f"{HOST}/api/status", params=PARAMS).json()
print(status["current_proxy"], status["proxy_source_mode"])

# 面板:手动切换一次上游代理
switched = requests.get(f"{HOST}/api/switch_proxy", params=PARAMS).json()
if switched["status"] != "success":
    # 冷却中和真的失败都是 error,靠字段区分
    print("冷却中" if switched.get("cooldown") else switched.get("message"))

# 面板:查询最近一小时的失败请求
records = requests.get(
    f"{HOST}/api/logs/records",
    params={**PARAMS, "outcome": "failure", "limit": 50},
).json()
for row in records["records"]:
    print(row["ts"], row["host"], row["upstream_label"], row["reason"])

# 代理池:取一个可用代理(纯文本)
proxy = requests.get(
    f"{HOST}/api/pool/random",
    params={**PARAMS, "status": "valid", "format": "text"},
).text.strip()
print(proxy)   # 例如 http://1.2.3.4:8080

# 代理池:创建批量验证任务并轮询
task = requests.post(
    f"{HOST}/api/pool/proxies/batch/validate",
    params=PARAMS,
    json={"proxy_ids": [1, 2, 3], "mode": "liveness"},
).json()
progress = requests.get(
    f"{HOST}/api/pool/tasks/{task['task_id']}", params=PARAMS
).json()
print(progress["status"], progress.get("progress"), progress.get("total"))

cURL

# 面板:运行状态
curl "http://localhost:5001/api/status?token=YOUR_TOKEN"

# 面板:保存配置(改轮换模式)
curl -X POST "http://localhost:5001/api/config?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"server": {"mode": "loadbalance"}}'

# 面板:重启出口代理服务
curl -X POST "http://localhost:5001/api/service?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"action": "restart"}'

# 面板:切换界面语言
curl -X POST "http://localhost:5001/api/language?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"language": "en"}'

# 面板:导出最近一小时的访问记录为 CSV
curl -OJ "http://localhost:5001/api/logs/records/export?token=YOUR_TOKEN&format=csv"

# 代理池:筛选查询
curl "http://localhost:5001/api/pool/get?protocol=http&status=valid&limit=10&token=YOUR_TOKEN"

# 代理池:取一个可用代理(纯文本)
curl "http://localhost:5001/api/pool/random?status=valid&format=text&token=YOUR_TOKEN"

# 代理池:手动运行插件
curl -X POST "http://localhost:5001/api/pool/plugins/geonode_plugin/run?token=YOUR_TOKEN"

# 代理池:批量导入
curl -X POST "http://localhost:5001/api/pool/import?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"proxies": ["http://1.2.3.4:8080"]}'

五、注意事项

  1. 鉴权只在查询串里比对单一 token,无 CSRF 防护,也不区分调用方权限:能访问面板即可 修改配置(含下游凭据)。不要把面板直接暴露到公网。
  2. 两套错误约定并存:面板侧多数业务失败是 200 + status: "error",代理池侧是真实 4xx/5xx + error_code。调用方两个都要判。
  3. message 随界面语言变化,只可用于展示,不可用于程序判断。
  4. 运行状态在进程内存中(当前代理、出口名册、任务表、广告关闭状态),进程重启即重置; 访问记录与域名统计落在 SQLite,重启后仍在。
  5. web_port 改动需重启整个进程;port 改动需重启出口服务(走 POST /api/config 保存时面板会自动重启出口服务;手工编辑与命令行入口需自行重启),其余配置项保存后 即时生效,但个别项例外:如访问记录缓冲 access_records_buffer_size 要等代理服务下次 启动时才按新容量重建缓冲。手工编辑 config.ini 的 [Server] 改动也会被自动热重载 (两个入口都在轮询配置文件),但端口换绑不会自动发生;[Pool] 段由池自行热加载。
  6. 多数批量操作返回 task_id(两个删除端点直接返回 success / deleted_count), 用 GET /api/pool/tasks/{task_id} 查进度;任务动辄跑几十分钟,可随时用 POST /api/pool/tasks/{task_id}/cancel 中止。注意离线归属地库更新任务的下载跑在 不可取消的线程里,取消只会写出状态、下载仍会跑完。任务表是内存态且有 1 小时 TTL, 进程重启后不可查。
  7. 代理池的查询参数是签名白名单:参数名写错会被静默忽略,返回未筛选的结果而不报错。
  8. 修改 /api/pool/* 的端点或字段时,必须同步更新本文档 —— 适配层是手写的 Flask 蓝图, 不像 FastAPI 那样能自动生成 OpenAPI 描述。

附录 A:错误码总表

面板接口错误约定

情形 HTTP 响应体
成功 200 {"status": "success", ...}
成功(部分只读端点) 200 直接返回数据,无 status 字段
鉴权失败 401 {"status": "error", "message": "..."}
配置校验未通过(仅 POST /api/config) 400 {"status": "error", "message": "...", "details": {"key": "...", "reason": "..."}}
其余业务失败 200 {"status": "error", "message": "..."}

代理池接口错误约定

error_code HTTP 含义
invalid_parameter 400 请求参数非法
api_error 4xx / 5xx 业务错误(代理不存在、无匹配结果、规格不符、插件操作失败、恢复失败等);状态码随 ApiError 自带
pool_running 409 池运行中,不允许该操作(仅 POST /api/pool/backups/restore)
pool_unavailable 503 代理池未运行或正在重启
pool_timeout 504 代理池响应超时
queue_full 503 写入队列已满
internal_error 500 服务端内部错误
(无) 500 池启停失败:POST /api/pool/start|stop|restart 失败时不带 error_code

附录 B:端点总索引

页面路由(非 api)

方法 路径 鉴权 说明
GET / 否 带 token 跳转到 /web
GET /web 是 面板单页
GET /static/<path> 否 前端静态资源

面板接口(34)

方法 路径 说明
GET /api/status 运行状态
GET /api/config 读取 [Server] / [Pool] 配置
POST /api/config 保存配置
POST /api/language 切换界面语言
GET /api/proxies 读本地代理列表
POST /api/proxies 写本地代理列表
GET /api/check_proxies 检测源清单里的代理可用性(local 来源即 ip.txt 全部条目)
GET /api/ip_lists 读 IP 黑白名单
POST /api/ip_lists 写 IP 黑白名单
GET /api/bypass_whitelist 读绕过名单
POST /api/bypass_whitelist 写绕过名单
POST /api/service 启停 / 重启出口代理服务
GET /api/switch_proxy 刷新出口池:不满则补满,满员则换掉最近到期的那个出口
GET /api/logs 查询内存日志
GET /api/logs/stats 日志统计
GET /api/logs/files 列出日志文件
GET /api/logs/file 读取日志文件
GET /api/logs/export 导出日志
POST /api/logs/clear 清空日志
GET /api/logs/records 查询访问记录
GET /api/logs/records/export 导出访问记录
GET /api/logs/records/options 访问记录过滤候选项
POST /api/logs/records/clear 清空访问记录
GET /api/logs/domains 查询域名统计
POST /api/logs/domains/clear 清空域名统计
GET /api/users 读出口代理账号
POST /api/users 写出口代理账号
GET /api/api_credentials 读上游 API 凭据
POST /api/api_credentials 管理上游 API 凭据
GET /api/version 版本检查结果(只读缓存,免鉴权)
POST /api/version/check 立即触发一次版本检查(需鉴权)
GET /api/ads 获取广告(免鉴权)
POST /api/ads/dismiss 关闭广告(免鉴权)
POST /api/ads/reopen 重新开启广告(免鉴权)

代理池接口(38)

方法 路径 说明
GET /api/pool/status 池状态与全局统计
GET /api/pool/schema [Pool] 字段结构(面板表单同源)
POST /api/pool/start 启动池
POST /api/pool/stop 停止池(等写队列排空)
POST /api/pool/restart 重启池
GET /api/pool/get 筛选代理列表(json / text)
GET /api/pool/random 随机取一个代理(json / text)
GET /api/pool/count 统计符合条件的代理数
GET /api/pool/export 导出代理(txt / json / csv)
POST /api/pool/import 批量导入(先验证再入库)
POST /api/pool/proxies/{id}/favorite 切换收藏状态
GET /api/pool/stats 池统计
GET /api/pool/sources 来源插件名列表
GET /api/pool/host-ip 本机公网出口 IP
GET /api/pool/plugins 插件列表与状态
POST /api/pool/plugins/{name}/enable 启用插件
POST /api/pool/plugins/{name}/disable 禁用插件
PUT /api/pool/plugins/{name}/interval 设置抓取周期
PUT /api/pool/plugins/{name}/test-url 设置插件专用检测地址
PUT /api/pool/plugins/{name}/validation 设置插件验证策略
POST /api/pool/plugins/{name}/run 立即执行插件
POST /api/pool/plugins/{name}/reload 热重载插件
POST /api/pool/validate/all-valid 重新验证所有有效代理
POST /api/pool/validate/all-invalid 重新验证所有失效代理
POST /api/pool/update-geo/all 全面检测并补归属地
DELETE /api/pool/delete/invalid 删除全部失效代理
POST /api/pool/proxies/batch/validate 批量验证指定代理
DELETE /api/pool/proxies/batch/delete 批量删除指定代理(参数在请求体)
GET /api/pool/tasks 列出任务
GET /api/pool/tasks/{task_id} 查询任务状态
POST /api/pool/tasks/{task_id}/cancel 取消任务
GET /api/pool/geo/status 离线归属地库状态
POST /api/pool/geo/update 更新离线归属地库(约 100 MB)
POST /api/pool/geo/recompute 重算存量记录的归属地
GET /api/pool/backups 列出备份
POST /api/pool/backups/restore 从备份恢复(需先停池)
GET /api/pool/database/stats 数据库统计
POST /api/pool/database/optimize 优化数据库(ANALYZE / REINDEX / VACUUM)

附录 C:字段字典

代理对象

GET /api/pool/get、GET /api/pool/random(format=json)返回的对象。

字段 类型 说明
id int 主键
proxy_url string 完整代理地址;用户名与密码都存在时形如 协议://用户:密码@ip:端口
protocol string http / https / socks5
ip string 入口 IP
port int 端口
username / password string | null 代理认证凭据
real_ip string | null 出口 IP(经代理访问时对端看到的 IP)
exit_ip_differs bool 入口 IP 与出口 IP 是否不同。查询与随机响应都提供(两者共用同一序列化),导出路径刻意不带
region string 中文归属地全名,如「中国 广东 深圳」;查不到为「未知」
country / province / city string 中文归属地分级
region_en / country_en / province_en / city_en string | null 英文归属地分级
delay_ms float | null 最近一次测得的延迟(毫秒)
validated_at string | null 最近一次验证时间(ISO 8601)
source_plugin string 来源插件名;手动导入为 manual_import
is_favorite bool 是否收藏(用户选择,抓取与验证不会覆盖)
is_valid bool 当前是否可用;列表用它给失效行打「失效」徽标。导出走自己的状态列
health_score float 健康分,0-100,保留 1 位小数
anonymity_level string transparent / anonymous / elite / unverified
anonymity_fallback bool 匿名度是否被判成兜底档
supports_https / supports_http bool 是否支持对应协议
quality_assessed_at string | null 最近一次质量评估时间(ISO 8601)
success_rate float 验证成功率,0-1,保留 3 位小数
avg_delay_ms float | null 历史平均延迟(毫秒)
total_checks int 累计验证次数;缺值以 0 兜底
probe_success_rate float 探测成功率,0-1,保留 3 位小数
probe_total_count int 累计探测次数;缺值以 0 兜底

归属地空值的语义:中英两套按列给出,null 表示还没查过(不是「未知」)。 英文列缺失时前端回退到中文列;两列相同表示该级没有对应译名。不要往这些列里写 「未知」文案 —— 「未知」是中文列查询过但没查到的取值。

任务对象

GET /api/pool/tasks/{task_id} 的响应,也是 GET /api/pool/tasks 列表里的元素。

字段 类型 说明
task_id string 任务 id;只在 GET /api/pool/tasks 的列表元素里出现(列表逐条注入),单查接口的响应里没有
status string running / completed / failed / cancelled(queued 只出现在部分创建响应里,任务表里没有)
message string 当前阶段的可读描述,跟随界面语言
progress / total int 已完成 / 总数
started_at / completed_at string 起止时间(ISO 8601);未结束则无 completed_at
stage string prescreen / validate / ingest / done(完成时写 done),仅导入与抓取类任务有
stage_done / stage_total int 当前阶段的进度,同上
in_flight int 当前阶段正在并发处理中的条数,同上
rate float 处理速率(条/秒)
eta_seconds int | null 预计剩余秒数
valid_count / invalid_count / inconclusive_count int 验证结果分类计数
prescreened_out int 端口初筛即淘汰的条数
incomplete_count / failed_count int 未完成 / 失败条数
results object 数据文件名到结果文案(已更新 / 失败: ...)的映射,仅离线归属地库更新任务的完成态有

字段集合随任务类型与端点不同,上表是各处的并集;只依赖 status、progress、 total、message 这四个字段最稳妥。

备份对象

GET /api/pool/backups 列表里的元素。

字段 类型 说明
filename string 备份文件名,作为 POST /api/pool/backups/restore 的参数
size int 文件大小(字节)
created_at string 创建时间,格式固定为 YYYY-MM-DD HH:MM:SS