Go API代理开发示例

阅读模式

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

提示

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

依赖安装

1
go get github.com/go-resty/resty/v2
2
go get github.com/valyala/fasthttp@latest
3
go get github.com/valyala/fasthttp/fasthttpproxy@latest
4
go get github.com/gocolly/colly/v2
5
go get github.com/PuerkitoBio/goquery
6
go get github.com/chromedp/chromedp
7
go get github.com/go-rod/rod
8
go get github.com/imroc/req/v3

入门示例(net/http)

1
package main
2
3
import (
4
"encoding/json"
5
"fmt"
6
"io/ioutil"
7
"net/http"
8
"net/url"
9
"time"
10
)
11
12
type ProxyItem struct {
13
IP string `json:"ip"`
14
Port int `json:"port"`
15
}
16
17
func fetchProxy(api string) (string, int, error) {
18
client := &http.Client{Timeout: 10 * time.Second}
19
resp, err := client.Get(api)
20
if err != nil {
21
return "", 0, err
22
}
23
defer resp.Body.Close()
24
if resp.StatusCode != 200 {
25
return "", 0, fmt.Errorf("API错误: %d", resp.StatusCode)
26
}
27
b, _ := ioutil.ReadAll(resp.Body)
28
var arr []ProxyItem
29
if err := json.Unmarshal(b, &arr); err != nil {
30
return "", 0, err
31
}
32
if len(arr) == 0 {
33
return "", 0, fmt.Errorf("API 返回为空")
34
}
35
return arr[0].IP, arr[0].Port, nil
36
}
37
38
func main() {
39
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
40
ip, port, err := fetchProxy(apiUrl)
41
if err != nil {
42
panic(err)
43
}
44
proxyURL, _ := url.Parse(fmt.Sprintf("http://%s:%d", ip, port))
45
tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
46
client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
47
r, err := client.Get("https://httpbin.org/ip")
48
if err != nil {
49
panic(err)
50
}
51
defer r.Body.Close()
52
body, _ := ioutil.ReadAll(r.Body)
53
fmt.Println(string(body))
54
}

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

1
package main
2
3
import (
4
"context"
5
"encoding/json"
6
"errors"
7
"fmt"
8
"io"
9
"net/http"
10
"net/url"
11
"time"
12
)
13
14
type ProxyItem struct {
15
IP string `json:"ip"`
16
Port int `json:"port"`
17
}
18
19
func fetchFirstProxy(ctx context.Context, api string) (string, int, error) {
20
req, err := http.NewRequestWithContext(ctx, http.MethodGet, api, nil)
21
if err != nil {
22
return "", 0, err
23
}
24
req.Header.Set("User-Agent", "Go-ApiProxy-Demo/1.0")
25
26
client := &http.Client{Timeout: 10 * time.Second}
27
resp, err := client.Do(req)
28
if err != nil {
29
return "", 0, fmt.Errorf("API 请求失败: %w", err)
30
}
31
defer resp.Body.Close()
32
33
if resp.StatusCode != http.StatusOK {
34
switch resp.StatusCode {
35
case 400:
36
return "", 0, errors.New("API错误 400: 参数错误")
37
case 403:
38
return "", 0, errors.New("API错误 403: 主机IP不在白名单")
39
case 429:
40
return "", 0, errors.New("API错误 429: 提取频率过快")
41
default:
42
return "", 0, fmt.Errorf("API错误: %d", resp.StatusCode)
43
}
44
}
45
46
body, err := io.ReadAll(resp.Body)
47
if err != nil {
48
return "", 0, err
49
}
50
var arr []ProxyItem
51
if err := json.Unmarshal(body, &arr); err != nil {
52
return "", 0, err
53
}
54
if len(arr) == 0 || arr[0].IP == "" || arr[0].Port == 0 {
55
return "", 0, errors.New("API 返回为空或缺少 ip/port")
56
}
57
return arr[0].IP, arr[0].Port, nil
58
}
59
60
func visitTargetViaProxy(ctx context.Context, ip string, port int) error {
61
proxyURL, _ := url.Parse(fmt.Sprintf("http://%s:%d", ip, port))
62
tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
63
client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
64
65
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://httpbin.org/ip", nil)
66
req.Header.Set("User-Agent", "Mozilla/5.0")
67
resp, err := client.Do(req)
68
if err != nil {
69
return fmt.Errorf("访问目标站失败: %w", err)
70
}
71
defer resp.Body.Close()
72
73
if resp.StatusCode != http.StatusOK {
74
switch resp.StatusCode {
75
case 403:
76
return errors.New("访问失败 403: 主机IP不在白名单或目标站拒绝")
77
case 408:
78
return errors.New("访问失败 408: 请求超时,检查带宽/目标站速度")
79
case 429:
80
return errors.New("访问失败 429: 访问频率过快,降低并发/增加间隔")
81
case 504:
82
return errors.New("访问失败 504: 目标站暂时不可达,稍后重试或补充 headers")
83
default:
84
return fmt.Errorf("访问失败: %d", resp.StatusCode)
85
}
86
}
87
b, _ := io.ReadAll(resp.Body)
88
fmt.Println(string(b))
89
return nil
90
}
91
92
func main() {
93
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
94
defer cancel()
95
96
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
97
ip, port, err := fetchFirstProxy(ctx, apiUrl)
98
if err != nil {
99
panic(err)
100
}
101
if err := visitTargetViaProxy(ctx, ip, port); err != nil {
102
panic(err)
103
}
104
}

Resty(HTTP 客户端)

1
package main
2
3
import (
4
"encoding/json"
5
"errors"
6
"fmt"
7
"time"
8
9
"github.com/go-resty/resty/v2"
10
)
11
12
type ProxyItem struct {
13
IP string `json:"ip"`
14
Port int `json:"port"`
15
}
16
17
func main() {
18
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
19
20
client := resty.New().SetTimeout(10 * time.Second)
21
resp, err := client.R().Get(apiUrl)
22
if err != nil {
23
panic(fmt.Errorf("API 请求失败: %w", err))
24
}
25
if resp.StatusCode() != 200 {
26
switch resp.StatusCode() {
27
case 400:
28
panic("API错误 400: 参数错误")
29
case 403:
30
panic("API错误 403: 主机IP不在白名单")
31
case 429:
32
panic("API错误 429: 提取频率过快")
33
default:
34
panic(fmt.Errorf("API错误: %d", resp.StatusCode()))
35
}
36
}
37
38
var arr []ProxyItem
39
if err := json.Unmarshal(resp.Body(), &arr); err != nil || len(arr) == 0 {
40
panic(errors.New("API 返回为空或解析失败"))
41
}
42
ip, port := arr[0].IP, arr[0].Port
43
44
// 设置代理访问目标站
45
proxied := resty.New().SetTimeout(10 * time.Second).SetProxy(fmt.Sprintf("http://%s:%d", ip, port))
46
r2, err := proxied.R().SetHeader("User-Agent", "Mozilla/5.0").Get("https://httpbin.org/ip")
47
if err != nil {
48
panic(fmt.Errorf("访问目标站失败: %w", err))
49
}
50
if r2.StatusCode() != 200 {
51
panic(fmt.Errorf("访问失败: %d", r2.StatusCode()))
52
}
53
fmt.Println(string(r2.Body()))
54
}

fasthttp + fasthttpproxy(高性能客户端)

1
package main
2
3
import (
4
"encoding/json"
5
"errors"
6
"fmt"
7
"time"
8
9
"github.com/valyala/fasthttp"
10
"github.com/valyala/fasthttp/fasthttpproxy"
11
)
12
13
type ProxyItem struct {
14
IP string `json:"ip"`
15
Port int `json:"port"`
16
}
17
18
func main() {
19
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
20
21
// 先用 fasthttp 获取代理
22
var arr []ProxyItem
23
{
24
req := fasthttp.AcquireRequest()
25
resp := fasthttp.AcquireResponse()
26
defer fasthttp.ReleaseRequest(req)
27
defer fasthttp.ReleaseResponse(resp)
28
req.SetRequestURI(apiUrl)
29
req.Header.SetMethod(fasthttp.MethodGet)
30
31
if err := fasthttp.DoTimeout(req, resp, 10*time.Second); err != nil {
32
panic(fmt.Errorf("API 请求失败: %w", err))
33
}
34
if resp.StatusCode() != 200 {
35
panic(fmt.Errorf("API错误: %d", resp.StatusCode()))
36
}
37
if err := json.Unmarshal(resp.Body(), &arr); err != nil || len(arr) == 0 {
38
panic(errors.New("API 返回为空或解析失败"))
39
}
40
}
41
42
ip, port := arr[0].IP, arr[0].Port
43
44
// 使用代理访问目标站
45
client := &fasthttp.Client{
46
Dial: fasthttpproxy.FasthttpHTTPDialer(fmt.Sprintf("%s:%d", ip, port)),
47
}
48
req := fasthttp.AcquireRequest()
49
resp := fasthttp.AcquireResponse()
50
defer fasthttp.ReleaseRequest(req)
51
defer fasthttp.ReleaseResponse(resp)
52
req.SetRequestURI("https://httpbin.org/ip")
53
req.Header.SetMethod(fasthttp.MethodGet)
54
req.Header.Set("User-Agent", "Mozilla/5.0")
55
if err := client.DoTimeout(req, resp, 10*time.Second); err != nil {
56
panic(fmt.Errorf("访问目标站失败: %w", err))
57
}
58
if resp.StatusCode() != 200 {
59
panic(fmt.Errorf("访问失败: %d", resp.StatusCode()))
60
}
61
fmt.Println(string(resp.Body()))
62
}

Colly(对标 Java WebMagic/crawler4j)

1
package main
2
3
import (
4
"encoding/json"
5
"fmt"
6
"log"
7
"time"
8
9
"github.com/gocolly/colly/v2"
10
"github.com/gocolly/colly/v2/proxy"
11
"github.com/imroc/req/v3"
12
)
13
14
type ProxyItem struct {
15
IP string `json:"ip"`
16
Port int `json:"port"`
17
}
18
19
func fetchFirstProxy(api string) (string, int, error) {
20
r := req.C().SetTimeout(10 * time.Second)
21
resp, err := r.R().Get(api)
22
if err != nil {
23
return "", 0, err
24
}
25
if resp.StatusCode != 200 {
26
return "", 0, fmt.Errorf("API错误: %d", resp.StatusCode)
27
}
28
var arr []ProxyItem
29
if err := json.Unmarshal(resp.Bytes(), &arr); err != nil || len(arr) == 0 {
30
return "", 0, fmt.Errorf("API 返回为空或解析失败")
31
}
32
return arr[0].IP, arr[0].Port, nil
33
}
34
35
func main() {
36
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
37
ip, port, err := fetchFirstProxy(apiUrl)
38
if err != nil {
39
log.Fatal(err)
40
}
41
42
c := colly.NewCollector(colly.UserAgent("Mozilla/5.0"))
43
rp, _ := proxy.RoundRobinProxySwitcher(fmt.Sprintf("http://%s:%d", ip, port))
44
c.SetProxyFunc(rp)
45
46
c.OnResponse(func(r *colly.Response) { fmt.Println(string(r.Body)) })
47
c.OnError(func(r *colly.Response, e error) { log.Printf("访问失败: %d %v", r.StatusCode, e) })
48
49
if err := c.Visit("https://httpbin.org/ip"); err != nil {
50
log.Fatal(err)
51
}
52
}

goquery(对标 Java Jsoup)

1
package main
2
3
import (
4
"encoding/json"
5
"fmt"
6
"log"
7
"net/http"
8
"net/url"
9
"time"
10
11
"github.com/PuerkitoBio/goquery"
12
)
13
14
type ProxyItem struct {
15
IP string `json:"ip"`
16
Port int `json:"port"`
17
}
18
19
func fetchProxy(api string) (string, int, error) {
20
client := &http.Client{Timeout: 10 * time.Second}
21
resp, err := client.Get(api)
22
if err != nil {
23
return "", 0, err
24
}
25
defer resp.Body.Close()
26
var arr []ProxyItem
27
if err := json.NewDecoder(resp.Body).Decode(&arr); err != nil || len(arr) == 0 {
28
return "", 0, fmt.Errorf("API 返回为空或解析失败")
29
}
30
return arr[0].IP, arr[0].Port, nil
31
}
32
33
func main() {
34
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
35
ip, port, err := fetchProxy(apiUrl)
36
if err != nil { log.Fatal(err) }
37
38
proxyURL, _ := url.Parse(fmt.Sprintf("http://%s:%d", ip, port))
39
tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
40
client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
41
42
req, _ := http.NewRequest(http.MethodGet, "https://httpbin.org/ip", nil)
43
req.Header.Set("User-Agent", "Mozilla/5.0")
44
resp, err := client.Do(req)
45
if err != nil { log.Fatal(err) }
46
defer resp.Body.Close()
47
48
doc, err := goquery.NewDocumentFromReader(resp.Body)
49
if err != nil { log.Fatal(err) }
50
fmt.Println(doc.Text())
51
}

chromedp(对标 Java Selenium)

1
package main
2
3
import (
4
"context"
5
"encoding/json"
6
"fmt"
7
"log"
8
"time"
9
10
"github.com/chromedp/chromedp"
11
"net/http"
12
)
13
14
type ProxyItem struct {
15
IP string `json:"ip"`
16
Port int `json:"port"`
17
}
18
19
func fetchProxy(api string) (string, int, error) {
20
c := &http.Client{Timeout: 10 * time.Second}
21
r, err := c.Get(api)
22
if err != nil { return "", 0, err }
23
defer r.Body.Close()
24
var arr []ProxyItem
25
if err := json.NewDecoder(r.Body).Decode(&arr); err != nil || len(arr) == 0 {
26
return "", 0, fmt.Errorf("API 返回为空或解析失败")
27
}
28
return arr[0].IP, arr[0].Port, nil
29
}
30
31
func main() {
32
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
33
ip, port, err := fetchProxy(apiUrl)
34
if err != nil { log.Fatal(err) }
35
36
allocOpts := append(chromedp.DefaultExecAllocatorOptions[:],
37
chromedp.Flag("headless", true),
38
chromedp.Flag("proxy-server", fmt.Sprintf("http://%s:%d", ip, port)),
39
)
40
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), allocOpts...)
41
defer cancel()
42
ctx, cancel2 := chromedp.NewContext(allocCtx)
43
defer cancel2()
44
45
var html string
46
if err := chromedp.Run(ctx,
47
chromedp.Navigate("https://httpbin.org/ip"),
48
chromedp.OuterHTML("html", &html),
49
); err != nil {
50
log.Fatal(err)
51
}
52
fmt.Println(html)
53
}

rod(浏览器自动化)

1
package main
2
3
import (
4
"encoding/json"
5
"fmt"
6
"log"
7
"time"
8
9
"github.com/go-rod/rod"
10
"github.com/go-rod/rod/lib/launcher"
11
"net/http"
12
)
13
14
type ProxyItem struct {
15
IP string `json:"ip"`
16
Port int `json:"port"`
17
}
18
19
func fetchProxy(api string) (string, int, error) {
20
c := &http.Client{Timeout: 10 * time.Second}
21
r, err := c.Get(api)
22
if err != nil { return "", 0, err }
23
defer r.Body.Close()
24
var arr []ProxyItem
25
if err := json.NewDecoder(r.Body).Decode(&arr); err != nil || len(arr) == 0 {
26
return "", 0, fmt.Errorf("API 返回为空或解析失败")
27
}
28
return arr[0].IP, arr[0].Port, nil
29
}
30
31
func main() {
32
apiUrl := "http://ip.16yun.cn:817/myip/pl/<ORDER_ID>/?s=<ORDER_SIGN>&u=<USER>&format=json"
33
ip, port, err := fetchProxy(apiUrl)
34
if err != nil { log.Fatal(err) }
35
36
url := launcher.New().Proxy(fmt.Sprintf("http://%s:%d", ip, port)).MustLaunch()
37
browser := rod.New().ControlURL(url).MustConnect()
38
defer browser.MustClose()
39
40
page := browser.MustPage("https://httpbin.org/ip")
41
html := page.MustHTML()
42
fmt.Println(html)
43
}