Collyでのローテーティングプロキシ完全ガイド: Goスクレイパーの本番運用

Collyでのローテーティングプロキシの実装方法を解説。RoundRobinProxySwitcherとカスタムプロキシ関数でResidentialプロキシをリクエストごとに切り替え、ブロック回避と高スループットを実現する実践的なGoコード例を提供します。

Rotating Proxies in Colly: A Framework-Idiomatic Guide for Go Developers
この記事の内容

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.SetProxyFuncfunc(*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の性質成功率レイテンシ適した用途
ResidentialISP割り当てIP95%以上200-800msSERPスクレイピング、ECサイト監視
DatacenterデータセンターIP50-70%50-200ms制限の緩いサイト、APIアクセス
MobileモバイルキャリアIP98%以上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,
})

MaxIdleConnsMaxIdleConnsPerHost を適切に設定することで、接続の再利用を最適化し、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ドキュメントを参照してください。

倫理的配慮とベストプラクティス

スクレイピングを行う際は以下の原則を守ってください。

  1. robots.txtを尊重するGoogleのガイドラインに従い、robots.txtで禁止されたパスはクロールしない。
  2. レート制限を守る — ターゲットサーバーに過負荷をかけない適切な間隔を設定する。
  3. 公開データのみ収集 — ログイン背後のデータや個人情報は収集しない。
  4. 利用規約を確認 — 対象サイトのToSを確認し、スクレイピングが禁止されていないか確認する。
  5. GDPR/CCPAへの準拠 — EUやカリフォルニアのユーザーデータを扱う場合は、適用法規を確認する。

Key Takeaways

  • Collyの SetProxyFunc はプロキシローテーションの公式拡張ポイントであり、RoundRobinProxySwitcher とカスタム関数の両方が使える。
  • Residentialプロキシは、ユーザー名に country- / city- / session- フラグを追加するだけでジオターゲティングとセッション管理が可能。
  • c.Limit()RandomDelayParallelism を適切に設定し、ボット検知を回避する。
  • c.Clone() でリトライ時の新しいセッション作成が可能。プロキシ関数は引き継がれないため、新しいIPを強制できる。
  • Redisストレージバックエンドで分散スクレイピングが可能。コンテナ環境での水平スケールに対応。
  • JavaScript重視のSPAにはCollyではなくヘッドレスブラウザを使用する。プロキシはProxyHatで引き続き対応可能。

ProxyHatのResidentialプロキシでCollyスクレイパーを本番運用する準備ができたら、プラン一覧を確認して最適なプランを選択してください。

よくある質問

Collyでのローテーティングプロキシとは何ですか?

Collyでのローテーティングプロキシとは、Go言語のスクレイピングフレームワークCollyのSetProxyFunc拡張ポイントを使用して、各HTTPリクエストごとに異なるプロキシIPを割り当てる仕組みです。proxy.RoundRobinProxySwitcherで複数エンドポイントを順番に切り替えるか、カスタム関数func(*http.Request)(*url.URL,error)を実装して動的にプロキシを選択できます。これにより、ターゲットサイトのIPベースブロッキングを回避し、スクレイピングの成功率を向上させることができます。

Collyでプロキシローテーションを使用する理由は何ですか?

単一IPで大量リクエストを送信すると、ターゲットサイトのレート制限やボット検知システムによってブロックされるためです。プロキシローテーションにより各リクエストが異なるIPから送信され、通常のユーザーアクセスと区別が困難になります。特にResidentialプロキシを使用すればISP割り当てのIPでアクセスできるため、Datacenterプロキシでは50-70%の成功率が95%以上に向上します。SERPスクレイピングやECサイト価格監視など、高度なボット検知を持つサイトでは必須の技術です。

Collyのプロキシローテーションに最適なプロキシタイプは何ですか?

ターゲットの難易度によって異なります。一般的にはResidentialプロキシが最適です。ISP割り当てのIPアドレスを使用するため、通常ユーザーと区別が困難で、成功率が95%以上に達します。レイテンシは200-800msとDatacenterより高いですが、ブロック回避の観点では圧倒的に優位です。最高難度のターゲット(ソーシャルメディアなど)ではMobileプロキシ(成功率98%以上)を検討してください。制限の緩いサイトやAPIアクセスでは、低レイテンシのDatacenterプロキシでも十分な場合があります。

Collyでブロックを回避するにはどうすればよいですか?

複数の対策を組み合わせる必要があります。まず、ResidentialプロキシでリクエストごとにIPをローテーションします。次に、c.Limit()でRandomDelayを設定し、リクエスト間隔にランダム性を持たせます。固定間隔のリクエストはボットの明確なシグナルです。また、c.Clone()を使用してHTTP 429や503を受信時に新しいセッションIDでリトライします。さらに、適切なUser-Agentヘッダーを設定し、カスタムTransportでTLS設定を調整することで、TLSフィンガープリント検知も回避できます。並行性は50並行程度から始め、成功率を監視しながら調整してください。

始める準備はできましたか?

148か国以上の住宅用・ISP・モバイルプロキシ。無料アカウントを作成。

無料アカウントを作成
← ブログに戻る