3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare bot対策に引っかかるパターンと回避策|AI実務ノート 編集部

3
Last updated at Posted at 2026-01-03

1. 結論(この記事で得られること)

この記事では、Cloudflareのbot対策(Bot Fight Mode、Turnstile、WAF等)に引っかかってしまう具体的なパターンと、実務で使える回避策を解説します。

得られること:

  • HTTPクライアントが403/429エラーになる原因の特定方法
  • 正規のクライアントなのにbotと誤判定される典型的な実装ミス
  • JavaScriptチャレンジを突破できない原因と対処法
  • スクレイピング・自動テスト・監視ツールでの実装例
  • AI(Claude/GPT)を使った効率的なトラブルシューティング方法

昔、私もAPIクライアントで突然403が返ってきて、ログには何も残ってないし、curlでは通るのに本番だけダメで半日溶かした経験があります。結局User-Agentとか初歩的なところだったんですが、当時は切り分け方を知らなくて…。

この記事では、そういう時間を無駄にしないための実践的な知識をまとめます。

2. 前提(環境・読者層)

想定読者:

  • Cloudflare保護下のサイトにHTTPリクエストを送る必要があるエンジニア
  • スクレイピング、E2Eテスト、監視ツール、APIクライアントを実装している方
  • 403 Forbidden、429 Too Many Requests、Challenge画面で困っている方

前提知識:

  • HTTP通信の基礎(リクエスト/レスポンス、ヘッダー)
  • 基本的なPython/Node.js/Goなどのプログラミング経験
  • Cloudflareが保護しているサイトにアクセスする必要性があること

環境:

  • Cloudflareで保護されたWebサイト(Free〜Enterpriseプラン)
  • クライアント側:Python 3.9+、Node.js 18+、Go 1.20+ など

注意:
この記事は正規の目的でのアクセスを前提としています。不正アクセスや利用規約違反を推奨するものではありません。

3. Before:よくあるつまずきポイント

3-1. 典型的なエラーパターン

パターン1:即座に403が返る

import requests
 
response = requests.get("https://example.com")
# => 403 Forbidden (HTML内に "Attention Required! | Cloudflare" が含まれる)

curlやブラウザでは普通にアクセスできるのに、スクリプトだけ弾かれる。

パターン2:Challenge画面(5秒待機)が返る

<!DOCTYPE html>
<html>
<head><title>Just a moment...</title></head>
<!-- JavaScriptチャレンジが含まれる -->

パターン3:429 Too Many Requests

HTTP/1.1 429 Too Many Requests
CF-RAY: xxxxx-NRT
cf-mitigated: challenge

レート制限に引っかかっている場合と、bot判定されている場合の両方あり。

3-2. なぜ引っかかるのか?

Cloudflareは複数の指標でbot判定しています:

① User-Agentの異常
 ・デフォルトのUA(「python-requests/2.28.0」など)
 ・UAが空、または古すぎる

② TLS Fingerprint
 ・HTTPライブラリごとに異なるTLS handshakeパターン
 ・ブラウザと明らかに違う接続パターン

③ HTTPヘッダーの不自然さ
 ・Accept、Accept-Language、Accept-Encodingの欠如
 ・ヘッダーの順序がブラウザと異なる

④ JavaScriptチャレンジ未対応
 ・JSを実行できないクライアント
 ・Cookieを保存しない実装

⑤ IPレピュテーション
 ・VPN、データセンターIP、過去の不正アクセス履歴

3-3. 私がハマったケース

昔、社内のヘルスチェック監視ツールで、突然Cloudflare導入後に全部アラートが飛ぶようになりました。調べたら:

// 問題のあったコード
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(url)

Go標準の「http.Client」はUser-Agentを 「Go-http-client/1.1」 にするので、即座にbot判定。しかもTLS fingerprintも独特。

4. After:基本的な解決パターン

4-1. User-Agentとヘッダーの正規化

基本方針:
できるだけ実際のブラウザに見せかける。

import requests
 
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'ja,en-US;q=0.9,en;q=0.8',
    'Accept-Encoding': 'gzip, deflate, br',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
}
 
response = requests.get("https://example.com", headers=headers)

注意点:

  • User-Agentは定期的に更新する(古いバージョンは逆に怪しい)
  • 実際にそのブラウザから送られるヘッダーをコピーする

4-2. TLS Fingerprintの模倣

Pythonの「requests」はOpenSSLベースなので、TLS fingerprintがブラウザと異なります。

解決策:curl_cffiを使う

# pip install curl_cffi
from curl_cffi import requests
 
response = requests.get(
    "https://example.com",
    impersonate="chrome120"  # Chrome 120のTLS fingerprintを模倣
)
print(response.status_code)  # => 200

これだけで、TLSレベルの判定を回避できます。私も最近知って「もっと早く知りたかった…」ってなりました。

4-3. JavaScriptチャレンジへの対応

方法1:Seleniumでブラウザ自動化

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
 
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
 
# 5秒待機してチャレンジをクリア
import time
time.sleep(6)
 
html = driver.page_source
driver.quit()

方法2:Playwrightを使う(推奨)

from playwright.sync_api import sync_playwright
 
with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context(
        user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    )
    page = context.new_page()
    page.goto("https://example.com")
 
    # Cloudflare challengeを自動で待機
    page.wait_for_load_state("networkidle")
 
    content = page.content()
    browser.close()

Playwrightの方がSeleniumより検出されにくく、動作も速いです。

4-4. Node.jsでの実装例

// npm install playwright
const { chromium } = require('playwright');
 
(async () => {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  });
 
  const page = await context.newPage();
  await page.goto('https://example.com');
  await page.waitForLoadState('networkidle');
 
  const content = await page.content();
  console.log(content);
 
  await browser.close();
})();

4-5. 判定フローチャート

403エラーが出た
  ↓
User-Agent確認
  ├─ デフォルト → 変更
  └─ 問題なし
      ↓
  curl_cffi等でTLS対策
      ↓
  まだダメ?
      ↓
  JavaScriptチャレンジ
      ↓
  Playwright/Selenium導入
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?