カタナとキャップソルバーの統合: ウェブクローリング用の自動CAPTCHA解決

Sora Fujimoto
AI Solutions Architect
12-Jan-2026

ウェブクローリングは、セキュリティ研究者、ペネトレーションテスター、データアナリストにとって重要な技術です。しかし、現代のウェブサイトはますます自動アクセスを防ぐためにCAPTCHAを採用しています。このガイドでは、Katana(ProjectDiscoveryの強力なウェブクローラー)とCapSolver(リーディングなCAPTCHA解決サービス)を統合し、自動的にCAPTCHAチャレンジを処理する堅牢なクローリングソリューションを構築する方法を紹介します。
学ぶ内容
- ヘッドレスブラウザモードでのKatanaの設定
- CapSolverのAPIを自動CAPTCHA解決に統合
- reCAPTCHA v2とCloudflare Turnstileの処理
- 各CAPTCHAタイプの完全な実行可能なコード例
- 効率的で責任あるクローリングのベストプラクティス
什麼Katana?
Katana は、ProjectDiscoveryによって開発された次世代のウェブクローリングフレームワークです。高速で柔軟性があり、セキュリティ調査と自動化パイプラインに最適です。
主な特徴
- 2つのクローリングモード: 通常のHTTPベースのクローリングとヘッドレスブラウザの自動化
- JavaScriptサポート: JavaScriptでレンダリングされたコンテンツを解析・クロール
- 柔軟な設定: カスタムヘッダー、クッキー、フォーム入力、スコープ制御
- 複数の出力形式: テキスト、JSON、またはJSONL
インストール
bash
# Go 1.24+が必要
CGO_ENABLED=1 go install github.com/projectdiscovery/katana/cmd/katana@latest
基本的な使い方
bash
katana -u https://example.com -headless
什么是CapSolver?
CapSolver は、AIを活用したCAPTCHA解決サービスで、さまざまなタイプのCAPTCHAに高速かつ信頼性高く対応します。
サポートされているCAPTCHAタイプ
- reCAPTCHA: v2とEnterpriseバージョン
- Cloudflare: TurnstileとChallenge
- AWS WAF: WAF保護の回避
- およびその他
APIワークフロー
CapSolverはタスクベースのAPIモデルを使用します:
- タスクの作成: CAPTCHAパラメータ(タイプ、siteKey、URL)を送信
- タスクIDの取得: 一意のタスク識別子を受信
- 結果のポーリング: ソリューションが準備できるまでタスクステータスを確認
- トークンの取得信: 解決されたCAPTCHAトークンを取得
必要条件
開始する前に、以下の項目確認してください:
- **Go 1.24+**がインストールされていること
- CapSolver APIキー - 登に登録
- Chromeブラウザ(ヘッドレスモード用)
APIキーを環境変数として設定してください:
bash
export CAPSOLVER_API_KEY="YOUR_API_KEY"
結合アーキテクチャ
┌─────────────────────────┐
│ Goアプリケーション │
│ (go-rodブラウザ) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ ターゲットウェブサイト │
│ (CAPTCHAあり) │
└───────────┬─────────────┘
│
CAPTCHA検出
│
▼
┌─────────────────────────┐
│ パラメータの抽出 │
│ (siteKey、URL、タイプ) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ CapSolver API │
│ createTask() │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ 結果のポーリング │
│ getTaskResult() │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ トークンの挿入 │
│ ページに注入 │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ クローリングを継続 │
└─────────────────────────┘
CapSolverでreCAPTCHA v2を解決する方法
reCAPTCHA v2は最も一般的なCAPTCHAタイプで、「I'm not a robot」チェックボックスや画像チャレンジを表示します。以下はreCAPTCHA v2を解決する完全な実行可能なスクリプトです:
go
// reCAPTCHA v2 ソルバー - 完全な例
// 使用方法: go run main.go
// 必要条件: CAPSOLVER_API_KEY環境変数
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
)
// 設定
var (
CAPSOLVER_API_KEY = os.Getenv("CAPSOLVER_API_KEY")
CAPSOLVER_API = "https://api.capsolver.com"
)
// APIレスポンス構造
type CreateTaskResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
TaskID string `json:"taskId"`
}
type GetTaskResultResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
Status string `json:"status"`
Solution struct {
GRecaptchaResponse string `json:"gRecaptchaResponse"`
} `json:"solution"`
}
type BalanceResponse struct {
ErrorID int `json:"errorId"`
Balance float64 `json:"balance"`
}
// CapSolverClientはAPI通信を処理します
type CapsolverClient struct {
APIKey string
Client *http.Client
}
// NewCapsolverClientは新しいCapSolverクライアントを作成します
func NewCapsolverClient(apiKey string) *CapsolverClient {
return &CapsolverClient{
APIKey: apiKey,
Client: &http.Client{Timeout: 120 * time.Second},
}
}
// GetBalanceはアカウント残高を取得します
func (c *CapsolverClient) GetBalance() (float64, error) {
payload := map[string]string{"clientKey": c.APIKey}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getBalance", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result BalanceResponse
json.Unmarshal(body, &result)
if result.ErrorID != 0 {
return 0, fmt.Errorf("残高確認失失敗しました")
}
return result.Balance, nil
}
// SolveRecaptchaV2はreCAPTCHA v2チャレンジを解決します
func (c *CapsolverClient) SolveRecaptchaV2(websiteURL, siteKey string) (string, error) {
log.Printf(" %s のreCAPTCHA v2タスクを作成しています", websiteURL)
// タスクの作成
task := map[string]interface{}{
"type": "ReCaptchaV2TaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": siteKey,
}
payload := map[string]interface{}{
"clientKey": c.APIKey,
"task": task,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("タスクの作成に失敗しました: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var createResult CreateTaskResponse
json.Unmarshal(body, &createResult)
if createResult.ErrorID != 0 {
return "", fmt.Errorf("APIエラー: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
}
log.Printf("タスクを作成しました: %s", createResult.TaskID)
// 結果のポーリング
for i := 0; i < 120; i++ {
result, err := c.getTaskResult(createResult.TaskID)
if err != nil {
return "", err
}
if result.Status == "ready" {
log.Printf("CAPTCHAが正常に解決しました!")
return result.Solution.GRecaptchaResponse, nil
}
if result.Status == "failed" {
return "", fmt.Errorf("タスクが失敗しました: %s", result.ErrorDescription)
}
if i%10 == 0 {
log.Printf("解決を待機中... (%ds)", i)
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("解決を待つするタイムアウト")
}
func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
payload := map[string]string{
"clientKey": c.APIKey,
"taskId": taskID,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result GetTaskResultResponse
json.Unmarshal(body, &result)
return &result, nil
}
// extractSiteKeyはページHTMLからreCAPTCHAサイトキーを抽出します
func extractSiteKey(html string) string {
// data-sitekey属性を検索
patterns := []string{
`data-sitekey="`,
`data-sitekey='`,
`"sitekey":"`,
`'sitekey':'`,
}
for _, pattern := range patterns {
if idx := strings.Index(html, pattern); idx != -1 {
start := idx + len(pattern)
end := start
for end < len(html) && html[end] != '"' && html[end] != '\'' {
end++
}
if end > start {
return html[start:end]
}
}
}
return ""
}
// injectRecaptchaTokenは解決されたトークンをページに挿入します
func injectRecaptchaToken(page *rod.Page, token string) error {
js := fmt.Sprintf(`
(function() {
// 応答テキストエリアを設定
var responseField = document.getElementById('g-recaptcha-response');
if (responseField) {
responseField.style.display = 'block';
responseField.value = '%s';
}
// 他の隠しテキストエリアも設定
var textareas = document.querySelectorAll('textarea[name="g-recaptcha-response"]');
for (var i = 0; i < textareas.length; i++) {
textareas[i].value = '%s';
}
// コールバックが存在する場合を処理
if (typeof ___grecaptcha_cfg !== 'undefined') {
var clients = ___grecaptcha_cfg.clients;
for (var key in clients) {
var client = clients[key];
if (client) {
// コールバックを検索して呼び出す
try {
var callback = client.callback ||
(client.Q && client.Q.callback) ||
(client.S && client.S.callback);
if (typeof callback === 'function') {
callback('%s');
}
} catch(e) {}
}
}
}
return true;
})();
`, token, token, token)
_, err := page.Eval(js)
return err
}
func main() {
// APIキーのチェック
if CAPSOLVER_API_KEY == "" {
log.Fatal("CAPSOLVER_API_KEY環境変数が必要です")
}
// ターゲットURL - GoogleのreCAPTCHAデモページ
targetURL := "https://www.google.com/recaptcha/api2/demo"
log.Println("==============================================")
log.Println("Katana + CapSolver - reCAPTCHA v2デモ")
log.Println("==============================================")
// CapSolverクライアントの初期化
client := NewCapsolverClient(CAPSOLVER_API_KEY)
// 残高の確認
balance, err := client.GetBalance()
if err != nil {
log.Printf("警告: 残高を確認できませんでした: %v", err)
} else {
log.Printf("CapSolver残高: $%.2f", balance)
}
// ブラウザの起動
log.Println("ブラウザを起動しています...")
path, _ := launcher.LookPath()
u := launcher.New().Bin(path).Headless(true).MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
defer browser.MustClose()
// ターゲットに移動
log.Printf("移に移動しています: %s", targetURL)
page := browser.MustPage(targetURL)
page.MustWaitLoad()
time.Sleep(2 * time.Second)
// ページHTMLを取得し、サイトキーを抽出
html := page.MustHTML()
// reCAPTCHAの確認
if !strings.Contains(html, "g-recaptcha") && !strings.Contains(html, "grecaptcha") {
log.Fatal("ページにreCAPTCHAが見つかりません")
}
log.Println("reCAPTCHAが検出されました!")
// サイトキーの抽出
siteKey := extractSiteKey(html)
if siteKey == "" {
log.Fatal("サイトキーを抽出できませんでした")
}
log.Printf("サイトキー: %s", siteKey)
// CAPTCHAの解決
log.Println("CapSolverでCAPTCHAを解決しています...")
token, err := client.SolveRecaptchaV2(targetURL, siteKey)
if err != nil {
log.Fatalf("CAPTCHAの解決に失敗しました: %v", err)
}
log.Printf("トークンを取得しました: %s...", token[:50])
// トークンの挿入
log.Println("トークンをページに挿入しています...")
err = injectRecaptchaToken(page, token)
if err != nil {
log.Fatalf("トークンの挿入に失敗しました: %v", err)
}
// フォームの送信
log.Println("フォームを送信しています...")
submitBtn := page.MustElement("#recaptcha-demo-submit")
submitBtn.MustClick()
// 結果を
time.Sleep(3 * time.Second)
// 結果の確認
newHTML := page.MustHTML()
if strings.Contains(newHTML, "Verification Success") || strings.Contains(newHTML, "success") {
log.Println("==============================================")
log.Println("成功!reCAPTCHAが解決され、検証されました!")
log.Println("==============================================")
} else {
log.Println("フォームが送信されました - ページを確認してください")
}
// ページタイトルを取得
title := page.MustEval(`document.title`).String()
log.Printf("最終的なページタイトル: %s", title)
}
設定と実行
bash
# プロジェクトの作成
mkdir katana-recaptcha-v2
cd katana-recaptcha-v2
go mod init katana-recaptcha-v2
# 依存関係のインストール
go get github.com/go-rod/rod@latest
# APIキーの設定
export CAPSOLVER_API_KEY="YOUR_API_KEY"
# 実行
go run main.go
CapSolverでCloudflare Turnstileを解決する方法
Cloudflare Turnstileはプライバシーを重視したCAPTCHAの代替です。以下は完全なスクリプトです:
go
// Cloudflare Turnstile ソルバー - 完全な例
// 使用方法: go run main.go
// 必要条件: CAPSOLVER_API_KEY環境変数
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
)
// 設定
var (
CAPSOLVER_API_KEY = os.Getenv("CAPSOLVER_API_KEY")
CAPSOLVER_API = "https://api.capsolver.com"
)
// APIレスポンス構造
type CreateTaskResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
TaskID string `json:"taskId"`
}
type GetTaskResultResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
Status string `json:"status"`
Solution struct {
Token string `json:"token"`
} `json:"solution"`
}
type BalanceResponse struct {
ErrorID int `json:"errorId"`
Balance float64 `json:"balance"`
}
// CapSolverClientはAPI通信を処理します
type CapsolverClient struct {
APIKey string
Client *http.Client
}
// NewCapsolverClientは新しいCapSolverクライアントを作成します
func NewCapsolverClient(apiKey string) *CapsolverClient {
return &CapsolverClient{
APIKey: apiKey,
Client: &http.Client{Timeout: 120 * time.Second},
}
}
// GetBalanceはアカウント残高を取得します
func (c *CapsolverClient) GetBalance() (float64, error) {
payload := map[string]string{"clientKey": c.APIKey}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getBalance", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result BalanceResponse
json.Unmarshal(body, &result)
return result.Balance, nil
}
// SolveTurnstileはCloudflare Turnstileチャレンジを解決します
func (c *CapsolverClient) SolveTurnstile(websiteURL, siteKey string) (string, error) {
log.Printf(" %s のTurnstileタスクを作成しています", websiteURL)
// タスクの作成
task := map[string]interface{}{
"type": "AntiTurnstileTaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": siteKey,
}
payload := map[string]interface{}{
"clientKey": c.APIKey,
"task": task,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("タスクの作成に失敗しました: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var createResult CreateTaskResponse
json.Unmarshal(body, &createResult)
if createResult.ErrorID != 0 {
return "", fmt.Errorf("APIエラー: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
}
log.Printf("タスクを作成しました: %s", createResult.TaskID)
// 結果のポーリング
for i := 0; i < 120; i++ {
result, err := c.getTaskResult(createResult.TaskID)
if err != nil {
return "", err
}
if result.Status == "ready" {
log.Printf("CAPTCHAが正常に解決しました!")
return result.Solution.Token, nil
}
if result.Status == "failed" {
return "", fmt.Errorf("タスクが失敗しました: %s", result.ErrorDescription)
}
if i%10 == 0 {
log.Printf("解決を待機中... (%ds)", i)
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("解決を待機するタイムアウト")
}
func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
payload := map[string]string{
"clientKey": c.APIKey,
"taskId": taskID,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result GetTaskResultResponse
json.Unmarshal(body, &result)
return &result, nil
}
// extractSiteKeyはページHTMLからTurnstileサイトキーを抽出します
func extractSiteKey(html string) string {
// data-sitekey属性を検索
patterns := []string{
`data-sitekey="`,
`data-sitekey='`,
`"sitekey":"`,
`'sitekey':'`,
}
for _, pattern := range patterns {
if idx := strings.Index(html, pattern); idx != -1 {
start := idx + len(pattern)
end := start
for end < len(html) && html[end] != '"' && html[end] != '\'' {
end++
}
if end > start {
return html[start:end]
}
}
}
return ""
}
// injectTurnstileTokenは解決されたトークンをページに挿入します
func injectTurnstileToken(page *rod.Page, token string) error {
js := fmt.Sprintf(`
(function() {
// トークンを設定
var tokenField = document.querySelector('input[name="cf-turnstile-response"]');
if (tokenField) {
tokenField.value = '%s';
}
// クリックイベントをトリガー
var event = new MouseEvent('click');
tokenField.dispatchEvent(event);
return true;
})();
`, token)
_, err := page.Eval(js)
return err
}
func main() {
// APIキーのチェック
if CAPSOLVER_API_KEY == "" {
log.Fatal("CAPSOLVER_API_KEY環境変数が必要です")
}
// ターゲットURL - Cloudflare Turnstileデモページ
targetURL := "https://challenges.cloudflare.com/cdn-cgi/challenge-platform/1/14a7978e-603f-48c8-8b05-638c9807396e"
log.Println("==============================================")
log.Println("Katana + CapSolver - Cloudflare Turnstile デモ")
log.Println("==============================================")
// CapSolverクライアントの初期化
client := NewCapsolverClient(CAPSOLVER_API_KEY)
// 残高の確認
balance, err := client.GetBalance()
if err != nil {
log.Printf("警告: 残高を確認できませんでした: %v", err)
} else {
log.Printf("CapSolver残高: $%.2f", balance)
}
// ブラウザの起動
log.Println("ブラウザを起動しています...")
path, _ := launcher.LookPath()
u := launcher.New().Bin(path).Headless(true).MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
defer browser.MustClose()
// ターゲットに移動
log.Printf("移動中: %s", targetURL)
page := browser.MustPage(targetURL)
page.MustWaitLoad()
time.Sleep(2 * time.Second)
// ページHTMLを取得し、サイトキーを抽出
html := page.MustHTML()
// Turnstileの確認
if !strings.Contains(html, "cf-turnstile") {
log.Fatal("ページにTurnstileが見つかりません")
}
log.Println("Turnstileが検出されました!")
// サイトキーの抽出
siteKey := extractSiteKey(html)
if siteKey == "" {
log.Fatal("サイトキーを抽出できませんでした")
}
log.Printf("サイトキー: %s", siteKey)
// CAPTCHAの解決
log.Println("CapSolverでCAPTCHAを解決しています...")
token, err := client.SolveTurnstile(targetURL, siteKey)
if err != nil {
log.Fatalf("CAPTCHAの解決に失敗しました: %v", err)
}
log.Printf("トークンを取得しました: %s...", token[:50])
// トークンの挿入
log.Println("トークンをページに挿入しています...")
err = injectTurnstileToken(page, token)
if err != nil {
log.Fatalf("トークンの挿入に失敗しました: %v", err)
}
// フォームの送信
log.Println("フォームを送信しています...")
submitBtn := page.MustElement("button[type='submit']")
submitBtn.MustClick()
// 結果を待機
time.Sleep(3 * time.Second)
// 結果の確認
newHTML := page.MustHTML()
if strings.Contains(newHTML, "success") {
log.Println("==============================================")
log.Println("成功!Cloudstileが解決され、検証されました!")
log.Println("==============================================")
} else {
log.Println("フォームが送信されました - ページを確認してください")
}
// ページタイトルを取得
title := page.MustEval(`document.title`).String()
log.Printf("最終的なページタイトル: %s", title)
}
"websiteKey": siteKey,
}
payload := map[string]interface{}{
"clientKey": c.APIKey,
"task": task,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("タスクの作成に失敗しました: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var createResult CreateTaskResponse
json.Unmarshal(body, &createResult)
if createResult.ErrorID != 0 {
return "", fmt.Errorf("APIエラー: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
}
log.Printf("タスクが作成されました: %s", createResult.TaskID)
// 結果をポーリング
for i := 0; i < 120; i++ {
result, err := c.getTaskResult(createResult.TaskID)
if err != nil {
return "", err
}
if result.Status == "ready" {
log.Printf("Turnstileが正常に解決されました!")
return result.Solution.Token, nil
}
if result.Status == "failed" {
return "", fmt.Errorf("タスクが失敗しました: %s", result.ErrorDescription)
}
if i%10 == 0 {
log.Printf("解決待ち... (%ds)", i)
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("解決待ちのタイムアウト")
}
func (c *CapsolverClient) getTaskResult(taskID string) (*GetTaskResultResponse, error) {
payload := map[string]string{
"clientKey": c.APIKey,
"taskId": taskID,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result GetTaskResultResponse
json.Unmarshal(body, &result)
return &result, nil
}
// extractTurnstileSiteKey はページのHTMLからTurnstileサイトキーを抽出します
func extractTurnstileSiteKey(html string) string {
// パターン1: cf-turnstile divのdata-sitekey属性
patterns := []string{
cf-turnstile[^>]*data-sitekey=['"]([^'"]+)['"],
data-sitekey=['"]([^'"]+)['"][^>]*class=['"][^'"]*cf-turnstile,
turnstile\.render\s*\([^,]+,\s*\{[^}]*sitekey['":\s]+['"]([^'"]+)['"],
sitekey['":\s]+['"]([0-9a-zA-Z_-]+)['"],
}
for _, pattern := range patterns {
re := regexp.MustCompile(pattern)
matches := re.FindStringSubmatch(html)
if len(matches) > 1 {
return matches[1]
}
}
return ""
}
// injectTurnstileToken は解決されたトークンをページに挿入します
func injectTurnstileToken(page *rod.Page, token string) error {
js := fmt.Sprintf(`
(function() {
// cf-turnstile-responseフィールドを設定します
var responseField = document.querySelector('[name="cf-turnstile-response"]');
if (responseField) {
responseField.value = '%s';
}
// IDで検索も試みます
var byId = document.getElementById('cf-turnstile-response');
if (byId) {
byId.value = '%s';
}
// 必要に応じて隠し入力を作成します
if (!responseField && !byId) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'cf-turnstile-response';
input.value = '%s';
var form = document.querySelector('form');
if (form) {
form.appendChild(input);
}
}
// コールバックをトリガーします
if (window.turnstile && window.turnstileCallback) {
window.turnstileCallback('%s');
}
return true;
})();
`, token, token, token, token)
_, err := page.Eval(js)
return err
}
func main() {
// APIキーをチェックします
if CAPSOLVER_API_KEY == "" {
log.Fatal("CAPSOLVER_API_KEY環境変数が必要です")
}
// ターゲットURL - Cloudflare Turnstileを使用しているサイトに置き換えてください
targetURL := "https://example.com"
log.Println("==============================================")
log.Println("Katana + Capsolver - Turnstileデモ")
log.Println("==============================================")
// Capsolverクライアントを初期化します
client := NewCapsolverClient(CAPSOLVER_API_KEY)
// 残高をチェックします
balance, err := client.GetBalance()
if err != nil {
log.Printf("警告: 残高をチェックできませんでした: %v", err)
} else {
log.Printf("Capsolver残高: $%.2f", balance)
}
// ブラウザを起動します
log.Println("ブラウザを起動しています...")
path, _ := launcher.LookPath()
u := launcher.New().Bin(path).Headless(true).MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
defer browser.MustClose()
// ターゲットにアクセスします
log.Printf("アクセス中: %s", targetURL)
page := browser.MustPage(targetURL)
page.MustWaitLoad()
time.Sleep(2 * time.Second)
// ページHTMLを取得します
html := page.MustHTML()
// Turnstileをチェックします
if !strings.Contains(html, "cf-turnstile") && !strings.Contains(html, "turnstile") {
log.Println("ページにTurnstileが見つかりません")
log.Println("ヒント: targetURLをCloudflare Turnstileを使用しているサイトに置き換えてください")
return
}
log.Println("Cloudflare Turnstileが検出されました!")
// サイトキーを抽出します
siteKey := extractTurnstileSiteKey(html)
if siteKey == "" {
log.Fatal("サイトキーを抽出できませんでした")
}
log.Printf("サイトキー: %s", siteKey)
// Turnstileを解決します
log.Println("CapsolverでTurnstileを解決しています...")
token, err := client.SolveTurnstile(targetURL, siteKey)
if err != nil {
log.Fatalf("Turnstileの解決に失敗しました: %v", err)
}
log.Printf("トークンを取得しました: %s...", token[:min(50, len(token))])
// トークンを挿入します
log.Println("トークンをページに挿入しています...")
err = injectTurnstileToken(page, token)
if err != nil {
log.Fatalf("トークンの挿入に失敗しました: %v", err)
}
log.Println("==============================================")
log.Println("成功!Turnstileトークンが挿入されました!")
log.Println("==============================================")
// ページタイトルを取得します
title := page.MustEval(`document.title`).String()
log.Printf("ページタイトル: %s", title)
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
### Turnstileのポイント
1. **タスクタイプ**: `AntiTurnstileTaskProxyLess` を使用してください
2. **レスポンスフィールド**: Turnstileは `g-recaptcha-response` ではなく `cf-turnstile-response` を使用します
3. **より高速な解決**: Turnstileは通常、reCAPTCHAよりも高速に解決されます (1-10秒)
4. **トークンフィールド**: 解決結果は `solution.token` に含まれます `solution.gRecaptchaResponse` ではありません
---
## 万能CAPTCHAクローラー
すべてのCAPTCHAタイプを自動的に検出および解決する完全なモジュール式クローラーです:
```go
// 万能CAPTCHAクローラー - 完全な例
// reCAPTCHA v2とTurnstileを自動的に検出および解決します
// 使用方法: go run main.go -url "https://example.com"
// 必要条件: CAPSOLVER_API_KEY環境変数
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
)
// ============================================
// 設定
// ============================================
var (
CAPSOLVER_API_KEY = os.Getenv("CAPSOLVER_API_KEY")
CAPSOLVER_API = "https://api.capsolver.com"
)
// CaptchaType はさまざまなCAPTCHAタイプを表します
type CaptchaType string
const (
RecaptchaV2 CaptchaType = "recaptcha_v2"
Turnstile CaptchaType = "turnstile"
Unknown CaptchaType = "unknown"
)
// CaptchaInfo は抽出されたCAPTCHAパラメータを含みます
type CaptchaInfo struct {
Type CaptchaType
SiteKey string
}
// ============================================
// APIタイプ
// ============================================
type CreateTaskResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
TaskID string `json:"taskId"`
}
type GetTaskResultResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
Status string `json:"status"`
Solution struct {
GRecaptchaResponse string `json:"gRecaptchaResponse"`
Token string `json:"token"`
} `json:"solution"`
}
type BalanceResponse struct {
ErrorID int `json:"errorId"`
Balance float64 `json:"balance"`
}
// ============================================
// Capsolverクライアント
// ============================================
type CapsolverClient struct {
APIKey string
Client *http.Client
}
func NewCapsolverClient(apiKey string) *CapsolverClient {
return &CapsolverClient{
APIKey: apiKey,
Client: &http.Client{Timeout: 120 * time.Second},
}
}
func (c *CapsolverClient) GetBalance() (float64, error) {
payload := map[string]string{"clientKey": c.APIKey}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getBalance", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result BalanceResponse
json.Unmarshal(body, &result)
return result.Balance, nil
}
func (c *CapsolverClient) Solve(info *CaptchaInfo, websiteURL string) (string, error) {
switch info.Type {
case RecaptchaV2:
return c.solveRecaptchaV2(websiteURL, info.SiteKey)
case Turnstile:
return c.solveTurnstile(websiteURL, info.SiteKey)
default:
return "", fmt.Errorf("サポートされていないCAPTCHAタイプ: %s", info.Type)
}
}
func (c *CapsolverClient) solveRecaptchaV2(websiteURL, siteKey string) (string, error) {
task := map[string]interface{}{
"type": "ReCaptchaV2TaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": siteKey,
}
return c.solveTask(task, "recaptcha")
}
func (c *CapsolverClient) solveTurnstile(websiteURL, siteKey string) (string, error) {
task := map[string]interface{}{
"type": "AntiTurnstileTaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": siteKey,
}
return c.solveTask(task, "turnstile")
}
func (c *CapsolverClient) solveTask(task map[string]interface{}, tokenType string) (string, error) {
// タスクを作成します
payload := map[string]interface{}{
"clientKey": c.APIKey,
"task": task,
}
jsonData, _ := json.Marshal(payload)
resp, err := c.Client.Post(CAPSOLVER_API+"/createTask", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("タスクの作成に失敗しました: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var createResult CreateTaskResponse
json.Unmarshal(body, &createResult)
if createResult.ErrorID != 0 {
return "", fmt.Errorf("APIエラー: %s - %s", createResult.ErrorCode, createResult.ErrorDescription)
}
log.Printf("タスクが作成されました: %s", createResult.TaskID)
// 結果をポーリング
for i := 0; i < 120; i++ {
getPayload := map[string]string{
"clientKey": c.APIKey,
"taskId": createResult.TaskID,
}
jsonData, _ := json.Marshal(getPayload)
resp, err := c.Client.Post(CAPSOLVER_API+"/getTaskResult", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return "", err
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
var result GetTaskResultResponse
json.Unmarshal(body, &result)
if result.Status == "ready" {
if tokenType == "turnstile" {
return result.Solution.Token, nil
}
return result.Solution.GRecaptchaResponse, nil
}
if result.Status == "failed" {
return "", fmt.Errorf("タスクが失敗しました: %s", result.ErrorDescription)
}
if i%10 == 0 {
log.Printf("解決待ち... (%ds)", i)
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("解決待ちのタイムアウト")
}
// ============================================
// CAPTCHA検出
// ============================================
func DetectCaptcha(html string) *CaptchaInfo {
// reCAPTCHA v2 (チェックボックス)をチェックします
if strings.Contains(html, "g-recaptcha") {
siteKey := extractDataSiteKey(html, "g-recaptcha")
if siteKey != "" {
return &CaptchaInfo{
Type: RecaptchaV2,
SiteKey: siteKey,
}
}
}
// Cloudflare Turnstileをチェックします
if strings.Contains(html, "cf-turnstile") || strings.Contains(html, "challenges.cloudflare.com/turnstile") {
siteKey := extractDataSiteKey(html, "cf-turnstile")
if siteKey != "" {
return &CaptchaInfo{
Type: Turnstile,
SiteKey: siteKey,
}
}
}
return nil
}
func extractDataSiteKey(html, className string) string {
pattern := fmt.Sprintf(`class=['"][^'"]*%s[^'"]*['"][^>]*data-sitekey=['"]([^'"]+)['"]`, className)
re := regexp.MustCompile(pattern)
matches := re.FindStringSubmatch(html)
if len(matches) > 1 {
return matches[1]
}
// 代替パターン
pattern = fmt.Sprintf(`data-sitekey=['"]([^'"]+)['"][^>]*class=['"][^'"]*%s`, className)
re = regexp.MustCompile(pattern)
matches = re.FindStringSubmatch(html)
if len(matches) > 1 {
return matches[1]
}
// 一般的なサイトキーパターン
re = regexp.MustCompile(`data-sitekey=['"]([^'"]+)['"]`)
matches = re.FindStringSubmatch(html)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// ============================================
// トークン挿入
// ============================================
func InjectToken(page *rod.Page, token string, captchaType CaptchaType) error {
var js string
switch captchaType {
case RecaptchaV2:
js = fmt.Sprintf(`
(function() {
var responseField = document.getElementById('g-recaptcha-response');
if (responseField) {
responseField.style.display = 'block';
responseField.value = '%s';
}
var textareas = document.querySelectorAll('textarea[name="g-recaptcha-response"]');
for (var i = 0; i < textareas.length; i++) {
textareas[i].value = '%s';
}
if (typeof ___grecaptcha_cfg !== 'undefined') {
var clients = ___grecaptcha_cfg.clients;
for (var key in clients) {
var client = clients[key];
if (client) {
try {
var callback = client.callback ||
(client.Q && client.Q.callback) ||
(client.S && client.S.callback);
if (typeof callback === 'function') {
callback('%s');
}
} catch(e) {}
}
}
}
return true;
})();
`, token, token, token)
case Turnstile:
js = fmt.Sprintf(`
(function() {
var responseField = document.querySelector('[name="cf-turnstile-response"]');
if (responseField) {
responseField.value = '%s';
}
if (!responseField) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'cf-turnstile-response';
input.value = '%s';
var form = document.querySelector('form');
if (form) form.appendChild(input);
}
if (window.turnstile && window.turnstileCallback) {
window.turnstileCallback('%s');
}
return true;
})();
`, token, token, token)
default:
return fmt.Errorf("サポートされていないCAPTCHAタイプ: %s", captchaType)
}
_, err := page.Eval(js)
return err
}
// ============================================
// クローラー
// ============================================
type CrawlResult struct {
URL string
Title string
Success bool
CaptchaFound bool
CaptchaType CaptchaType
CaptchaSolved bool
Error string
}
func Crawl(browser *rod.Browser, client *CapsolverClient, targetURL string) *CrawlResult {
result := &CrawlResult{
URL: targetURL,
成功: false,
}
// ターゲットに移動
log.Printf("ナビゲート中: %s", targetURL)
page := browser.MustPage(targetURL)
defer page.MustClose()
page.MustWaitLoad()
time.Sleep(2 * time.Second)
// ページHTMLを取得
html := page.MustHTML()
// CAPTCHAを検出
captchaInfo := DetectCaptcha(html)
if captchaInfo != nil && captchaInfo.Type != Unknown {
result.CaptchaFound = true
result.CaptchaType = captchaInfo.Type
log.Printf("CAPTCHAを検出: %s (siteKey: %s)", captchaInfo.Type, captchaInfo.SiteKey)
// CAPTCHAを解決
log.Println("CapsolverでCAPTCHAを解決中...")
token, err := client.Solve(captchaInfo, targetURL)
if err != nil {
result.Error = fmt.Sprintf("CAPTCHAの解決に失敗: %v", err)
log.Printf("エラー: %s", result.Error)
return result
}
log.Printf("トークンを受信: %s...", token[:min(50, len(token))])
// トークンを注入
log.Println("トークンを注入中...")
err = InjectToken(page, token, captchaInfo.Type)
if err != nil {
result.Error = fmt.Sprintf("トークンの注入に失敗: %v", err)
log.Printf("エラー: %s", result.Error)
return result
}
result.CaptchaSolved = true
log.Println("トークンを正常に注入しました!")
// フォームを送信してみる
submitForm(page)
time.Sleep(3 * time.Second)
} else {
log.Println("ページにCAPTCHAは検出されませんでした")
}
// 最終的なページ情報を取得
result.Title = page.MustEval(`document.title`).String()
result.Success = true
return result
}
func submitForm(page *rod.Page) {
selectors := []string{
"button[type='submit']",
"input[type='submit']",
"#recaptcha-demo-submit",
".submit-button",
}
for _, selector := range selectors {
js := fmt.Sprintf(`
(function() {
var btn = document.querySelector('%s');
if (btn && btn.offsetParent !== null) {
btn.click();
return true;
}
return false;
})();
`, selector)
result := page.MustEval(js)
if result.Bool() {
log.Printf("送信ボタンをクリック: %s", selector)
return
}
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// ============================================
// メイン
// ============================================
func main() {
// フラグを解析
targetURL := flag.String("url", "https://www.google.com/recaptcha/api2/demo", "クロールするターゲットURL")
headless := flag.Bool("headless", true, "ブラウザをヘッドレスモードで実行")
checkBalance := flag.Bool("balance", false, "アカウント残高のみを確認")
flag.Parse()
// APIキーを確認
if CAPSOLVER_API_KEY == "" {
log.Fatal("CAPSOLVER_API_KEY環境変数が必要です")
}
log.Println("==============================================")
log.Println("Katana + Capsolver - ウニバーサルCAPTCHAクローラー")
log.Println("==============================================")
// クライアントを初期化
client := NewCapsolverClient(CAPSOLVER_API_KEY)
// 残高を確認
balance, err := client.GetBalance()
if err != nil {
log.Printf("警告: 残高を確認できませんでした: %v", err)
} else {
log.Printf("Capsolver残高: $%.2f", balance)
}
if *checkBalance {
return
}
// ブラウザを起動
log.Println("ブラウザを起動中...")
path, _ := launcher.LookPath()
u := launcher.New().Bin(path).Headless(*headless).MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
defer browser.MustClose()
// クロール
result := Crawl(browser, client, *targetURL)
// 結果を出力
log.Println("==============================================")
log.Println("クロール結果")
log.Println("==============================================")
log.Printf("URL: %s", result.URL)
log.Printf("タイトル: %s", result.Title)
log.Printf("成功: %v", result.Success)
log.Printf("CAPTCHA検出: %v", result.CaptchaFound)
if result.CaptchaFound {
log.Printf("CAPTCHAタイプ: %s", result.CaptchaType)
log.Printf("CAPTCHA解決: %v", result.CaptchaSolved)
}
if result.Error != "" {
log.Printf("エラー: %s", result.Error)
}
log.Println("==============================================")
}
使用方法
bash
# プロジェクトを作成
mkdir katana-universal-crawler
cd katana-universal-crawler
go mod init katana-universal-crawler
# 依存関係をインストール
go get github.com/go-rod/rod@latest
# APIキーを設定
export CAPSOLVER_API_KEY="YOUR_API_KEY"
# デフォルトで実行 (reCAPTCHA v2デモ)
go run main.go
# カスタムURLで実行
go run main.go -url "https://example.com"
# 残高のみ確認
go run main.go -balance
# ヘッドレスモードなしで実行
go run main.go -headless=false
最適な実践方法
1. パフォーマンス最適化
- ProxyLessタスクタイプを使用する:
ReCaptchaV2TaskProxyLessはCapsolverの内部プロキシを使用して高速に解決します - 並列処理: 他のページ要素が読み込まれている間にCAPTCHAの解決を開始します
- トークンキャッシュ: reCAPTCHAトークンは約2分間有効です。可能であればキャッシュを活用してください
2. コスト管理
- 解決前に検出: CAPTCHAが実際に存在する場合にのみCapsolverを呼び出してください
- サイトキーの検証: API呼び出し前に抽出されたキーが有効か確認してください
- 使用状況の監視: コストを効果的に管理するためにAPI呼び出しを追跡してください
3. エラー処理
go
func SolveWithRetry(client *CapsolverClient, info *CaptchaInfo, url string, maxRetries int) (string, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
token, err := client.Solve(info, url)
if err == nil {
return token, nil
}
lastErr = err
log.Printf("試行 %d に失敗: %v", i+1, err)
// 指数バックオフ
time.Sleep(time.Duration(i+1) * time.Second)
}
return "", fmt.Errorf("試行回数上限に達しました: %w", lastErr)
}
4. レートリミット
リクエストの間に適切な遅延を実装して検出を回避してください:
go
type RateLimiter struct {
requests int
interval time.Duration
lastRequest time.Time
mu sync.Mutex
}
func (r *RateLimiter) Wait() {
r.mu.Lock()
defer r.mu.Unlock()
elapsed := time.Since(r.lastRequest)
if elapsed < r.interval {
time.Sleep(r.interval - elapsed)
}
r.lastRequest = time.Now()
}
問題解決
一般的なエラー
| エラー | 原因 | 解決策 |
|---|---|---|
ERROR_ZERO_BALANCE |
クレジット不足 | Capsolverアカウントに残高を追加してください |
ERROR_CAPTCHA_UNSOLVABLE |
無効なサイトキー | 抽出ロジックを確認してください |
ERROR_INVALID_TASK_DATA |
必要パラメータが不足 | タスク構造体を確認してください |
context deadline exceeded |
タイムアウト | タイムアウトを増やしてみてください、またはネットワークを確認してください |
デバッグのヒント
- ヘッドレスモードを無効化:
Headless(false)を設定して何が起きているか確認してください - ネットワークトラフィックをログ: リクエストを監視して問題を特定してください
- スクリーンショットを保存: デバッグ用にページ状態をキャプチャしてください
- トークンを検証: 注入前にトークンの形式をログに出力してください
よくある質問
Q: CAPTCHAページでヘッドレスモードなしでKatanaを使用できますか?
A: いいえ、CAPTCHAページではJavaScriptのレンダリングが必要であり、ヘッドレスモードでのみ動作します。
Q: CAPTCHAトークンはどれくらい有効ですか?
A: reCAPTCHAトークン: 約2分。Turnstile: 設定により異なります。
Q: 平均的な解決時間はどのくらいですか?
A: reCAPTCHA v2: 5-15秒、Turnstile: 1-10秒。
Q: 自前のプロキシを使用できますか?
A: はい、"ProxyLess"の接尾辞を持たないタスクタイプを使用し、プロキシ設定を提供してください。
結論
CapsolverをKatanaと統合することで、ウェブクローリングのための信頼性の高いCAPTCHA処理が可能になります。上記の完全なスクリプトは直接コピーしてGoプロジェクトで使用できます。
準備はできましたか? Capsolverに登録 そしてクローラーを強化してください!
💡 Katana統合ユーザー向け特典:
この統合を記念して、このチュートリアルを通じて登録したCapsolverユーザーに6%のボーナスコードを提供しています — Katana。ダッシュボードで再充電時にコードを入力すると、即座に追加の6%クレジットが得られます。
12. ドキュメンテーション
- 12.1. Katana GitHubリポジトリ
- 12.2. Katanaドキュメンテーション
- 12.3. Capsolverドキュメンテーション
- 12.4. Go Rodブラウザ自動化
コンプライアンス免責事項: このブログで提供される情報は、情報提供のみを目的としています。CapSolverは、すべての適用される法律および規制の遵守に努めています。CapSolverネットワークの不法、詐欺、または悪用の目的での使用は厳格に禁止され、調査されます。私たちのキャプチャ解決ソリューションは、公共データのクローリング中にキャプチャの問題を解決する際に100%のコンプライアンスを確保しながら、ユーザーエクスペリエンスを向上させます。私たちは、サービスの責任ある使用を奨励します。詳細については、サービス利用規約およびプライバシーポリシーをご覧ください。
もっと見る

Pythonでウェブサイトをクロールする際の403エラーの解決方法
Pythonでウェブサイトをクロールする際の403 Forbiddenエラーを乗り越える方法を学びましょう。このガイドでは、IPローテーション、User-Agentのスプーフィング、リクエストのスローティング、認証処理、アクセス制限を回避し、ウェブスキャッピングを成功裏に継続するためのヘッドレスブラウザの使用についてカバーしています。

Sora Fujimoto
13-Jan-2026

アグノでキャプソルバー統合を使用してCaptchaを解く方法
自律型AIエージェントでreCAPTCHA v2/v3、Cloudflare Turnstile、WAFのチャレンジを解決する方法を学びましょう。ウェブスクラビングと自動化のための実際のPythonコード例を含みます。

Sora Fujimoto
13-Jan-2026

カタナとキャップソルバーの統合: ウェブクローリング用の自動CAPTCHA解決
KatanaとCapsolverを統合して、ヘッドレスクローリングでreCAPTCHA v2とCloudflare Turnstileを自動で解決する方法を学ぶ。

Sora Fujimoto
12-Jan-2026

トップ Python ウェブスクラピング ライブラリ 2026年
2026年の最高のPythonウェブスクレイピングライブラリを探索し、特徴、使いやすさ、パフォーマンスを比較して、データ抽出のニーズに応じた情報を提供します。エキスパートの洞察とよくある質問も含まれます。

Adélia Cruz
12-Jan-2026

CrawlabとCapSolverの統合: 分散クローリングのための自動CAPTCHA解決
CapSolverをCrawlabに統合して、大規模にreCAPTCHAとCloudflare Turnstileを解決する方法を学びます

Sora Fujimoto
09-Jan-2026

2026年に知っておくべき最適なAIスクラッピングツール
2026年の最高のAIスクラピングツールのオプションをチェックしてください。私たちが比較する最高のAIウェブスクラピングツールには、Bright Data、Crawl4AI、Browse AIが含まれており、具体的な価格を提供して、自動データ抽出とセキュリティチャレンジの解決をマスターするお手伝いをします。

Lucas Mitchell
07-Jan-2026


