Goでスクレイピングを行う開発者にとって、Collyでのプロキシローテーションは回避不可能な課題です。CollyはGoのスクレイピングフレームワークとして広く使われていますが、単一IPで大量リクエストを送ると、IPブロックやCAPTCHA、レート制限にすぐに直面します。本記事では、Collyのプロキシスイッチャーを活用し、ProxyHatのレジデンシャルプロキシでIPをリクエストごとにローテーションする実践的な方法を解説します。
法的・倫理的注意: 公開データのみを収集し、対象サイトの利用規約と RFC 9309 (robots.txt) を遵守してください。米国のCFAAや欧州のGDPRに違反する収集は行わないでください。本記事の内容は公開情報の収集を前提としています。
Collyでのプロキシローテーション:なぜ必要なのか
CollyはGo言語で書かれた高速なスクレイピングフレームワークで、GitHub で21,000以上のスターを持つ人気プロジェクトです。しかし、Collyのデフォルト設定では単一IPからリクエストを送信するため、1秒間に50リクエスト以上送ると数分でブロックされるのが一般的です。
プロキシローテーションは、リクエストごとに異なるIPアドレスを使用することで、ターゲットサイトのレート制限やIPベースのブロックを回避する技術です。Collyでは proxy.RoundRobinProxySwitcher と c.SetProxyFunc を使って、このローテーションをフレームワークの機能として統合できます。
Collyのコレクターモデルとコールバック
Collyの中核は colly.Collector 構造体です。CollectorはHTTPリクエストの送信、レスポンスの解析、コールバックの実行を管理します。主要なコールバックを理解することが、プロキシローテーションを正しく実装する第一歩です。
OnRequest — リクエスト前のフック
OnRequest は各HTTPリクエスト送信前に呼ばれます。ここでUser-Agentの設定やカスタムヘッダーの追加を行います。プロキシの選択自体は SetProxyFunc で処理されますが、OnRequest でリクエストのコンテキストにセッションIDを付与できます。
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
r.Headers.Set("Accept-Language", "ja,en-US;q=0.9")
})
OnHTML — goqueryによるDOM解析
OnHTML はレスポンスHTMLのCSSセレクタにマッチする要素に対して実行されます。内部では goquery が使われており、jQueryライクなAPIでDOMを走査できます。
c.OnHTML("div.result", func(e *colly.HTMLElement) {
title := e.ChildText("h3")
link := e.ChildAttr("a", "href")
fmt.Printf("Title: %s, Link: %s\n", title, link)
})
OnError — エラーハンドリング
OnError はHTTPエラーやパースエラー時に呼ばれます。プロキシのタイムアウトや403 Forbiddenをここで捕捉し、リトライロジックを実装します。
c.OnError(func(r *colly.Response, err error) {
log.Printf("Request URL: %s failed: %v (status: %d)",
r.Request.URL, err, r.StatusCode)
if r.StatusCode == 403 || r.StatusCode == 429 {
r.Request.Retry()
}
})
非同期モードと並行処理
Collyはデフォルトで非同期モード (c.Async = true) をサポートしています。ただし、非同期モードではコールバックが別ゴルーチンで実行されるため、共有状態へのアクセスには sync.Mutex が必要です。並行度は c.Limit() で制御します。
プロキシスイッチャーの実装
Collyでプロキシローテーションを実装するには、主に2つのアプローチがあります。
RoundRobinProxySwitcher — ラウンドロビン方式
proxy.RoundRobinProxySwitcher は、プロキシURLのリストを順番に切り替える組み込み関数です。最もシンプルなアプローチで、少人数のプロキシプールで十分な場合に適しています。
package main
import (
"log"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
proxies := []string{
"http://user-country-US:pass@gate.proxyhat.com:8080",
"http://user-country-DE:pass@gate.proxyhat.com:8080",
"http://user-country-GB:pass@gate.proxyhat.com:8080",
}
rp, err := proxy.RoundRobinProxySwitcher(proxies...)
if err != nil {
log.Fatal(err)
}
c := colly.NewCollector()
c.SetProxyFunc(rp)
c.OnHTML("title", func(e *colly.HTMLElement) {
log.Println("Title:", e.Text)
})
c.Visit("https://example.com")
c.Wait()
}
カスタムSetProxyFunc — 柔軟な制御
より高度な制御が必要な場合は、func(*http.Request) (*url.URL, error) シグネチャのカスタム関数を c.SetProxyFunc に渡します。これにより、リクエストのコンテキストやターゲットURLに基づいてプロキシを動的に選択できます。
import (
"fmt"
"math/rand"
"net/http"
"net/url"
)
func customProxyFunc(req *http.Request) (*url.URL, error) {
countries := []string{"US", "DE", "GB", "FR", "JP"}
country := countries[rand.Intn(len(countries))]
sessionID := fmt.Sprintf("sess-%d", rand.Intn(100000))
proxyStr := fmt.Sprintf(
"http://user-country-%s-session-%s:pass@gate.proxyhat.com:8080",
country, sessionID,
)
return url.Parse(proxyStr)
}
// 使用例
c := colly.NewCollector()
c.SetProxyFunc(customProxyFunc)
このアプローチでは、ProxyHatのユーザー名フィールドに -country-XX と -session-YYYY を埋め込むことで、リクエストごとに異なる国とセッション(=異なるIP)を指定できます。セッションIDを固定すればsticky sessionとして動作し、同じIPを維持できます。
レジデンシャルプロキシとジオターゲティング
厳格なアンチボット対策を持つサイト(SERP、ECサイト、ソーシャルメディア)では、データセンタープロキシではすぐにブロックされます。レジデンシャルプロキシは実際のISPに割り当てられたIPアドレスを使用するため、通常のユーザートラフィックと区別が困難です。
| プロキシタイプ | ブロック回避率 | 平均レイテンシ | 適用場面 |
|---|---|---|---|
| レジデンシャル | 高(90%以上) | 200-800ms | SERP、EC価格監視、厳格なターゲット |
| データセンター | 低(40-60%) | 10-50ms | API、緩い制限のサイト |
| モバイル | 最高(95%以上) | 300-1500ms | ソーシャル、最も厳格なターゲット |
ProxyHatでは、ユーザー名にジオターゲティングパラメータを埋め込んで国・都市レベルのターゲティングが可能です。
// 国レベルのターゲティング
"http://user-country-DE:pass@gate.proxyhat.com:8080"
// 都市レベルのターゲティング
"http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"
// セッション固定(sticky session)
"http://user-country-DE-session-abc123:pass@gate.proxyhat.com:8080"
// SOCKS5を使用する場合
"socks5://user-country-DE-session-abc123:pass@gate.proxyhat.com:1080"
ジオターゲティングは、地域ごとに異なるコンテンツを返すサイト(価格比較、ローカル検索結果など)で特に重要です。ProxyHatの対応ロケーション で利用可能な国と都市を確認できます。
実践的なGoコード例:本番対応スクレイパー
以下は、ProxyHatのレジデンシャルプロキシを使った本番対応のCollyスクレイパーの完全例です。カスタムプロキシスイッチャー、レート制限、エラーハンドリング、リトライを統合しています。
package main
import (
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"sync"
"time"
"github.com/gocolly/colly/v2"
)
func main() {
countries := []string{"US", "DE", "GB", "FR", "JP", "CA", "AU"}
customSwitcher := func(req *http.Request) (*url.URL, error) {
country := countries[rand.Intn(len(countries))]
session := fmt.Sprintf("s%d-%d", time.Now().Unix(), rand.Intn(10000))
proxyStr := fmt.Sprintf(
"http://user-country-%s-session-%s:pass@gate.proxyhat.com:8080",
country, session,
)
return url.Parse(proxyStr)
}
c := colly.NewCollector(
colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"),
colly.MaxDepth(2),
)
c.SetProxyFunc(customSwitcher)
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 10,
Delay: 2 * time.Second,
RandomDelay: 3 * time.Second,
})
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("Accept-Language", "en-US,en;q=0.9")
log.Printf("Visiting: %s", r.URL)
})
var results []string
var mu sync.Mutex
c.OnHTML("div.result", func(e *colly.HTMLElement) {
mu.Lock()
results = append(results, e.ChildText("h3"))
mu.Unlock()
})
retryCount := make(map[string]int)
c.OnError(func(r *colly.Response, err error) {
urlStr := r.Request.URL.String()
log.Printf("Error on %s: %v (status: %d)", urlStr, err, r.StatusCode)
if r.StatusCode == 403 || r.StatusCode == 429 || r.StatusCode == 503 {
if retryCount[urlStr] < 3 {
retryCount[urlStr]++
time.Sleep(5 * time.Second)
r.Request.Retry()
}
}
})
err := c.Visit("https://example.com/search?q=test")
if err != nil {
log.Fatal(err)
}
c.Wait()
log.Printf("Collected %d results", len(results))
}
この例のポイントは、プロキシ選択がCollyのフレームワーク内で処理されることです。SetProxyFunc に渡した関数が各リクエストの前に呼ばれ、新しいセッションIDを持つプロキシURLが動的に生成されます。これにより、100並行セッションでも各リクエストが異なるIPから送信されます。
本番運用のベストプラクティス
リトライとc.Clone()
Collyの c.Clone() を使うと、同じ設定を持つ新しいCollectorを作成できます。エラー発生時に別のプロキシでリトライする際に便利です。クローン元のコールバックはコピーされないため、再設定が必要です。
retryCollector := c.Clone()
retryCollector.SetProxyFunc(func(req *http.Request) (*url.URL, error) {
session := fmt.Sprintf("retry-%d", time.Now().UnixNano())
return url.Parse(fmt.Sprintf(
"http://user-country-US-session-%s:pass@gate.proxyhat.com:8080",
session,
))
})
retryCollector.OnHTML("div.result", handler)
retryCollector.Visit(failedURL)
RandomDelayとParallelismの調整
Limit ルールで RandomDelay を設定すると、リクエスト間隔にランダム性が加わり、ボット検知を回避しやすくなります。Parallelism は同時接続数を制限し、対象サーバーへの負荷を抑えます。100並行セッションを超える場合は、プロキシプロバイダーの同時接続制限にも注意してください。
カスタムTransportとTLS設定
デフォルトの http.Transport をカスタマイズして、タイムアウトやTLS設定を調整できます。一部のプロキシではTLSハンドシェイクのタイムアウトが原因でエラーになることがあります。15秒のタイムアウトを設定することで、遅いプロキシでもハングアップを防げます。
c.WithTransport(&http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
},
DialContext: (&net.Dialer{
Timeout: 15 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
})
分散スクレイピング:Redisストレージバックエンド
Collyは storage インターフェースを通じて分散スクレイピングをサポートしています。Redisバックエンド を使うと、複数のCollyインスタンスで訪問済みURLを共有し、重複クロールを防げます。
import "github.com/gocolly/colly/v2/storage"
redisStorage := &storage.RedisStorage{
Address: "localhost:6379",
Password: "",
DB: 0,
Prefix: "scraper_",
}
c.SetStorage(redisStorage)
defer redisStorage.Close()
複数のコンテナでCollyを実行し、Redisで状態を共有することで、水平スケーリングが可能になります。Kubernetesでデプロイする場合、各PodにCollyインスタンスを配置し、共通のRedisインスタンスに接続する構成が一般的です。99.9%の可用性が必要な本番環境では、RedisにSentinelまたはCluster構成を使用してください。
Collyを使うべきでないケース
Collyは軽量なHTTPクライアントベースのスクレイパーであり、JavaScriptでレンダリングされるSPA(Single Page Application)には対応できません。React、Vue、Angularで構築されたサイトでコンテンツがクライアントサイドレンダリングされる場合は、ブラウザベースのツールが必要です。
- chromedp — GoからChrome DevTools Protocolを操作
- Playwright — Node.js/Pythonでヘッドレスブラウザを制御
- Rod — Goネイティブのブラウザ自動化ライブラリ
ブラウザベースのスクレイピングでもプロキシローテーションは同様に重要です。ProxyHatのプロキシはブラウザのプロキシ設定としても利用可能です。詳しくは ウェブスクレイピングのユースケース を参照してください。
倫理的考量とコンプライアンス
スクレイピングを行う際は以下を遵守してください:
- robots.txtの尊重 — デフォルトでCollyはrobots.txtを遵守します。
colly.IgnoreRobotsTxt()は避けてください - レート制限の遵守 — 対象サイトのサーバーに過度な負荷をかけない
- 公開データのみ収集 — ログインが必要なページや個人情報の無断収集は避ける
- GDPR/CCPA準拠 — EUやカリフォルニア州の居住者に関するデータ収集は法的要件を確認
- 利用規約の確認 — 対象サイトのToSを確認し、スクレイピングが禁止されていないか確認
SERP追跡や価格監視など、商用目的でのデータ収集については SERP追跡のユースケース を参照してください。
Key Takeaways
- Collyの
proxy.RoundRobinProxySwitcherはシンプルなローテーションに、カスタムSetProxyFuncは動的なプロキシ選択に適している- ProxyHatのユーザー名フィールドに
-country-XXと-session-YYYYを埋め込むことで、リクエストごとに国とIPを動的に切り替えられる- 厳格なアンチボット対策があるサイトではレジデンシャルプロキシが必須(ブロック回避率90%以上)
LimitでParallelismとRandomDelayを設定し、サーバー負荷を抑えつつボット検知を回避する- JS-heavyなSPAにはCollyではなくブラウザベースのツールを使用する
- 分散スクレイピングにはRedisストレージバックエンドを活用し、複数インスタンスで状態を共有する
ProxyHatの料金プランでは、レジデンシャルプロキシのギガバイト単位の従量課金と、無制限プランを提供しています。まずは少規模でテストし、成功率とレイテンシを測定してからスケールアップすることをお勧めします。詳細な設定については ProxyHatドキュメント も参照してください。
FAQ
Collyでのプロキシローテーションとは何ですか?
Collyでのプロキシローテーションとは、SetProxyFunc または proxy.RoundRobinProxySwitcher を使って、HTTPリクエストごとに異なるプロキシIPアドレスを割り当てる手法です。これにより、単一IPによるブロックやレート制限を回避し、大規模なデータ収集を安定して行えます。ProxyHatのレジデンシャルプロキシと組み合わせることで、厳格なアンチボット対策を持つサイトでも高い成功率を維持できます。
なぜCollyでプロキシローテーションが重要なのですか?
単一IPから短時間に大量リクエストを送ると、ターゲットサイトのアンチボットシステムが即座にブロックします。プロキシローテーションにより、リクエストを複数のIPに分散させ、各IPのリクエスト頻度を低く保つことで、ブロックを回避できます。レジデンシャルプロキシを使えば、ブロック回避率を90%以上に上げられ、SERPスクレイピングやEC価格監視などの用途で安定したデータ収集が可能になります。
Collyでどのプロキシタイプを使うべきですか?
ターゲットサイトのアンチボット対策の厳格さによります。SERPやECサイトなど厳格なターゲットにはレジデンシャルプロキシ(ブロック回避率90%以上)、APIや緩い制限のサイトにはデータセンタープロキシ(レイテンシ10-50ms)、ソーシャルメディアなど最も厳格なターゲットにはモバイルプロキシ(ブロック回避率95%以上)を推奨します。ProxyHatでは3タイプすべてを提供しており、ユーザー名パラメータで簡単に切り替えられます。
Collyでブロックを回避するにはどうすればいいですか?
レジデンシャルプロキシのローテーション、RandomDelay によるリクエスト間隔のランダム化、適切なUser-AgentとAccept-Languageヘッダーの設定、Parallelism の制限、エラー時のリトライとプロキシ切り替えを組み合わせてください。また、robots.txtを尊重し、公開データのみを収集することが長期的な安定性に繋がります。ProxyHatのセッションID機能を使えば、同じドメイン内でsticky sessionを維持しつつ、エラー時に新しいセッションに切り替えることも可能です。






