HTTP/2フィンガープリンティングは、2026年において最も重要かつ過小評価されているボット検出手法の一つである。多くのスクレイピングエンジニアはTLSフィンガープリント(JA3/JA4)にばかり注目し、HTTP/2レイヤーの漏洩を見落としている。本記事では、h2 settings frameからja4h fingerprintまで、プロトコルレベルの検出がどのように機能し、どのように一貫したブラウザフィンガープリントを提示すべきかを深掘りする。
HTTP/2フィンガープリンティングとは何か
HTTP/2フィンガープリンティングとは、クライアントがHTTP/2接続を確立する際に送信するプロトコルレベルのメタデータを収集し、その組み合わせからクライアントの正体を特定する技術である。TLSハンドシェイク(JA3/JA4)が「接続の暗号化方法」を指紋として使うのに対し、HTTP/2フィンガープリンティングは「接続確立後のフレーム構成」を指紋として使う。両者は補完関係にあり、どちらか一方だけでは不十分である。
具体的には、以下のシグナルが指紋として利用される:
- SETTINGSフレーム —
HEADER_TABLE_SIZE、INITIAL_WINDOW_SIZE、MAX_CONCURRENT_STREAMS、MAX_FRAME_SIZE、MAX_HEADER_LIST_SIZEの値と送信順序 - WINDOW_UPDATEフレーム — フロー制御ウィンドウの増分値
- ストリーム優先度 — PRIORITYフレーム(HTTP/2の廃止予定だが依然送信される場合がある)の依存関係ツリーと重み
- 疑似ヘッダー順序 —
:method、:authority、:scheme、:path(m,a,s,p)の並び順
これらのシグナルは、RFC 9113(HTTP/2)で標準化されているが、各ブラウザの実装が微妙に異なるため、クライアント識別に利用できる。例えば、Chrome 148はHEADER_TABLE_SIZEに65536を送信するが、Firefox 137は4096を送信する。この違いは決定的である。
検出対象の技術的詳細:SETTINGSフレームとAkamai h2フィンガープリント
SETTINGSフレームの構成
HTTP/2接続の開始時、クライアントはサーバーに対してSETTINGSフレームを送信する。このフレームに含まれるパラメータとその値が、クライアントを一意に特定する材料となる。主要なブラウザのデフォルト値を比較しよう。
| パラメータ | Chrome 148 | Firefox 137 | Safari 18 | httpx (Python) |
|---|---|---|---|---|
| HEADER_TABLE_SIZE | 65536 | 4096 | 4096 | 4096 |
| ENABLE_PUSH | 0 | 0 | 1 | 0 |
| INITIAL_WINDOW_SIZE | 6291456 | 131072 | 2097152 | 65535 |
| MAX_CONCURRENT_STREAMS | 1000 | 1000 | 100 | 未送信 |
| MAX_FRAME_SIZE | 16384 | 16384 | 16384 | 16384 |
| MAX_HEADER_LIST_SIZE | 262144 | 262144 | 未送信 | 未送信 |
この表から分かるように、Pythonのhttpxライブラリ(内部でh2ライブラリを使用)は、ChromeともFirefoxとも一致しない独自のプロファイルを送信する。HEADER_TABLE_SIZEが4096である時点で、これはブラウザではない。
Akamaiのコンパクトh2フィンガープリント文字列
Akamaiのボット検出エンジンは、HTTP/2の各シグナルをパイプ区切りのコンパクトな文字列にエンコードする。この形式は業界標準として広く認識されており、以下の構造を持つ:
2:1:0:1:1:1:1:0:1:1:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0|0:0:0:0:1:0:0:0:0:0:0:0:0:0:0:0:0|0:0:0:0:0:0:0:0:0:m,a,s,p
第一セグメントはSETTINGSパラメータの有無と値のフラグ、第二セグメントはWINDOW_UPDATEの増分値フラグ、第三セグメントはストリーム優先度と疑似ヘッダー順序(m,a,s,p)を表す。Chromeの典型的なフィンガープリントはm,a,s,pの順序を送信するが、一部のHTTP/2クライアントはm,p,a,sや:method,:path,:scheme,:authorityなど異なる順序を送信し、即座に検出される。
JA4Hフィンガープリント
JA4Hは、FoxIOが開発したHTTPレベルのフィンガープリント手法で、TLSのJA4と対になる存在である。JA4HはHTTPリクエストのメタデータ(HTTPバージョン、Cookieの有無、Refererの有無、Accept-Languageの順序、疑似ヘッダー順序など)をハッシュ化し、10文字のフィンガープリント文字列を生成する。JA4HはHTTP/2のSETTINGSフレーム自体は含まないが、疑似ヘッダー順序とHTTPバージョンを含むため、HTTP/2フィンガープリンティングと組み合わせて使用される。
なぜhttpxのデフォルトが即座に検出されるのか
ここで最も重要な実践的ポイントを説明する。あるスクレイピングエンジニアが、最新のChrome 148のUser-Agentを設定し、JA3/JA4フィンガープリントをChromeに偽装したとする。しかし、HTTP/2レイヤーでHEADER_TABLE_SIZEとして4096を送信している場合、そのリクエストはHTMLが読み込まれる前に最大ボットスコアを割り当てられる。
理由は単純である。Chrome 148はHEADER_TABLE_SIZE=65536を送信する。これが4096であれば、クライアントはChromeではない。TLSフィンガープリントでChromeを主張しながら、HTTP/2でChromeと異なる値を送信することは、フィンガープリントの不整合(mismatch)であり、これは自動化ツールの決定的な証拠となる。Akamai、Cloudflare、DataDome、PerimeterXなどのWAF/ボット検出ベンダーはすべて、この不整合を検出する。
重要:TLSフィンガープリントとHTTP/2フィンガープリントは「合意」していなければならない。JA4がChrome 148を主張するなら、HTTP/2のSETTINGSフレームもChrome 148の値でなければならない。一方だけ偽装しても意味がない。
TLS(JA3/JA4)とHTTP/2の整合性
どのクライアントが不整合を漏洩させるか
主要なPython/Node.js HTTPクライアントのHTTP/2フィンガープリント状況を整理する:
| クライアント | 言語 | TLS偽装 | HTTP/2 SETTINGS偽装 | 不整合リスク |
|---|---|---|---|---|
| httpx | Python | 不可(OpenSSLデフォルト) | 不可(h2デフォルト) | 極高 |
| requests | Python | 不可 | HTTP/2非対応 | 高(HTTP/1.1のみ) |
| curl_cffi | Python | 可能(impersonate) | 可能(impersonate) | 低(適切に設定すれば) |
| undici | Node.js | 不可(Node TLS) | 不可 | 高 |
| got | Node.js | 不可 | 部分対応 | 高 |
| Puppeteer/Playwright | Node.js | 実ブラウザ | 実ブラウザ | 極低(本物のブラウザ) |
curl_cffiは、libcurlの--tls-maxとBoringSSLベースのimpersonate機能をPythonから利用できるようにしたライブラリで、TLSフィンガープリントとHTTP/2 SETTINGSフレームの両方をChrome/Firefox/Safariに偽装できる。現時点で、Pythonでプロトコルレベルの偽装を行う最も実用的な選択肢である。
JA3/JA4の具体的シグナル
TLSフィンガープリント(JA3/JA4)は、ClientHelloメッセージに含まれる以下の要素から構成される:
- 暗号スイートのリストと順序 — Chrome 148はTLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_CCM_SHA256, TLS_AES_128_CCM_8_SHA256の順序で送信
- 拡張機能のリストと順序 — Chromeは約17個の拡張機能を特定の順序で送信。例えば、
supported_versions、key_share、signature_algorithmsの順序 - 楕円曲線のリスト —
x25519、secp256r1、secp384r1の順序
Pythonの標準sslモジュール(OpenSSLベース)は、これらの順序がChromeと異なる。OpenSSLはアルファベット順やID順でソートする傾向があるが、Chromeは独自の順序を使用する。この違いがJA3/JA4ハッシュの不一致を引き起こす。詳細についてはMDNのTLS文書を参照されたい。
なぜ住宅プロキシが依然として必要なのか
プロトコルレベルの偽装が完璧であっても、IPレピュテーションスコアリングは依然として機能する。CloudflareやAkamaiは、IPのASN、過去の挙動、データセンタープロキシの既知リストなどを組み合わせてIPスコアを計算する。プロトコルフィンガープリントが完璧にChromeに偽装されていても、IPがAWS(ASN 14618)やDigitalOcean(ASN 14061)であれば、ボットスコアは高くなる。
具体的には、以下のIP属性がスコアリングに影響する:
- ASNタイプ — ISP/住宅(低リスク)vs データセンター(高リスク)
- IPの年齢 — 新規割り当てIPは高リスク
- 過去のボット活動 — 同一IPからの過去のボット的挙動
- 地理的一貫性 — IPの地理的位置とAccept-Languageヘッダーの整合性
したがって、プロトコル偽装 + 住宅プロキシの組み合わせが不可欠である。プロトコル偽装は「ブラウザらしさ」を提供し、住宅プロキシは「人間らしさ」を提供する。両方が揃って初めて、検出システムを通過できる。ProxyHatのロケーション一覧で利用可能な住宅IPの地理的カバレッジを確認できる。
実践:curl_cffi + ProxyHatでChrome h2フィンガープリントを送信する
以下に、curl_cffiを使用してChrome 120のHTTP/2フィンガープリントを送信し、ProxyHatの住宅プロキシ経由でリクエストをルーティングする実践的なPythonコードを示す。これは正当なセキュリティ研究や認可済みの監視目的でのみ使用すべきである。
ステップ1:curl_cffiのインストール
pip install curl_cffi
ステップ2:ProxyHat住宅プロキシ経由でChrome偽装リクエストを送信
from curl_cffi import requests
# ProxyHat住宅プロキシ(米国IPを指定)
proxy_url = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
# Chrome 120のTLS + HTTP/2フィンガープリントを偽装
response = requests.get(
"https://tls.peet.ws/api/all",
impersonate="chrome120",
proxies={"http": proxy_url, "https": proxy_url},
timeout=30
)
print(f"Status: {response.status_code}")
print(f"JA4: {response.json().get('tls', {}).get('ja4', 'N/A')}")
print(f"HTTP/2: {response.json().get('http2', {})}")
このコードは、impersonate="chrome120"により、TLS ClientHelloの暗号スイート順序、拡張機能順序、HTTP/2のSETTINGSフレーム(HEADER_TABLE_SIZE=65536、INITIAL_WINDOW_SIZE=6291456など)、疑似ヘッダー順序(m,a,s,p)をすべてChrome 120と一致させる。ProxyHatの住宅プロキシを経由することで、IPレピュテーションも住宅ISPのスコアを獲得する。
ステップ3:フィンガープリントの検証
送信したフィンガープリントを検証するには、tls.peet.wsやbrowserleaks.comなどのサービスを使用する。これにより、TLSフィンガープリントとHTTP/2フィンガープリントの両方がChromeと一致しているかを確認できる。
# フィンガープリント検証の詳細表示
import json
data = response.json()
# HTTP/2 SETTINGSフレームの確認
h2_settings = data.get("http2", {}).get("sent_frames", {})
print("HTTP/2 SETTINGS:")
print(json.dumps(h2_settings, indent=2))
# 疑似ヘッダー順序の確認
headers = data.get("http2", {}).get("sent_frames", {}).get("headers", [])
for h in headers:
if h.startswith(":"):
print(f"Pseudo-header: {h}")
ステップ4:SOCKS5を使用する場合
SOCKS5プロトコルが必要な場合は、ポート1080を使用する:
from curl_cffi import requests
# ProxyHat SOCKS5住宅プロキシ
socks5_url = "socks5://user-country-US:PASSWORD@gate.proxyhat.com:1080"
response = requests.get(
"https://tls.peet.ws/api/all",
impersonate="chrome120",
proxies={"http": socks5_url, "https": socks5_url},
timeout=30
)
ステップ5:sticky sessionで継続的アクセス
同一セッションを維持する必要がある場合(ログイン後のページ遷移など)は、session IDを指定する:
from curl_cffi import requests
# 同一IPを維持するsticky session
session_proxy = "http://user-country-US-session-myid123:PASSWORD@gate.proxyhat.com:8080"
session = requests.Session()
# 最初のリクエスト
r1 = session.get(
"https://httpbin.org/ip",
impersonate="chrome120",
proxies={"http": session_proxy, "https": session_proxy}
)
print(f"Request 1 IP: {r1.json()['origin']}")
# 2回目のリクエスト(同一IP)
r2 = session.get(
"https://httpbin.org/ip",
impersonate="chrome120",
proxies={"http": session_proxy, "https": session_proxy}
)
print(f"Request 2 IP: {r2.json()['origin']}")
詳細な設定についてはProxyHat公式ドキュメントを参照されたい。価格ページで利用可能なプランを確認できる。
実ブラウザルーティングの代替アプローチ
プロトコル偽装が最も厳格なサイトでは不十分な場合、PlaywrightやPuppeteerを通じて実際のChromiumブラウザを使用し、ProxyHat住宅プロキシを経由させるアプローチがある。実ブラウザはTLS、HTTP/2、JavaScript、Canvas、WebGLのすべてのフィンガープリントを本物として送信するため、検出リスクは最低限になる。
from playwright.sync_api import sync_playwright
proxy_config = {
"server": "http://gate.proxyhat.com:8080",
"username": "user-country-US",
"password": "PASSWORD"
}
with sync_playwright() as p:
browser = p.chromium.launch(
proxy=proxy_config,
headless=True
)
page = browser.new_page()
page.goto("https://tls.peet.ws/api/all")
content = page.content()
print(content)
browser.close()
このアプローチはリソース消費が大きい(1インスタンスあたり約150-300MBのメモリ)が、最も堅牢である。用途に応じて使い分けたい。詳細なユースケースについてはスクレイピングのユースケースとSERPトラッキングのユースケースを参照されたい。
よくある間違いとエッジケース
1. User-Agentとフィンガープリントの不整合
最も一般的な間違いは、User-AgentでChrome 148を主張しながら、impersonate="chrome116"を使用することである。Chromeのバージョンが上がるたびに、TLS拡張機能やHTTP/2 SETTINGSが微妙に変化する。User-Agentのバージョンとimpersonateのバージョンを一致させる必要がある。
2. HTTP/1.1へのダウングレード
一部のプロキシチェーンはHTTP/2をサポートせず、HTTP/1.1にダウングレードする。この場合、HTTP/2フィンガープリントは送信されないが、HTTP/1.1のヘッダー順序が新たなフィンガープリントとなる。ProxyHatのプロキシはHTTP/2をエンドツーエンドでサポートするため、この問題は発生しない。
3. WINDOW_UPDATEの欠落
Chromeは接続確立後、即座にWINDOW_UPDATEフレームを送信し、接続レベルのフロー制御ウィンドウを6291456に増やす。これを送信しないクライアントは、即座に非ブラウザとして検出される。curl_cffiのimpersonateモードはこれを正しく送信するが、カスタム実装では忘れがちである。
4. Accept-LanguageとIP地理の不整合
IPがドイツ(DE)の住宅プロキシであるのに、Accept-Language: en-USのみを送信すると、不整合として検出される。Geo-targetingとヘッダーの一貫性を保つ必要がある:
# ドイツIP + ドイツ語Accept-Language
proxy_url = "http://user-country-DE-city-berlin:PASSWORD@gate.proxyhat.com:8080"
response = requests.get(
"https://example.com",
impersonate="chrome120",
proxies={"http": proxy_url, "https": proxy_url},
headers={
"Accept-Language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"
}
)
適切な利用と法的注意事項
本記事の技術は、正当なセキュリティ研究、認可済みのペネトレーションテスト、合法的な自動化(API利用規約に準拠したデータ収集)、および自社 assets の監視目的でのみ使用すべきである。
米国では、Computer Fraud and Abuse Act(CFAA)がコンピュータシステムへの不正アクセスを禁止しており、利用規約に違反するスクレイピングがCFAA違反とされる判例がある。EUでは、GDPRが個人データの収集・処理に適用され、スクレイピングで個人データを収集する場合は合法的根拠が必要である。GDPR公式サイトで条文を確認できる。
以下の行為には絶対に使用しないこと:
- 不正取引(チケット・スニーカーのボット購入で利用規約違反)
- クレジットカード詐欺や個人情報盗難
- DDoS攻撃やサービス妨害
- 著作権保護されたコンテンツの不正収集
主要なポイント
Key Takeaways
- HTTP/2フィンガープリンティングはTLSフィンガープリンティングと同等に重要であり、SETTINGSフレーム、WINDOW_UPDATE、疑似ヘッダー順序が検出対象となる
httpxのデフォルトHEADER_TABLE_SIZE=4096はChrome(65536)と一致せず、即座に検出される- TLS(JA3/JA4)とHTTP/2フィンガープリントは合意していなければならない。一方だけ偽装しても意味がない
curl_cffiのimpersonate機能は、TLSとHTTP/2の両方を一貫して偽装できる最も実用的なPythonソリューションである- プロトコル偽装だけでは不十分であり、住宅プロキシによるIPレピュテーション管理が不可欠である
- 本技術は正当なセキュリティ研究と認可済みの自動化のみに使用すべきである
プロトコルレベルの検出を回避するには、多層的なアプローチが必要である。TLSフィンガープリント、HTTP/2 SETTINGS、疑似ヘッダー順序、IPレピュテーションのすべてが一貫していなければならない。curl_cffiとProxyHat住宅プロキシの組み合わせは、この一貫性を実現する最も実用的な方法の一つである。ProxyHatのプランを確認し、プロジェクトの要件に合わせて構成してほしい。






