开始之前

  • PATH 中可用的 Go 工具链。代码只用到 net/http 和 net/url 中存在已久的接口,当前任何版本都可以。
  • 一个用来放小模块的文件夹,第 1 步会创建它。
  • 从服务页面复制的 HOST、PORT、USERNAME 和 PASSWORD。

你的连接信息

登录控制台,打开你购买的服务。服务页面上会显示该服务的主机、端口、用户名和密码。ProxyPanda 没有一个统一的固定地址,所以每次都请从那里复制。指南中的代码使用下面这些占位符,请逐一替换成你自己的值。

HOST
该服务的代理地址,按服务页面上显示的原样填写。
PORT
要连接的端口。请和主机一起复制,因为不同服务的端口可能不同。
USERNAME
你的代理登录名。它与你登录控制台所用的邮箱是分开的。
PASSWORD
请完整复制。住宅代理在控制台中选择的选项会附加在密码末尾,凭记忆手动输入的密码会丢失这些选项。

把你用来连接的 IP 地址加入服务的白名单,就可以不用用户名和密码。这时代码里只需要 HOST 和 PORT。

示例都使用 HTTP,它可以通过加密隧道访问 https 网站。SOCKS5 同样可用:住宅代理的端口两种都接受,ISP 和数据中心代理在控制台里切换协议。

步骤

  1. 创建模块

    go version 会确认运行代码的是哪个工具链。下面每个示例都是这个文件夹里完整的 main.go,每次替换文件内容后用 go run . 运行。

    terminal
    go version
    mkdir proxycheck && cd proxycheck
    go mod init proxycheck
  2. 让 Transport 指向代理

    http.ProxyURL 把一个固定的 URL 包装成 Transport 每次请求都会调用的函数。url.UserPassword 保存登录信息,并在写出 URL 时自动做百分号编码,所以密码里有 @、: 或 / 也不用手动转义。Client.Timeout 限制整次请求的时间,连向代理的 CONNECT 也包括在内。

    main.go
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"net/url"
    	"time"
    )
    
    func main() {
    	proxyURL := &url.URL{
    		Scheme: "http",
    		User:   url.UserPassword("USERNAME", "PASSWORD"),
    		Host:   "HOST:PORT",
    	}
    
    	client := &http.Client{
    		Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
    		Timeout:   30 * time.Second,
    	}
    
    	resp, err := client.Get("https://api.ipify.org?format=json")
    	if err != nil {
    		fmt.Println("request failed:", err)
    		return
    	}
    	defer resp.Body.Close()
    
    	body, _ := io.ReadAll(resp.Body)
    	fmt.Println(resp.Status, string(body))
    }
  3. 运行并检查出口

    go run . 会打印 200 OK,后面是 api.ipify.org 返回的 JSON。ip 字段就是网站看到的地址,应当属于代理。

  4. 设置 Transport 超时,每个请求用新连接

    Dialer 的超时限制连上代理的时间,TLSHandshakeTimeout 限制在隧道内与网站握手的时间,ResponseHeaderTimeout 限制等待第一个响应字节的时间。DisableKeepAlives 让每条连接只用一次就关闭:测试中,三次请求在开启它时建立了三条 CONNECT 隧道,关闭时只有一条。

    main.go
    package main
    
    import (
    	"fmt"
    	"io"
    	"net"
    	"net/http"
    	"net/url"
    	"time"
    )
    
    func main() {
    	proxyURL := &url.URL{
    		Scheme: "http",
    		User:   url.UserPassword("USERNAME", "PASSWORD"),
    		Host:   "HOST:PORT",
    	}
    
    	transport := &http.Transport{
    		Proxy:                 http.ProxyURL(proxyURL),
    		DialContext:           (&net.Dialer{Timeout: 10 * time.Second}).DialContext,
    		TLSHandshakeTimeout:   10 * time.Second,
    		ResponseHeaderTimeout: 30 * time.Second,
    		DisableKeepAlives:     true,
    	}
    	client := &http.Client{Transport: transport, Timeout: 60 * time.Second}
    
    	for i := 0; i < 3; i++ {
    		resp, err := client.Get("https://api.ipify.org")
    		if err != nil {
    			fmt.Println(i, "error:", err)
    			continue
    		}
    		body, _ := io.ReadAll(resp.Body)
    		resp.Body.Close()
    		fmt.Println(i, string(body))
    	}
    }
  5. 改为从 HTTPS_PROXY 读取代理

    http.ProxyFromEnvironment 对 https 网址读取 HTTPS_PROXY,对 http 网址读取 HTTP_PROXY,并跳过 NO_PROXY 中列出的主机。http.DefaultTransport 已经在用它,但你自己创建的 Transport 不会:没有 Proxy 字段时,即使导出了变量,请求也会直接发出。

    main.go
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"time"
    )
    
    func main() {
    	client := &http.Client{
    		Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
    		Timeout:   30 * time.Second,
    	}
    
    	resp, err := client.Get("https://api.ipify.org")
    	if err != nil {
    		fmt.Println("request failed:", err)
    		return
    	}
    	defer resp.Body.Close()
    
    	body, _ := io.ReadAll(resp.Body)
    	fmt.Println(string(body))
    }
  6. 导出变量并运行

    登录信息留在变量里,Go 代码中不出现任何凭据。这里的 URL 是纯文本,密码中的 @、: 或 / 需要你自己做百分号编码。如果这个 shell 还要给别的工具用,结束后取消这个变量。

    terminal
    export HTTPS_PROXY="http://USERNAME:PASSWORD@HOST:PORT"
    go run .

轮换 IP 与粘性 IP

开启 keep-alive 时,Transport 会保持隧道,之后发往同一网站的请求都从这条隧道走,复用的隧道出口地址不变。住宅代理选“随机 IP”、又希望每个请求都能换地址时,请设置 DisableKeepAlives。

使用“粘性 IP”或 ISP、数据中心 IP 时,保持 keep-alive 开启即可。地址本来就应该不变,省去每次重新握手还能节省时间。

一个 http.Client 可以被多个 goroutine 共用。想把任务分到多个静态 IP 上,就为每个 IP 建一个 Transport,按请求选择。

常见错误与解决办法

Get "https://api.ipify.org…": Proxy Authentication Required

代理拒绝了登录。重新复制 USERNAME 和 PASSWORD。如果你用 url.Parse 从字符串构造代理 URL,要先对密码中的特殊字符做百分号编码,或者改用 url.UserPassword。

proxyconnect tcp: dial tcp …: connect: connection refused

HOST:PORT 上没有代理在监听。与服务页面核对两者,并确认服务仍然有效。

context deadline exceeded (Client.Timeout exceeded while awaiting headers)

在 Client.Timeout 用完之前没有收到响应。住宅出口是家庭网络,有些较慢,可以给这类请求更多时间,并只重试这一个网址。

程序打印的是你自己的地址

Transport 没有设置 Proxy 字段,或者 HTTPS_PROXY 是在另一个终端里导出的。自己创建的 Transport 在 Proxy 设为 http.ProxyFromEnvironment 之前,不会读取环境变量。

选择哪条产品线

轮询 API 或批量抓取页面的 Go 程序,只要目标接受,用数据中心 IP 成本最低。来自服务器 IP 段的请求开始被拒绝时,换成住宅代理;长期运行、需要在多次会话中保持同一地址的程序,给它一个 ISP IP。

卡在某一步了?

把命令和报错贴到 Discord,记得先删掉密码。那里的人大多遇到过这些错误。

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