Collyでのローテーティングプロキシの基本と重要性
Goでスクレイパーを構築した経験がある開発者なら、IPブロックやCAPTCHAに直面したことがあるはずです。Collyでのローテーティングプロキシは、この問題を解決する最も効果的な手段の一つです。各リクエストでIPを切り替えることで、ターゲットサイトのレート制限やIPベースのブロッキングを回避できます。
重要な法的注意事項: 公開データの収集は多くの国で合法ですが、米国のComputer Fraud and Abuse Act (CFAA)やEUのGDPRなど、適用される法律を遵守する必要があります。常にrobots.txtを尊重し、対象サイトの利用規約を確認し、個人情報を収集しないでください。本記事は技術的なガイドであり、法的助言ではありません。
Collyのコレクターモデルを理解する
CollyはGo言語のスクレイピングフレームワークで、コールバックベースのアーキテクチャを採用しています。中心となるのは *colly.Collector で、HTTPリクエストのライフサイクルに沿ったフックポイントを提供します。Collyの公式ドキュメントでは、これらのコールバックを組み合わせて宣言的にスクレイピングロジックを構築するアプローチが推奨されています。
OnRequest / OnHTML / OnError コールバック
Collyの3つのコアコールバックを理解することが、プロキシローテーションの前提となります。
c := colly.NewCollector()
c.OnRequest(func(r *colly.Request) {
fmt.Printf("Visiting: %s\n", r.URL)
})
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %s\n", link)
})
c.OnError(func(r *colly.Response, err error) {
fmt.Printf("Error: %s — %v\n", r.Request.URL, err)
})
c.Visit("https://example.com")OnHTML コールバックは内部でgoqueryを使用しており、CSS セレクタベースのDOMトラバーサルが可能です。OnRequest フックではリクエストヘッダーのカスタマイズができ、プロキシ設定のタイミングとしても重要です。OnError ではステータスコードに基づいたリトライロジックを実装できます。
非同期モードと並行処理
Collyは c.Async = true を設定することで非同期モードになります。このモードでは、複数のリクエストをゴルーチンで並行実行しますが、c.Wait() で完了を待機する必要があります。並行処理を行う場合は、プロキシのローテーションがスレッドセーフであることが重要です。後述するカスタムスイッチャーでは sync/atomic パッケージを使用して安全に実装します。
プロキシサーフェスの活用: colly proxy switcher
Collyはプロキシローテーションのための公式APIを提供しています。これらはフレームワークの拡張ポイントとして設計されており、ハックではありません。
RoundRobinProxySwitcher
proxy.RoundRobinProxySwitcher は、複数のプロキシエンドポイントを順番に使用するシンプルな実装です。Collyの proxy パッケージに含まれており、数行でプロキシローテーションを開始できます。
import "github.com/gocolly/colly/v2/proxy"
switcher, err := proxy.RoundRobinProxySwitcher(
"http://user:pass@gate.proxyhat.com:8080",
"http://user-country-DE:pass@gate.proxyhat.com:8080",
"http://user-country-US:pass@gate.proxyhat.com:8080",
)
if err != nil {
log.Fatal(err)
}
c.SetProxyFunc(switcher)c.SetProxyFunc は func(*http.Request) (*url.URL, error) 型の関数を受け取ります。RoundRobinProxySwitcherはこの型を実装した関数を返すため、そのまま渡すことができます。HTTPプロキシとSOCKS5プロキシの両方を混在させることも可能です。SOCKS5を使用する場合は socks5://user:pass@gate.proxyhat.com:1080 形式で指定します。
カスタムプロキシ関数で動的ローテーション
より高度な制御が必要な場合は、独自のプロキシ関数を実装できます。これにより、リクエストごとに国、都市、セッションを動的に変更できます。これがgolang colly proxyパターンの本質です。
c.SetProxyFunc(func(r *http.Request) (*url.URL, error) {
sessionID := fmt.Sprintf("sess-%d", rand.Intn(10000))
proxyURL := fmt.Sprintf(
"http://user-country-DE-city-berlin-session-%s:pass@gate.proxyhat.com:8080",
sessionID,
)
return url.Parse(proxyURL)
})このパターンはCollyのイディオマティックな拡張ポイントです。SetProxyFunc が意図的に公開されたインターフェースであり、各リクエストの *http.Request にアクセスできるため、URLやヘッダーに基づいたプロキシ選択ロジックを自由に実装できます。
Residentialプロキシでハードターゲットに対応する
なぜResidentialプロキシが必要なのか
Datacenterプロキシは高速ですが、IP範囲がデータセンターに属しているため、多くのサイトでブロックされます。一方、Residentialプロキシは実際のISPに登録されたIPアドレスを使用するため、通常のユーザーからのアクセスと区別が困難です。SERPトラッキングやECサイトの価格監視など、高度なボット検知システムを持つターゲットでは、Residentialプロキシが事実上必須となります。SERPトラッキングのユースケースについて詳しくはこちらを参照してください。
| プロキシタイプ | IPの性質 | 成功率 | レイテンシ | 適した用途 |
|---|---|---|---|---|
| Residential | ISP割り当てIP | 95%以上 | 200-800ms | SERPスクレイピング、ECサイト監視 |
| Datacenter | データセンターIP | 50-70% | 50-200ms | 制限の緩いサイト、APIアクセス |
| Mobile | モバイルキャリアIP | 98%以上 | 500-1500ms | 最高難度のターゲット、ソーシャル |
国・都市・セッションローテーション
ProxyHatのResidentialプロキシでは、ユーザー名にフラグを追加することでジオターゲティングとセッション管理を行います。これにより、プロキシエンドポイントを変更せずに、ユーザー名のフォーマットだけでローテーション戦略を柔軟に切り替えられます。
// ドイツ・ベルリンのIPを使用
proxyURL := "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"
// スティッキーセッション(同じIPを30分間維持)
proxyURL := "http://user-session-abc123:pass@gate.proxyhat.com:8080"
// 国指定 + セッション(組み合わせ可能)
proxyURL := "http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080"セッションIDを固定すると、指定した時間(通常30分間)同じIPアドレスが割り当てられます。ログインが必要なサイトのスクレイピングでは、セッション固定が必須です。一方、単純なページ収集ではセッションを毎回変更し、IPローテーションを最大化します。利用可能なロケーションについてはプロキシロケーション一覧を参照してください。
完全な実装例: ResidentialプロキシでRoundRobin
以下は、ProxyHatのResidentialプロキシを使用した実践的なcolly web scrapingスクレイパーの完全例です。複数国のIPをラウンドロビンで切り替え、レート制限を設け、エラーハンドリングを実装しています。
package main
import (
"fmt"
"log"
"math/rand"
"time"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
c := colly.NewCollector(
colly.MaxDepth(2),
colly.UserAgent("Mozilla/5.0 (compatible; MyBot/1.0)"),
)
// プロキシエンドポイントリストを構築
proxyURLs := make([]string, 0, 5)
countries := []string{"US", "DE", "GB", "FR", "JP"}
for _, country := range countries {
u := fmt.Sprintf(
"http://user-country-%s-session-%d:pass@gate.proxyhat.com:8080",
country, rand.Intn(100000),
)
proxyURLs = append(proxyURLs, u)
}
// ラウンドロビンスイッチャーを作成
switcher, err := proxy.RoundRobinProxySwitcher(proxyURLs...)
if err != nil {
log.Fatal(err)
}
c.SetProxyFunc(switcher)
// レート制限を設定
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Delay: 2 * time.Second,
RandomDelay: 1 * time.Second,
Parallelism: 5,
})
c.OnHTML("h1", func(e *colly.HTMLElement) {
fmt.Printf("Title: %s\n", e.Text)
})
c.OnError(func(r *colly.Response, err error) {
fmt.Printf("Error on %s: %v\n", r.Request.URL, err)
})
c.OnRequest(func(r *colly.Request) {
fmt.Printf("Visiting: %s\n", r.URL)
})
c.Visit("https://example.com")
c.Wait()
}カスタムスイッチャーでリクエストごとに動的にローテーション
より細かい制御が必要な場合は、カスタムスイッチャーを実装します。sync/atomic を使用することで、ゴルーチンセーフなラウンドロビンを実現できます。Collyの非同期モードでも安全に動作します。
type ProxyRotator struct {
countries []string
proxies []*url.URL
index uint32
}
func NewProxyRotator(countries []string) *ProxyRotator {
proxies := make([]*url.URL, 0, len(countries))
for _, c := range countries {
u, _ := url.Parse(fmt.Sprintf(
"http://user-country-%s:pass@gate.proxyhat.com:8080", c,
))
proxies = append(proxies, u)
}
return &ProxyRotator{countries: countries, proxies: proxies}
}
func (p *ProxyRotator) GetProxy(r *http.Request) (*url.URL, error) {
idx := atomic.AddUint32(&p.index, 1)
return p.proxies[idx%uint32(len(p.proxies))], nil
}
// 使用例
rotator := NewProxyRotator([]string{"US", "DE", "GB", "FR", "JP"})
c.SetProxyFunc(rotator.GetProxy)この実装はCollyの設計思想に沿っています。SetProxyFunc が受け取る関数シグネチャ func(*http.Request) (*url.URL, error) に準拠し、リクエストコンテキストに基づいたプロキシ選択を可能にします。
本番運用パターン
リトライと c.Clone()
Collyの c.Clone() を使用すると、コレクターの設定を引き継いだ新しいインスタンスを作成できます。HTTP 429(Too Many Requests)や503(Service Unavailable)を受信した際のリトライに役立ちます。
c.OnError(func(r *colly.Response, err error) {
if r.StatusCode == 429 || r.StatusCode == 503 {
retryCollector := c.Clone()
retryCollector.SetProxyFunc(func(req *http.Request) (*url.URL, error) {
sessionID := fmt.Sprintf("retry-%d", time.Now().UnixNano())
u := fmt.Sprintf(
"http://user-session-%s:pass@gate.proxyhat.com:8080",
sessionID,
)
return url.Parse(u)
})
time.Sleep(5 * time.Second)
retryCollector.Visit(r.Request.URL.String())
}
})c.Clone() はコールバック、DOMセレクタ、Limit設定をすべて引き継ぎますが、ストレージとプロキシ関数は引き継がないため、リトライ時に新しいプロキシ関数を明示的に設定できます。これは意図的な設計であり、リトライごとに新しいIPを強制するのに最適です。
RandomDelay と Parallelism の調整
c.Limit() で設定する RandomDelay は、リクエスト間隔にランダム性を加えます。これにより、ボット検知システムのパターンマッチングを回避できます。固定間隔のリクエストはボットの明確なシグナルとなるため、ランダム遅延は単なるオプションではなく必須の対策です。
c.Limit(&colly.LimitRule{
DomainGlob: "example.com",
Delay: 3 * time.Second,
RandomDelay: 2 * time.Second,
Parallelism: 3,
})並行性を高めすぎるとターゲットサーバーに負荷をかけ、ブロックの原因になります。50並行から始めて、成功率を見ながら調整することをお勧めします。ProxyHatのResidentialプロキシは99.9%の稼働率を提供しますが、ターゲットサイト側の制限は別問題です。
カスタムTransport と TLS設定
一部のサイトではTLSフィンガープリントでボットを検知します。CollyのTransportをカスタマイズすることで、接続プールとTLS設定を調整できます。
import (
"crypto/tls"
"net/http"
)
c.WithTransport(&http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
})MaxIdleConns と MaxIdleConnsPerHost を適切に設定することで、接続の再利用を最適化し、TCPハンドシェイクのオーバーヘッドを削減できます。ただし、プロキシローテーションを行う場合は、同じホストでもIPが変わるため、MaxIdleConnsPerHost は低めに設定する方が効果的です。
分散スクレイピング: Redisストレージバックエンド
複数のマシンでスクレイピングを分散する場合、CollyのストレージバックエンドをRedisに変更できます。これにより、訪問済みURLの重複を防ぎ、複数ワーカー間で状態を共有できます。
import "github.com/gocolly/redisstorage"
storage := &redisstorage.Storage{
Address: "redis:6379",
Password: "",
DB: 0,
Prefix: "scraper",
}
err := c.SetStorage(storage)
if err != nil {
log.Fatal(err)
}
defer storage.Close()コンテナ環境では、Docker ComposeやKubernetesでCollyワーカーを水平スケールし、Redisで状態を共有する構成が一般的です。各ワーカーに異なる国のプロキシを割り当てることで、地理的に分散したスクレイピングが可能です。100並行セッション程度までは単一ノードで処理できますが、それ以上のスループットが必要な場合は複数ノード構成を検討してください。Webスクレイピングのユースケースについて詳しくはこちらを参照してください。
Collyを使うべきでないケースと倫理的配慮
Collyは軽量なHTTPクライアントベースのスクレイパーであり、JavaScript重視のSPA(Single Page Application)のレンダリングは行いません。以下のようなケースでは、ヘッドレスブラウザ(Chromium/Puppeteer/Playwright)の使用を検討してください。
- 動的レンダリングが必要なSPA(React、Vue、Angular)
- ページネーションがJavaScriptで制御されるサイト
- 無限スクロールのコンテンツ
- CAPTCHAトークンが必要なサイト
このような場合は、GoからPlaywrightを呼び出すか、別の言語でブラウザ自動化ツールを使用し、プロキシ設定だけProxyHatに任せるアプローチが現実的です。ProxyHatのプロキシはHTTP/SOCKS5の両方をサポートしているため、どのツールとも互換性があります。詳細はProxyHatドキュメントを参照してください。
倫理的配慮とベストプラクティス
スクレイピングを行う際は以下の原則を守ってください。
- robots.txtを尊重する — Googleのガイドラインに従い、robots.txtで禁止されたパスはクロールしない。
- レート制限を守る — ターゲットサーバーに過負荷をかけない適切な間隔を設定する。
- 公開データのみ収集 — ログイン背後のデータや個人情報は収集しない。
- 利用規約を確認 — 対象サイトのToSを確認し、スクレイピングが禁止されていないか確認する。
- GDPR/CCPAへの準拠 — EUやカリフォルニアのユーザーデータを扱う場合は、適用法規を確認する。
Key Takeaways
- Collyの
SetProxyFuncはプロキシローテーションの公式拡張ポイントであり、RoundRobinProxySwitcherとカスタム関数の両方が使える。- Residentialプロキシは、ユーザー名に
country-/city-/session-フラグを追加するだけでジオターゲティングとセッション管理が可能。c.Limit()でRandomDelayとParallelismを適切に設定し、ボット検知を回避する。c.Clone()でリトライ時の新しいセッション作成が可能。プロキシ関数は引き継がれないため、新しいIPを強制できる。- Redisストレージバックエンドで分散スクレイピングが可能。コンテナ環境での水平スケールに対応。
- JavaScript重視のSPAにはCollyではなくヘッドレスブラウザを使用する。プロキシはProxyHatで引き続き対応可能。
ProxyHatのResidentialプロキシでCollyスクレイパーを本番運用する準備ができたら、プラン一覧を確認して最適なプランを選択してください。






