入门2026年9月29日阅读约 8 分钟

代理格式详解:每个工具要哪种写法

代理格式一次讲清:IP:端口:用户名:密码、user:pass@host:port 和完整 URL,各工具认哪种,附经过测试的 Python/JavaScript 转换代码和特殊字符处理。

代理格式,就是把主机、端口、用户名、密码这四个值写成一行字符串时的顺序。常见的有 HOST:PORT、HOST:PORT:USERNAME:PASSWORD(也就是常说的 IP:端口:用户名:密码)、USERNAME:PASSWORD@HOST:PORT,以及完整 URL http://USERNAME:PASSWORD@HOST:PORT。写代码用的库要完整 URL;指纹浏览器和批量导入框一般要冒号分隔的那一行。

不管哪种格式,四个值本身不变,变的只是它们之间的符号。一个正确的代理,按错误的格式粘进去,和一个错误的代理表现得一模一样,所以值得花十分钟把格式弄清楚。本文列出你会遇到的几种格式、各个工具接受哪一种,并给出一段 Python 和 JavaScript 的转换代码,免得你再手动改列表。

所有示例都用 HOST、PORT、USERNAME 和 PASSWORD 作占位符。你自己的值在控制台里每项服务的页面上,没有一个所有人共用、可以从博客里直接抄的网关地址。

你会遇到的五种代理格式

格式 示例 常见场合
主机加端口 HOST:PORT 浏览器和系统代理设置、已加白名单的连接
冒号行,主机在前 HOST:PORT:USERNAME:PASSWORD 服务商导出的列表、指纹浏览器导入
冒号行,用户名在前 USERNAME:PASSWORD:HOST:PORT 部分其他服务商的导出和老工具
登录信息在主机前 USERNAME:PASSWORD@HOST:PORT curl、一些机器人程序和配置文件
完整 URL http://USERNAME:PASSWORD@HOST:PORT Python、Node.js、Go、PHP、环境变量

建议把完整 URL 当作你的“原件”保存。它带协议头,是标准写法,其他格式都能从它推出来,不用猜。

开头的 http:// 是什么意思

协议头说明的是你的程序怎么和代理通信,而不是怎么和网站通信。http:// 表示普通的 HTTP 代理,照样能访问 https:// 网站:客户端请代理打开一条 CONNECT 隧道,加密在隧道里端到端进行。如果在代理前面写 https://,意思是要求和代理本身建立加密连接,这是另一回事。我们把 requests 指向一个普通 HTTP 代理、却写了 https://,它的报错说得很直白:

ProxyError('Unable to connect to proxy. Your proxy appears to only use HTTP and not HTTPS, try changing your proxy URL to be HTTP. ...')

每个产品都可以用 http://。想用 SOCKS5,就改写成 socks5h://:住宅代理的端口两种都接受,ISP 和数据中心代理在控制台里切换协议。

各工具认哪种代理字符串格式

我们把每种格式分别交给 curl 8.5、requests 2.34 和 undici 8.11(Node.js 的 fetch 库),连接一个本地的、需要登录的测试代理。结果如下。

curl 最宽容。-x "http://USERNAME:PASSWORD@HOST:PORT" 可以,不带协议头的 -x USERNAME:PASSWORD@HOST:PORT 也可以,curl 会默认按 http:// 处理。你也可以用 -U 把登录信息从 URL 里拿出来:

curl -x HOST:PORT -U "USERNAME:PASSWORD" https://api.ipify.org

冒号行在发出任何请求之前就会被拒绝:curl: (5) Unsupported proxy syntax in 'HOST:PORT:USERNAME:PASSWORD': Port number was not a decimal number between 0 and 65535。curl 的其他代理选项见 cURL 教程。

Python requests 只要带登录信息,就要完整 URL。不带 http:// 的 USERNAME:PASSWORD@HOST:PORT 会报 InvalidProxyURL: Please check proxy URL. It is malformed and could be missing the host.,冒号行会报 InvalidURL: Failed to parse。单独的 HOST:PORT 可以用,requests 会自动补上协议头。

Node.js 最严格。undici 的 ProxyAgent 遇到 HOST:PORT 会报 TypeError: Invalid URL,遇到 USERNAME:PASSWORD@HOST:PORT 会报 Invalid URL protocol: the URL must start with `http:` or `https:`.。每次都给它完整 URL,和 Node.js 教程里的写法一样。

浏览器用一组输入框填主机和端口,代理第一次要求登录时再弹窗让你输用户名和密码。Chrome 和 Edge 把整件事交给操作系统。浏览器教程讲了 Firefox 自带的设置面板,以及带正规登录字段的 FoxyProxy 扩展。如果改完设置 Chrome 什么都打不开,Chrome 代理报错把每个错误代码对应到了原因。

指纹浏览器两种都支持:分开的主机、端口、用户名、密码字段,以及一个按 HOST:PORT:USERNAME:PASSWORD 每行一个代理的粘贴或导入框。GoLogin、AdsPower 和 BitBrowser 的教程分别说明了这个框藏在哪里。

Proxifier 所有值都分开填。协议选 HTTPS,这是 Proxifier 对“能打开 CONNECT 隧道的 HTTP 代理”的叫法;Proxifier 教程有完整步骤。

控制台导出的 IP:端口:用户名:密码 格式

控制台里的住宅代理页面按 HOST:PORT:USERNAME:PASSWORD 的顺序导出代理,和 GoLogin、BitBrowser 的导入顺序相同。ISP 和数据中心服务会把每个静态 IP 连同它自己的端口和登录信息列出来,这四个值可以填进上面任何一种格式。

使用住宅代理时,先在控制台里选好 Randomize IP 或 Sticky IP,再去复制。选 Sticky IP 时,会话选项附在密码末尾,所以复制出来的密码比普通密码长。请按显示的样子整串复制。粘性密码如果在某个符号处被截断,就会得到 407;什么时候需要这个模式,见轮换代理与粘性代理。

特殊字符:user:pass@host:port 为什么会坏

在 URL 里,有些字符有自己的职责:@ 表示登录信息结束,: 分隔用户名和密码、主机和端口,/、#、?、% 也各有含义。密码里只要有这些字符,在 URL 格式中就必须做百分号编码。我们用密码 p@ss:w/rd#1%41 做了测试:

  • 原样放进 URL:curl 报 Unsupported proxy syntax,requests 报 InvalidURL,undici 报 Invalid URL。
  • 编码成 p%40ss%3Aw%2Frd%231%2541 后,三者都连上了。
  • 用 curl 的 -U 时,@、:、/、# 可以不编码,但 %41 仍然被解码成了 A,所以那里的 % 也要写成 %25。

冒号行的规则正好相反:它从不编码,因为工具是按冒号切分它,而不是当作 URL 解析。这也让冒号行有自己的脆弱之处:密码里带冒号时,只有工具知道字段顺序、并把第三个冒号之后的内容全部当作密码,才能切对。登录信息在去往代理的路上还有哪些被弄坏的方式,见 407 错误怎么解决。

IPv6 代理要加方括号

IPv6 地址里全是冒号,所以写进 URL 时要放进方括号:http://[2001:db8::10]:PORT,登录信息照常放在前面。不加方括号,curl 会报同样的 “Port number was not a decimal number”,requests 报 Failed to parse。加了方括号,curl、requests 和 undici 都连上了我们在 [::1] 上的测试代理。冒号行里也要保留方括号,写成 [2001:db8::10]:PORT:USERNAME:PASSWORD,否则没有工具分得清地址在哪里结束。

Python 代理格式转换代码

下面这段能读入五种格式中的任意一种,输出你需要的那种。保存为 proxyfmt.py:

import re
from urllib.parse import quote, unquote

HOST_RE = r"(?P<host>\[[^\]]+\]|[^:@\[\]]+)"
PORT_RE = r"(?P<port>[^:@/]+)"
URL_FORM = rf"^(?:(?P<scheme>[a-z0-9]+):\/\/)?(?:(?P<user>[^:@]*):(?P<password>.*)@)?{HOST_RE}:{PORT_RE}/?$"
COLON_FORMS = {
    "host_first": rf"^{HOST_RE}:{PORT_RE}:(?P<user>[^:]*):(?P<password>.*)$",
    "user_first": rf"^(?P<user>[^:]*):(?P<password>.*):{HOST_RE}:{PORT_RE}$",
}


def parse_proxy(line, order="host_first"):
    line = line.strip()
    match = re.match(URL_FORM, line)
    decode = unquote
    if not match:
        match = re.match(COLON_FORMS[order], line)
        decode = str
    if not match:
        raise ValueError(f"not a proxy line I recognise: {line!r}")
    p = match.groupdict()
    return {
        "scheme": p.get("scheme") or "http",
        "host": p["host"],
        "port": p["port"],
        "user": decode(p["user"]) if p["user"] is not None else None,
        "password": decode(p["password"]) if p["password"] is not None else None,
    }


def to_url(p):
    auth = ""
    if p["user"] is not None:
        auth = quote(p["user"], safe="") + ":" + quote(p["password"], safe="") + "@"
    return p["scheme"] + "://" + auth + p["host"] + ":" + p["port"]


def to_colon(p):
    parts = [p["host"], p["port"], p["user"], p["password"]]
    return ":".join(part for part in parts if part is not None)


if __name__ == "__main__":
    for line in [
        "HOST:PORT",
        "HOST:PORT:USERNAME:PASSWORD",
        "USERNAME:PASSWORD@HOST:PORT",
        "http://USERNAME:PASSWORD@HOST:PORT",
    ]:
        print(to_url(parse_proxy(line)))
    print(to_colon(parse_proxy("USERNAME:PASSWORD:HOST:PORT", order="user_first")))

两种冒号行从外表上看不出区别,所以 parse_proxy 不去猜。它默认主机在前,也就是控制台的顺序;来自其他地方的列表,传入 order="user_first"。URL 格式在读入时解码、输出时编码,所以满是符号的密码转一圈也不会变。用 requests 读取保存好的导出文件:

import requests

from proxyfmt import parse_proxy, to_url

with open("proxies.txt") as f:
    proxies = [to_url(parse_proxy(line)) for line in f if line.strip()]

for proxy in proxies:
    r = requests.get("https://api.ipify.org", proxies={"http": proxy, "https": proxy}, timeout=30)
    print(r.status_code, r.text)

JavaScript 版转换代码

proxyfmt.mjs,适用于 Node.js 22 及以上:

const HOST_RE = String.raw`(?<host>\[[^\]]+\]|[^:@\[\]]+)`;
const PORT_RE = String.raw`(?<port>[^:@/]+)`;
const URL_FORM = new RegExp(String.raw`^(?:(?<scheme>[a-z0-9]+):\/\/)?(?:(?<user>[^:@]*):(?<password>.*)@)?${HOST_RE}:${PORT_RE}\/?$`);
const COLON_FORMS = {
  hostFirst: new RegExp(String.raw`^${HOST_RE}:${PORT_RE}:(?<user>[^:]*):(?<password>.*)$`),
  userFirst: new RegExp(String.raw`^(?<user>[^:]*):(?<password>.*):${HOST_RE}:${PORT_RE}$`),
};

export function parseProxy(line, order = 'hostFirst') {
  line = line.trim();
  let match = line.match(URL_FORM);
  let decode = decodeURIComponent;
  if (!match) {
    match = line.match(COLON_FORMS[order]);
    decode = (s) => s;
  }
  if (!match) throw new Error(`not a proxy line I recognise: ${line}`);
  const { scheme, host, port, user, password } = match.groups;
  return {
    scheme: scheme ?? 'http',
    host,
    port,
    user: user === undefined ? undefined : decode(user),
    password: password === undefined ? undefined : decode(password),
  };
}

export function toUrl(p) {
  const auth = p.user === undefined ? '' : encodeURIComponent(p.user) + ':' + encodeURIComponent(p.password) + '@';
  return p.scheme + '://' + auth + p.host + ':' + p.port;
}

export function toColon(p) {
  return [p.host, p.port, p.user, p.password].filter((part) => part !== undefined).join(':');
}

配合 undici 使用(npm i undici):

import { fetch, ProxyAgent } from 'undici';
import { parseProxy, toUrl } from './proxyfmt.mjs';

const proxy = toUrl(parseProxy('HOST:PORT:USERNAME:PASSWORD'));
const response = await fetch('https://api.ipify.org', { dispatcher: new ProxyAgent(proxy) });
console.log(response.status, await response.text());

两个版本我们都在本地测试代理上跑过:普通密码、上面那个满是符号的密码、带方括号的 IPv6 地址,以及用户名在前的行。每一行都连上了,转成 URL 再转回来,四个值也完全一致。

什么时候只要 HOST:PORT 就够了

如果你在控制台里把自己连接时用的 IP 地址加进服务的 IP 白名单,代理就会放这个地址进来,不再要求登录。之后 HOST:PORT(Node.js 里写 http://HOST:PORT)就是完整的代理字符串,也没人需要把密码存在配置文件里。这适合公网地址固定的服务器,不适合在不同网络之间来回切换的笔记本电脑,因为白名单只认你填进去的地址。

常见问题

IP:端口:用户名:密码 是什么格式? 就是主机在前的冒号行:HOST:PORT:USERNAME:PASSWORD。控制台的住宅代理导出用的就是它,大多数指纹浏览器导入的也是它。

user:pass@host:port 和 URL 格式一样吗? 差不多,只是前面少了 http://。curl 可以直接用;Python requests 和 Node.js 需要补上协议头。

代理 URL 应该以 http 还是 https 开头? http://,访问 https 网站时也一样。网站的加密在隧道里进行。

为什么我的代理列表在一个工具里能用,换一个就不行? 字段顺序或编码不同。用上面的代码转换一下,再看看密码里有没有符号。

下一步

从服务页面复制一行,用转换代码跑一遍,然后向 https://api.ipify.org 发一个请求。返回的地址不是你自己的,格式就对了。第一个请求的更多细节见代理怎么用;如果列表还是导不进去,把报错(密码打码)贴到 Discord。

还有后续问题?

去 Discord 问。答案还能帮到下一个读到这个帖子的人。

加入 Discorddiscord.gg/proxypanda
$5 起步去 Discord 问