Go API代理开发示例

流程:调用 API → 解析 JSON → 处理错误 → 使用返回代理访问目标站。

[!NOTE]提示

  • 下述示例均采用 IP 白名单模式(无需用户名密码认证)。请将 apiUrl 替换为您自己的 API 提取链接。
  • 常见错误码:400 参数错误403 主机 IP 未在白名单429 频率过快(参考 @api-proxy.md)。
  • 对标 Java 热门生态中的常见组件:HttpClient/OkHttpnet/http/resty/fasthttpJsoupgoquerySeleniumchromedp/rodWebMagic/crawler4jcolly

依赖安装

go get github.com/go-resty/resty/v2
go get github.com/valyala/fasthttp@latest
go get github.com/valyala/fasthttp/fasthttpproxy@latest
go get github.com/gocolly/colly/v2
go get github.com/PuerkitoBio/goquery
go get github.com/chromedp/chromedp
go get github.com/go-rod/rod
go get github.com/imroc/req/v3

入门示例(net/http)

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "time"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func fetchProxy(api string) (string, int, error) {
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Get(api)
    if err != nil {
        return "", 0, err
    }
    defer resp.Body.Close()
    if resp.StatusCode != 200 {
        return "", 0, fmt.Errorf("API错误: %d", resp.StatusCode)
    }
    b, _ := ioutil.ReadAll(resp.Body)
    var arr []ProxyItem
    if err := json.Unmarshal(b, &arr); err != nil {
        return "", 0, err
    }
    if len(arr) == 0 {
        return "", 0, fmt.Errorf("API 返回为空")
    }
    return arr[0].IP, arr[0].Port, nil
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
    ip, port, err := fetchProxy(apiUrl)
    if err != nil {
        panic(err)
    }
    proxyURL, _ := url.Parse(fmt.Sprintf("http://%s:%d", ip, port))
    tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
    client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
    r, err := client.Get("https://httpbin.org/ip")
    if err != nil {
        panic(err)
    }
    defer r.Body.Close()
    body, _ := ioutil.ReadAll(r.Body)
    fmt.Println(string(body))
}

net/http(加强版,带错误映射)

package main

import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "time"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func fetchFirstProxy(ctx context.Context, api string) (string, int, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, api, nil)
    if err != nil {
        return "", 0, err
    }
    req.Header.Set("User-Agent", "Go-ApiProxy-Demo/1.0")

    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        return "", 0, fmt.Errorf("API 请求失败: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        switch resp.StatusCode {
        case 400:
            return "", 0, errors.New("API错误 400: 参数错误")
        case 403:
            return "", 0, errors.New("API错误 403: 主机IP不在白名单")
        case 429:
            return "", 0, errors.New("API错误 429: 提取频率过快")
        default:
            return "", 0, fmt.Errorf("API错误: %d", resp.StatusCode)
        }
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return "", 0, err
    }
    var arr []ProxyItem
    if err := json.Unmarshal(body, &arr); err != nil {
        return "", 0, err
    }
    if len(arr) == 0 || arr[0].IP == "" || arr[0].Port == 0 {
        return "", 0, errors.New("API 返回为空或缺少 ip/port")
    }
    return arr[0].IP, arr[0].Port, nil
}

func visitTargetViaProxy(ctx context.Context, ip string, port int) error {
    proxyURL, _ := url.Parse(fmt.Sprintf("http://%s:%d", ip, port))
    tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
    client := &http.Client{Transport: tr, Timeout: 10 * time.Second}

    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://httpbin.org/ip", nil)
    req.Header.Set("User-Agent", "Mozilla/5.0")
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("访问目标站失败: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        switch resp.StatusCode {
        case 403:
            return errors.New("访问失败 403: 主机IP不在白名单或目标站拒绝")
        case 408:
            return errors.New("访问失败 408: 请求超时,检查带宽/目标站速度")
        case 429:
            return errors.New("访问失败 429: 访问频率过快,降低并发/增加间隔")
        case 504:
            return errors.New("访问失败 504: 目标站暂时不可达,稍后重试或补充 headers")
        default:
            return fmt.Errorf("访问失败: %d", resp.StatusCode)
        }
    }
    b, _ := io.ReadAll(resp.Body)
    fmt.Println(string(b))
    return nil
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()

    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
    ip, port, err := fetchFirstProxy(ctx, apiUrl)
    if err != nil {
        panic(err)
    }
    if err := visitTargetViaProxy(ctx, ip, port); err != nil {
        panic(err)
    }
}

Resty(HTTP 客户端)

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "time"

    "github.com/go-resty/resty/v2"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"

    client := resty.New().SetTimeout(10 * time.Second)
    resp, err := client.R().Get(apiUrl)
    if err != nil {
        panic(fmt.Errorf("API 请求失败: %w", err))
    }
    if resp.StatusCode() != 200 {
        switch resp.StatusCode() {
        case 400:
            panic("API错误 400: 参数错误")
        case 403:
            panic("API错误 403: 主机IP不在白名单")
        case 429:
            panic("API错误 429: 提取频率过快")
        default:
            panic(fmt.Errorf("API错误: %d", resp.StatusCode()))
        }
    }

    var arr []ProxyItem
    if err := json.Unmarshal(resp.Body(), &arr); err != nil || len(arr) == 0 {
        panic(errors.New("API 返回为空或解析失败"))
    }
    ip, port := arr[0].IP, arr[0].Port

    // 设置代理访问目标站
    proxied := resty.New().SetTimeout(10 * time.Second).SetProxy(fmt.Sprintf("http://%s:%d", ip, port))
    r2, err := proxied.R().SetHeader("User-Agent", "Mozilla/5.0").Get("https://httpbin.org/ip")
    if err != nil {
        panic(fmt.Errorf("访问目标站失败: %w", err))
    }
    if r2.StatusCode() != 200 {
        panic(fmt.Errorf("访问失败: %d", r2.StatusCode()))
    }
    fmt.Println(string(r2.Body()))
}

fasthttp + fasthttpproxy(高性能客户端)

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "time"

    "github.com/valyala/fasthttp"
    "github.com/valyala/fasthttp/fasthttpproxy"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"

    // 先用 fasthttp 获取代理
    var arr []ProxyItem
    {
        req := fasthttp.AcquireRequest()
        resp := fasthttp.AcquireResponse()
        defer fasthttp.ReleaseRequest(req)
        defer fasthttp.ReleaseResponse(resp)
        req.SetRequestURI(apiUrl)
        req.Header.SetMethod(fasthttp.MethodGet)

        if err := fasthttp.DoTimeout(req, resp, 10*time.Second); err != nil {
            panic(fmt.Errorf("API 请求失败: %w", err))
        }
        if resp.StatusCode() != 200 {
            panic(fmt.Errorf("API错误: %d", resp.StatusCode()))
        }
        if err := json.Unmarshal(resp.Body(), &arr); err != nil || len(arr) == 0 {
            panic(errors.New("API 返回为空或解析失败"))
        }
    }

    ip, port := arr[0].IP, arr[0].Port

    // 使用代理访问目标站
    client := &fasthttp.Client{
        Dial: fasthttpproxy.FasthttpHTTPDialer(fmt.Sprintf("%s:%d", ip, port)),
    }
    req := fasthttp.AcquireRequest()
    resp := fasthttp.AcquireResponse()
    defer fasthttp.ReleaseRequest(req)
    defer fasthttp.ReleaseResponse(resp)
    req.SetRequestURI("https://httpbin.org/ip")
    req.Header.SetMethod(fasthttp.MethodGet)
    req.Header.Set("User-Agent", "Mozilla/5.0")
    if err := client.DoTimeout(req, resp, 10*time.Second); err != nil {
        panic(fmt.Errorf("访问目标站失败: %w", err))
    }
    if resp.StatusCode() != 200 {
        panic(fmt.Errorf("访问失败: %d", resp.StatusCode()))
    }
    fmt.Println(string(resp.Body()))
}

Colly(对标 Java WebMagic/crawler4j)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/gocolly/colly/v2"
    "github.com/gocolly/colly/v2/proxy"
    "github.com/imroc/req/v3"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func fetchFirstProxy(api string) (string, int, error) {
    r := req.C().SetTimeout(10 * time.Second)
    resp, err := r.R().Get(api)
    if err != nil {
        return "", 0, err
    }
    if resp.StatusCode != 200 {
        return "", 0, fmt.Errorf("API错误: %d", resp.StatusCode)
    }
    var arr []ProxyItem
    if err := json.Unmarshal(resp.Bytes(), &arr); err != nil || len(arr) == 0 {
        return "", 0, fmt.Errorf("API 返回为空或解析失败")
    }
    return arr[0].IP, arr[0].Port, nil
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
    ip, port, err := fetchFirstProxy(apiUrl)
    if err != nil {
        log.Fatal(err)
    }

    c := colly.NewCollector(colly.UserAgent("Mozilla/5.0"))
    rp, _ := proxy.RoundRobinProxySwitcher(fmt.Sprintf("http://%s:%d", ip, port))
    c.SetProxyFunc(rp)

    c.OnResponse(func(r *colly.Response) { fmt.Println(string(r.Body)) })
    c.OnError(func(r *colly.Response, e error) { log.Printf("访问失败: %d %v", r.StatusCode, e) })

    if err := c.Visit("https://httpbin.org/ip"); err != nil {
        log.Fatal(err)
    }
}

goquery(对标 Java Jsoup)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "time"

    "github.com/PuerkitoBio/goquery"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func fetchProxy(api string) (string, int, error) {
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Get(api)
    if err != nil {
        return "", 0, err
    }
    defer resp.Body.Close()
    var arr []ProxyItem
    if err := json.NewDecoder(resp.Body).Decode(&arr); err != nil || len(arr) == 0 {
        return "", 0, fmt.Errorf("API 返回为空或解析失败")
    }
    return arr[0].IP, arr[0].Port, nil
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
    ip, port, err := fetchProxy(apiUrl)
    if err != nil { log.Fatal(err) }

    proxyURL, _ := url.Parse(fmt.Sprintf("http://%s:%d", ip, port))
    tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
    client := &http.Client{Transport: tr, Timeout: 10 * time.Second}

    req, _ := http.NewRequest(http.MethodGet, "https://httpbin.org/ip", nil)
    req.Header.Set("User-Agent", "Mozilla/5.0")
    resp, err := client.Do(req)
    if err != nil { log.Fatal(err) }
    defer resp.Body.Close()

    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil { log.Fatal(err) }
    fmt.Println(doc.Text())
}

chromedp(对标 Java Selenium)

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/chromedp/chromedp"
    "net/http"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func fetchProxy(api string) (string, int, error) {
    c := &http.Client{Timeout: 10 * time.Second}
    r, err := c.Get(api)
    if err != nil { return "", 0, err }
    defer r.Body.Close()
    var arr []ProxyItem
    if err := json.NewDecoder(r.Body).Decode(&arr); err != nil || len(arr) == 0 {
        return "", 0, fmt.Errorf("API 返回为空或解析失败")
    }
    return arr[0].IP, arr[0].Port, nil
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
    ip, port, err := fetchProxy(apiUrl)
    if err != nil { log.Fatal(err) }

    allocOpts := append(chromedp.DefaultExecAllocatorOptions[:],
        chromedp.Flag("headless", true),
        chromedp.Flag("proxy-server", fmt.Sprintf("http://%s:%d", ip, port)),
    )
    allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), allocOpts...)
    defer cancel()
    ctx, cancel2 := chromedp.NewContext(allocCtx)
    defer cancel2()

    var html string
    if err := chromedp.Run(ctx,
        chromedp.Navigate("https://httpbin.org/ip"),
        chromedp.OuterHTML("html", &html),
    ); err != nil {
        log.Fatal(err)
    }
    fmt.Println(html)
}

rod(浏览器自动化)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
    "net/http"
)

type ProxyItem struct {
    IP   string `json:"ip"`
    Port int    `json:"port"`
}

func fetchProxy(api string) (string, int, error) {
    c := &http.Client{Timeout: 10 * time.Second}
    r, err := c.Get(api)
    if err != nil { return "", 0, err }
    defer r.Body.Close()
    var arr []ProxyItem
    if err := json.NewDecoder(r.Body).Decode(&arr); err != nil || len(arr) == 0 {
        return "", 0, fmt.Errorf("API 返回为空或解析失败")
    }
    return arr[0].IP, arr[0].Port, nil
}

func main() {
    apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
    ip, port, err := fetchProxy(apiUrl)
    if err != nil { log.Fatal(err) }

    url := launcher.New().Proxy(fmt.Sprintf("http://%s:%d", ip, port)).MustLaunch()
    browser := rod.New().ControlURL(url).MustConnect()
    defer browser.MustClose()

    page := browser.MustPage("https://httpbin.org/ip")
    html := page.MustHTML()
    fmt.Println(html)
}