# Goプロキシスクレイピング: http.Transport、認証、ローテーションと並行処理

Goでは、プロキシに関するすべての話は1つのフィールドに集約されています: Transport.Proxy。これを正しく設定すれば、リクエストごとのローテーション、SOCKS5、collyが無料で手に入ります。間違って設定すると、午前2時に「context canceled」に遭遇します。

Goはプロキシを非常にシンプルに見せます: クライアント側のすべての話は1つのフィールド、`http.Transport.Proxy`に集約されています。これを正しく設定すれば、リクエストごとのローテーション、SOCKS5、`colly`の統合が同じ設計から自然に生まれます。間違えると、典型的なGoプロキシの失敗に遭遇します - `context canceled`、接続プールの枯渇、そしてカスタムトランスポートが静かに無視する`HTTP_PROXY`変数。このガイドは実行可能なコードと共に全体の道筋を案内します。

## ワンライナー: プロキシURLを持つhttp.Transport

Goのすべてのプロキシリクエストは`Transport`を通じて流れます。`net/http`パッケージにはヘルパー`http.ProxyURL`があり、クライアント全体に1つの静的プロキシを固定します。資格情報はURLのユーザー情報に直接入れられ、Goがそれを`Proxy-Authorization`ヘッダーに変換します:

```go
package main

import (
  "fmt"
  "net/http"
  "net/url"
  "time"
)

func main() {
  proxyURL, _ := url.Parse("http://USER:PASS@residentialpremium.quantumproxies.io:8080")
  client := &http.Client{
    Timeout:   20 * time.Second,
    Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
  }
  resp, err := client.Get("https://httpbin.org/ip")
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()
  fmt.Println(resp.Status) // body shows the exit IP, not yours
}
```

常に`Client.Timeout`を設定してください。Goには`http.Client`にデフォルトのタイムアウトがないため、1つのデッドエグジットがゴルーチンを永遠にブロックします - Goスクレイパーがハングしているように見える一番の理由は、遅いターゲットではなく、タイムアウトが欠けていることです。

## プロキシ関数を使用したプロキシのローテーション

`Proxy`は固定URLに限定されていません - それは`func(*http.Request) (*url.URL, error)`という関数であり、Goはリクエストごとに1回呼び出します。それがあなたのローテーションフックです。プールからランダムなエンドポイントを選び、各リクエストが異なるIPから出発します。フェッチループにリスト管理ロジックは一切不要です:

```go
import "math/rand"

// three sticky sessions on the same gateway = three exit IPs to pick from
pool := []string{
  "http://USER-sid-pool1-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
  "http://USER-sid-pool2-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
  "http://USER-sid-pool3-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
}

transport := &http.Transport{
  Proxy: func(r *http.Request) (*url.URL, error) {
    return url.Parse(pool[rand.Intn(len(pool))])
  },
  MaxIdleConnsPerHost: 32, // reuse connections across the pool
}
```

ほとんどのスクレイピングでは、プールを管理する必要はありません。`Proxy`関数を1つの[ローテーティング住宅ゲートウェイ](https://quantumproxies.io/rotating-proxies)に向け、ゲートウェイが90M以上のアドレスを持つ200以上の国で各リクエストに新しいIPを提供します - 1つのエンドポイント、リストなし、数分間同じ出口が必要なフローにはスティッキーセッション。

![1つのTransportを持つGoのhttp.Clientで、そのProxy関数がリクエストごとに実行されるため、リクエスト1、2、3は異なる出口IPから同じターゲットに向かう。カードの要点：関数はリクエストごとに1回呼ばれ、プールから選ぶか1つのローテーションゲートウェイを指し、MaxIdleConnsPerHost: 32で接続を再利用し続ける。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335148/blog/generated/qp-b9-009-diag1.png)
*1つのTransport、1つのProxy関数: 関数はリクエストごとに実行されるため、1つのクライアントがプール全体をローテーションします。*

## Goにおけるプロキシ認証

プロキシURLに`USER:PASS@`を入れるのがクリーンな方法で、`ProxyURL`とカスタム`Proxy`関数の両方で機能します。資格情報をURLから外したい場合は、`Transport.ProxyConnectHeader`を介してCONNECTトンネルのヘッダーを自分で設定します。プロバイダーがIPホワイトリスト認証を使用している場合は、資格情報を完全に削除し、ダッシュボードでサーバーのIPを承認します - QuantumProxiesは両方をサポートしています。`407`または`proxyconnect tcp`で拒否されたCONNECTは、ほとんどの場合、ターゲット側のブロックではなく、資格情報が欠けているか間違っていることを意味します。

## HTTP_PROXY環境変数（およびGoがそれを無視する場合）

Goの`http.DefaultTransport`は`http.ProxyFromEnvironment`を使用し、`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY`を読み取ります（`golang.org/x/net/http/httpproxy`パッケージに従います）。2つの動作が人々を混乱させます。まず、これらの変数は完全なURLまたは裸の`host:port`を受け入れ、`http`スキームが仮定されます。次に、`localhost`またはループバックアドレスへのリクエストは常にプロキシをバイパスし、nil URLを返します。より大きな問題は逆です: 自分で`&http.Transport{Proxy: ...}`を構築した瞬間、環境変数の動作を*置き換え*ます - 明示的な関数が優先され、`HTTP_PROXY`は無視されます。両方を望む場合は、自分でラップしてください。

## GoにおけるSOCKS5プロキシ

標準ライブラリにはSOCKS5クライアントがないため、`golang.org/x/net/proxy`を取り込み、それを通じてダイヤルします。ダイヤラーを`Transport.DialContext`に供給し、DNSがあなたのマシンではなくプロキシで解決されるようにします（他のスタックで\<socks5h>が解決する同じホスト名リーク問題）。2つのスキームを比較する場合、[SOCKS5 vs HTTPプロキシ](https://quantumproxies.io/blog/socks5-vs-http-proxy-difference)に関する私たちのノートが、どちらがどのように優れているかを説明します。

```bash
go get golang.org/x/net/proxy
```

```go
import (
  "context"
  "net"
  "net/http"
  "golang.org/x/net/proxy"
)

auth := &proxy.Auth{User: "USER", Password: "PASS"}
dialer, err := proxy.SOCKS5("tcp", "residentialpremium.quantumproxies.io:1080", auth, proxy.Direct)
if err != nil {
  panic(err)
}

transport := &http.Transport{
  DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    return dialer.Dial(network, addr)
  },
}
client := &http.Client{Transport: transport}
```

## collyにおけるプロキシ

`colly`を使用してスクレイピングする場合、トランスポートに触れることなくローテーションが得られます。`colly/proxy`パッケージには、リクエストごとにエンドポイントを循環させる`RoundRobinProxySwitcher`があり、`LimitRule`がペーシングを処理します。これはGoで礼儀正しいローテーティングクローラーを作成する最速の方法です。

```go
import (
  "time"
  "github.com/gocolly/colly/v2"
  "github.com/gocolly/colly/v2/proxy"
)

c := colly.NewCollector(colly.Async(true))

rp, err := proxy.RoundRobinProxySwitcher(
  "http://USER-sid-crawl1-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
  "http://USER-sid-crawl2-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
)
if err != nil {
  panic(err)
}
c.SetProxyFunc(rp)

c.Limit(&colly.LimitRule{
  DomainGlob:  "*",
  Parallelism: 8,
  RandomDelay: 2 * time.Second,
})
```

## 正しい並行処理（およびcontext-canceledトラップ）

Goの並行処理は、チームがスクレイピングにそれを選ぶ理由であり、また`context canceled`エラーが発生する場所でもあります。これは、リクエストのコンテキストがボディを完全に読み込む前にキャンセルされたときに発生します - 通常はリクエストごとのタイムアウトが期限切れになったか、`defer`によって早く実行された`cancel()`です。セマフォでファンアウトを制限し、各リクエストに独自のタイムアウトコンテキストを与え、キャンセルが発生する前にボディを読み込んで閉じます:

```go
sem := make(chan struct{}, 20) // cap concurrency
var wg sync.WaitGroup

for _, u := range urls {
  wg.Add(1)
  sem <- struct{}{}
  go func(u string) {
    defer wg.Done()
    defer func() { <-sem }()

    ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
    defer cancel()

    req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)
    resp, err := client.Do(req)
    if err != nil {
      return // rotate/log; do not retry the same burned exit
    }
    io.Copy(io.Discard, resp.Body) // drain BEFORE cancel fires
    resp.Body.Close()
  }(u)
}
wg.Wait()
```

さらに2つのプロダクションノート。`MaxIdleConnsPerHost`を十分に高く設定し、大きなプールがファイルディスクリプタを使い果たすのではなく接続を再利用するようにします - 数百のリクエスト後の`EOF`の通常の原因です。そして、失敗時には同じものを再試行するのではなく出口をローテーションします; デッドな住宅IPは次の試行で回復しません。数百万ページの広範な設計については、[大規模スクレイピングアーキテクチャ](https://quantumproxies.io/blog/large-scale-web-scraping-architecture)に関するガイドがキュー、重複排除、プロキシ階層化をカバーしています。

## 1つのクライアントを再利用し、トランスポートを調整する

`http.Client`を1度作成し、すべてのゴルーチンで共有します - それは並行使用に安全であり、その接続プールがGoスクレイパーを高速にします。リクエストごとに新しいクライアントを作成すると、キープアライブを無駄にし、毎回新しいTLSハンドシェイクを強制します。負荷時に重要な3つの`Transport`フィールド: `MaxIdleConns`と`MaxIdleConnsPerHost`はプールのサイズを決定し、`IdleConnTimeout`は古い接続を退役させ、ローテーティングプロキシプールがデッドな出口に固定されないようにします。これらを明示的に設定してください - 標準ライブラリのデフォルトはブラウザ用に調整されており、毎分数千のリクエストを実行するクローラーには適していません。

![Goのプロキシエラーの表で、原因の列が強調されている。カードは各エラーと原因を対応させている：proxyconnectや407は認証情報の誤り、context canceledはボディを読む前にcancel()が実行されたこと、EOFはMaxIdleConnsPerHostが低すぎること、HTTP_PROXYが無視されるのはカスタムTransportのため、ハングするのはタイムアウトがないため。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335148/blog/generated/qp-b9-009-diag2.png)
*ほぼすべてのGoプロキシの失敗はこれら5つのいずれかにマッピングされます - 同じ出口を再試行する前に原因列を確認してください。*

## トランスポートの手作業をやめる時

上記のコードはクリーンなターゲットには十分です。サイトがCloudflare、TLSフィンガープリンティング、またはJavaScriptレンダリングを追加すると、Raw Go TLSハンドシェイクはChromeのものとは全く異なり、プロキシではその不一致を修正できません。その時点で、実際のブラウザフィンガープリントを持ち、IPをローテーションし、JSをレンダリングする[Scraper API](https://quantumproxies.io/scraper-api)は、スタックを手作業で維持するよりも少ないコードで成功率が高いです。新しいスクレイパーの言語を選ぶ場合、私たちの[ウェブスクレイピングのためのベストプロキシ](https://quantumproxies.io/blog/best-proxies-for-web-scraping)投稿がスタック間のトレードオフをマッピングしています。

[レンダリングとローテーションをScraper APIにオフロードする](https://quantumproxies.io/scraper-api)

## よくある質問

### Go HTTPクライアントにプロキシを設定するにはどうすればいいですか？

`Proxy`フィールドを持つ`http.Transport`を構築し、それを`http.Client`に渡します。単一の静的プロキシには`http.ProxyURL(u)`を使用し、リクエストごとに出口を選ぶには`func(*http.Request) (*url.URL, error)`を使用します。資格情報を`http://user:pass@host:port`としてURLに入れ、常に`Client.Timeout`を設定してください。

### なぜGoは私のHTTP_PROXY変数を無視するのですか？

カスタム`Transport`を明示的な`Proxy`関数で構築したため、デフォルトの`ProxyFromEnvironment`動作を置き換えました。環境変数は`http.DefaultTransport`を使用するか、`Proxy: http.ProxyFromEnvironment`を自分で設定した場合にのみ適用されます。また、ループバックとlocalhostのリクエストは常にプロキシをバイパスすることにも注意してください。

### Goプロキシで「context canceled」が発生する原因は何ですか？

リクエストのコンテキストがレスポンスボディを読み込む前にキャンセルされました - 通常はリクエストごとのタイムアウトが期限切れになったか、`defer`によって早く実行された`cancel()`です。各リクエストに独自のタイムアウトコンテキストを与え、キャンセルが実行される前にボディを排出して閉じ、1つのキャンセル可能なコンテキストを多くのゴルーチンで共有しないでください。

### GoはSOCKS5プロキシをサポートしていますか？

標準ライブラリではありませんが、`golang.org/x/net/proxy`がSOCKS5ダイヤラーを追加します。`proxy.SOCKS5`で作成し、`Transport.DialContext`に接続してホスト名がローカルDNSルックアップを通じて漏れないようにします。

これがクライアント側の全体像です: セットアップのための1つの`Transport`フィールド、ローテーションのための関数、SOCKS5のための`x/net/proxy`、礼儀正しいクロールのための`colly`、並行処理のためのセマフォとリクエストごとのコンテキスト。クリーンなローテーティングプールから始めれば、エラーリストのほとんどを事前に回避できます。

[QuantumProxiesの住宅プロキシから始めましょう](https://quantumproxies.io/residential-proxies)
