
Sora Fujimoto
AI Solutions Architect

detect、get_captcha_info、またはsolve_on_pageを決定論的な復元境界で使用します。AIブラウザ復元ハーネスは、ページ状態が予期せずに変化したときにブラウザタスクを制御する実行時レイヤーです。モデルは次のビジネスステップを決定できますが、ハーネスはPlaywrightコンテキスト、承認ホストポリシー、ナビゲーションチェックポイント、ページ分類、サポートされるチャレンジ復元、リトライ制限、トレース、スクリーンショット、終了処理を所有すべきです。CapSolverはこのレイヤー内で決定論的な復元機能として適合します。capsolver-coreはサポートされるチャレンジを検出、パラメータを読み取り、解決し、結果を同じページに戻すことができます。ハーネスは、エージェントが続ける前に期待されるアプリケーション状態が戻ったことを確認します。このガイドでは、信頼性のある認可されたブラウザオートメーションのためのポリシーモデル、状態マシン、非同期コンテキストマネージャー、復元関数、アーティファクトレコーダー、OpenTelemetryスパン、テスト、および運用制御を構築します。
ハーネスはモデルでもブラウザドライバーや単独でもありません。それらの間の制御プレーンです。
エージェントからのビジネス目標
↓
ブラウザ復元ハーネス
├─ ターゲットポリシー
├─ Playwrightコンテキスト
├─ 状態分類器
├─ チェックポイントストア
├─ CapSolver復元
├─ リトライ予算
├─ トレース + アーティファクト
└─ クリーンアップ
↓
承認されたページアクションまたはオペレーターのレビュー
Playwrightのfixtureドキュメントは、隔離されたページおよびブラウザコンテキストfixture、再利用可能なセットアップと終了、コンポーザビリティ、自動デバッグ添付を強調しています。これらの特性は直接的に運用ハーネスに変換されます。
CapSolver Core SDKドキュメントでは、4つの有用なブラウザステージが定義されています: detect、get_captcha_info、solve、solve_on_page。
モデルは既知の製品ページを開くことや、公開ステータスを読み取ることを決定できます。ハーネスは、要求されたホストが許可されているか、現在のページが期待されているか、復元がサポートされているか、リトライ予算が残っているかを決定します。
| 決定 | 所有者 | 理由 |
|---|---|---|
| 次のビジネスステップ | エージェントまたはワークフロー | タスクコンテキストが必要 |
| ホストとパスの権限 | ハーネスポリシー | 決定論的でなければならない |
| ページ状態分類 | ハーネス分類器 | 信頼できるDOM/ネットワーク証拠を使用する必要がある |
| チャレンジ復元コール | ハーネス | シークレットとブラウザオブジェクトが必要 |
| トークン/クッキーの処理 | ハーネス | センシティブなランタイムデータ |
| 継続またはレビュー | ハーネス状態マシン | 制限付き復元を強制する |
| 最終提出 | 人間または専用サービス | 高い影響を持つアクション |
CapSolver AIエージェントガイドでは、同じ作業分担が説明されています: モデルは推論を担当し、CapSolverのレイヤーはサポートされるチャレンジ作業を行います。
承認されたホスト、パス、アクション、予算の狭いポリシーから始めます。
from dataclasses import dataclass, field
from urllib.parse import urlparse
@dataclass(frozen=True)
class TargetPolicy:
allowed_hosts: set[str]
allowed_path_prefixes: tuple[str, ...]
max_navigations: int = 20
max_recovery_attempts: int = 1
capture_screenshots: bool = True
capture_html: bool = False
allow_form_submission: bool = False
def validate_url(self, url: str) -> None:
parsed = urlparse(url)
if parsed.scheme != "https":
raise PermissionError("HTTPSターゲットのみ許可されます")
if parsed.hostname not in self.allowed_hosts:
raise PermissionError("ホストは承認されたポリシー外です")
if not parsed.path.startswith(self.allowed_path_prefixes):
raise PermissionError("パスは承認されたポリシー外です")
テナント固有のポリシーを使用してください。無関係な顧客やプロジェクト用に1つのグローバル許可リストを維持しないでください。
CapSolver AIと自動化のFAQは統合の文脈を提供し、CapSolverウェブスクレイピングのFAQは責任ある公開データワークフローをカバーしています。
復元ハーネスは、無限ループの「もう一度試す」ではなく明示的な状態を使用すべきです。
from enum import Enum
class BrowserState(str, Enum):
EXPECTED_PAGE = "expected_page"
SUPPORTED_CHALLENGE = "supported_challenge"
UNKNOWN_PAGE = "unknown_page"
RECOVERING = "recovering"
RECOVERED = "recovered"
REVIEW_REQUIRED = "review_required"
FAILED = "failed"
許可された遷移はデータとして表されます:
ALLOWED_TRANSITIONS = {
BrowserState.EXPECTED_PAGE: {
BrowserState.EXPECTED_PAGE,
BrowserState.SUPPORTED_CHALLENGE,
BrowserState.UNKNOWN_PAGE,
},
BrowserState.SUPPORTED_CHALLENGE: {
BrowserState.RECOVERING,
BrowserState.REVIEW_REQUIRED,
},
BrowserState.RECOVERING: {
BrowserState.RECOVERED,
BrowserState.REVIEW_REQUIRED,
BrowserState.FAILED,
},
BrowserState.RECOVERED: {
BrowserState.EXPECTED_PAGE,
BrowserState.REVIEW_REQUIRED,
},
}
すべての遷移を検証します。これによりループが可視化され、テスト可能になります。
チェックポイントは、ワークフローが正しく再開されたかどうかを判断するために必要な安全なメタデータを記録します。
from dataclasses import dataclass
from datetime import datetime, timezone
@dataclass
class BrowserCheckpoint:
url: str
title: str
expected_selector: str | None
navigation_index: int
recovery_attempts: int
observed_at: str
async def checkpoint(page, expected_selector, nav_index, attempts):
return BrowserCheckpoint(
url=page.url,
title=await page.title(),
expected_selector=expected_selector,
navigation_index=nav_index,
recovery_attempts=attempts,
observed_at=datetime.now(timezone.utc).isoformat(),
)
チェックポイントにストレージ状態、クッキー、パスワード、トークン、またはフォームの完全な値を保存しないでください。
信頼できるDOM証拠、タイトル、URL、および期待されるセレクターを使用してください。モデルにスクリーンショットだけからページ状態を推論させないでください。
async def classify_page(page, expected_selector: str) -> BrowserState:
if await page.locator(expected_selector).count():
return BrowserState.EXPECTED_PAGE
title = (await page.title()).strip().lower()
html = (await page.content()).lower()
challenge_markers = (
"just a moment...",
"challenge-platform",
"cf-chl-",
)
if any(marker in title or marker in html for marker in challenge_markers):
return BrowserState.SUPPORTED_CHALLENGE
return BrowserState.UNKNOWN_PAGE
ターゲット固有のマーカーと制御されたfixtureを使用してください。マーカーのセットはアクセス権限ではなくルーティングのヒューリスティクスです。
CapSolver CAPTCHA解決のFAQではサポートされるチャレンジワークフローが説明されており、CapSolverエラーのFAQはエラーの分類に役立ちます。
公式Core SDKは、HTTP接続が正しく再利用および解放されるように非同期コンテキストマネージャーを使用することを推奨しています。
import os
from capsolver_core import create_capsolver
def create_recovery_client():
return create_capsolver(
api_key=os.environ["CAPSOLVER_API_KEY"],
default_timeout=120,
polling_interval=5,
request_timeout_ms=30000,
source="ai-browser-recovery-harness",
version="1.0.0",
)
各DOMチェックごとに新しいクライアントを作成しないでください。ハーネスのライフサイクル中に1つのクライアントを保持し、終了時に閉じます。
detectとget_captcha_infoを診断に使用し、solve_on_pageで一貫したブラウザフローを実行します。
from capsolver_core import SolveOnPageOptions
async def recover_supported_challenge(
cap,
page,
policy: TargetPolicy,
recovery_attempts: int,
) -> dict:
policy.validate_url(page.url)
if recovery_attempts >= policy.max_recovery_attempts:
return {
"success": False,
"state": BrowserState.REVIEW_REQUIRED,
"reason": "recovery budget exhausted",
}
detected = await cap.detect(page)
if not detected:
return {
"success": False,
"state": BrowserState.REVIEW_REQUIRED,
"reason": "no supported challenge detected",
}
infos = await cap.get_captcha_info(page)
results = await cap.solve_on_page(
page,
options=SolveOnPageOptions(
autofill=True,
throw_on_error=False,
timeout=120,
polling_interval=5,
),
)
errors = [item.error for item in results if item.error]
filled = bool(results) and all(item.filled for item in results)
return {
"success": filled and not errors,
"state": (
BrowserState.RECOVERED
if filled and not errors
else BrowserState.REVIEW_REQUIRED
),
"detected_count": len(detected),
"info_count": len(infos),
"result_count": len(results),
"errors": errors,
}
元のpageオブジェクトを保持してください。solve_on_pageの目的は、既存のブラウザセッション内で検出、解決、埋め込みを行うことです。
成功したツールの応答は、期待されるアプリケーションページが戻ったことを証明しません。
async def verify_recovery(
page,
expected_selector: str,
timeout_ms: int = 15000,
) -> bool:
try:
await page.locator(expected_selector).wait_for(
state="visible",
timeout=timeout_ms,
)
return True
except Exception:
return False
復元後、再度ページを分類してください。チャレンジが残っているか、期待されるセレクターが存在しない場合、レビューを要求して停止してください。
async def recover_and_verify(cap, page, policy, expected_selector, attempts):
result = await recover_supported_challenge(
cap=cap,
page=page,
policy=policy,
recovery_attempts=attempts,
)
if not result["success"]:
return result
if not await verify_recovery(page, expected_selector):
return {
"success": False,
"state": BrowserState.REVIEW_REQUIRED,
"reason": "expected page did not return after recovery",
}
return {
"success": True,
"state": BrowserState.EXPECTED_PAGE,
"reason": "page recovered and verified",
}
クリーンアップを保証する非同期コンテキストマネージャーを使用します。
from contextlib import asynccontextmanager
from playwright.async_api import async_playwright
@dataclass
class BrowserHarness:
policy: TargetPolicy
playwright: object
browser: object
context: object
page: object
capsolver: object
navigation_count: int = 0
recovery_attempts: int = 0
@asynccontextmanager
async def browser_recovery_harness(policy: TargetPolicy):
async with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
async with create_recovery_client() as cap:
harness = BrowserHarness(
policy=policy,
playwright=playwright,
browser=browser,
context=context,
page=page,
capsolver=cap,
)
try:
yield harness
finally:
await context.close()
await browser.close()
モデルまたはワークフローは制御されたメソッドを受け取るだけで、制限のないブラウザアクセスは受けません。
async def safe_navigate(
harness: BrowserHarness,
url: str,
expected_selector: str,
) -> dict:
harness.policy.validate_url(url)
if harness.navigation_count >= harness.policy.max_navigations:
return {
"success": False,
"state": BrowserState.REVIEW_REQUIRED,
"reason": "navigation budget exhausted",
}
harness.navigation_count += 1
await harness.page.goto(url, wait_until="domcontentloaded")
state = await classify_page(harness.page, expected_selector)
if state == BrowserState.EXPECTED_PAGE:
return {"success": True, "state": state}
if state == BrowserState.SUPPORTED_CHALLENGE:
result = await recover_and_verify(
cap=harness.capsolver,
page=harness.page,
policy=harness.policy,
expected_selector=expected_selector,
attempts=harness.recovery_attempts,
)
harness.recovery_attempts += 1
return result
return {
"success": False,
"state": BrowserState.REVIEW_REQUIRED,
"reason": "unknown page state",
}
エージェントはsafe_navigateを要求できますが、ポリシーと復元パスはハーネスが所有します。
OpenTelemetryのGenAIオブザーバビリティガイドラインは、モデルとツール操作のトレースを説明しています。また、完全なプロンプトとツールコンテンツが機密データを含む可能性があることを指摘しています。デフォルトではメタデータのみのスパンを使用してください。
from opentelemetry import trace
tracer = trace.get_tracer("capsolver.browser_harness")
async def traced_safe_navigate(harness, url, expected_selector):
with tracer.start_as_current_span("browser.safe_navigate") as span:
span.set_attribute("browser.target_host", url.split("/")[2])
span.set_attribute("browser.navigation_index", harness.navigation_count + 1)
span.set_attribute("browser.recovery_attempts", harness.recovery_attempts)
result = await safe_navigate(harness, url, expected_selector)
span.set_attribute("browser.outcome", str(result.get("state")))
span.set_attribute("browser.success", bool(result.get("success")))
return result
失敗時にのみアーティファクトをキャプチャしてください
スクリーンショットやHTMLには個人情報や機密データが含まれる可能性があります。ポリシーが許可する場合にのみキャプチャし、可能な限り削除してください。
from pathlib import Path
import secrets
async def capture_failure_artifacts(harness, directory: Path) -> dict:
artifact_id = secrets.token_hex(12)
screenshot = directory / f"{artifact_id}.png"
await harness.page.screenshot(
path=str(screenshot),
full_page=False,
)
return {
"artifact_id": artifact_id,
"screenshot_path": str(screenshot),
"url": harness.page.url,
"title": await harness.page.title(),
}
保持制限とアクセス制御を使用してください。トップレベルの状態のみが必要な場合、フルページスクリーンショットをキャプチャしないでください。
CapSolverブラウザ自動化ブログには関連する実装パターンが記載されており、CapSolver Chrome拡張機能ガイドは開発中のサポートされているウィジェットパラメータを検証するのに役立ちます。
隔離されたブラウザコンテキストと制御されたページを使用してください。Playwrightのフィクスチャは再利用可能なセットアップとシャットダウンを提供します。
import pytest
@pytest.mark.asyncio
async def test_unknown_host_is_rejected():
policy = TargetPolicy(
allowed_hosts={"staging.example.com"},
allowed_path_prefixes=("/qa/",),
)
with pytest.raises(PermissionError):
policy.validate_url("https://other.example.net/qa/test")
@pytest.mark.asyncio
async def test_recovery_budget_is_bounded(fake_cap, fake_page):
policy = TargetPolicy(
allowed_hosts={"staging.example.com"},
allowed_path_prefixes=("/qa/",),
max_recovery_attempts=1,
)
result = await recover_supported_challenge(
cap=fake_cap,
page=fake_page,
policy=policy,
recovery_attempts=1,
)
assert result["state"] == BrowserState.REVIEW_REQUIRED
assert result["reason"] == "recovery budget exhausted"
ノーチャレンジ、サポートされているチャレンジ、未知のインタースティシャル、成功したフィルバック、ソルブ失敗、復元後のチャレンジループ、期待されるセレクタが見つからないなどのフィクスチャを作成してください。
| メトリクス | 目的 |
|---|---|
| 期待されたページ率 | 成功した通常のナビゲーションを測定します |
| チャレンジ遭遇率 | 承認されたホストごとのソースの摩擦を示します |
| 復元成功率 | サポートされている復元結果を測定します |
| チャレンジループ率 | 繰り返されるインタースティシャル状態を検出します |
| 未知のページ率 | レイアウト、認証、またはポリシーの変更を検出します |
| P95復元遅延 | ユーザーに見える遅延をトラッキングします |
| オペレータレビュー率 | 解決されていないワークフローの量を測定します |
| アーティファクトキャプチャ率 | 過剰な失敗ログの検出をします |
ターゲットポリシー、ルート、ブラウザバージョン、チャレンジタイプ、ハンドルバージョンごとにメトリクスを分解してください。チャレンジ復元失敗をビジネスタスクの失敗としてラベル付けしないでください。両方の次元を保持してください。
ボーナスコード: CapSolverダッシュボードでコード WEBS を使用して、すべての充電に対して追加の5%のボーナスを取得してください。
| アプローチ | ブラウザ所有権 | 復元制御 | 最適な用途 |
|---|---|---|---|
| 直接エージェントブラウザアクセス | エージェントランタイム | プロンプト依存 | 低リスクプロトタイプのみ |
| フレームワーク固有のアクション | エージェントフレームワーク | ツールラッパー | 速い統合 |
| 専用復元ハンドル | 独立した制御層 | 決定論的な状態機械 | プロダクションの信頼性とガバナンス |
| 人間のみの復元 | オペレータ | 手動 | 対応不可または高リスクワークフロー |
専用ハンドルはより多くのエンジニアリングを必要としますが、複数のエージェントフレームワークにサービスを提供する1つのポリシーと観測性レイヤーを作成します。
CapSolver製品ページにはサポートされている解決カテゴリがリストされており、CapSolver AIブログにはハンドルアクションを呼び出すエージェントフレームワークの例が掲載されています。
ブラウザ復元ハンドルは、所有しているシステム、テストしているシステム、または明示的な承認を取得したシステムでのみ使用してください。成功したチャレンジソリューションは、プライベートコンテンツへのアクセス、認証境界の無視、レートリミットの超過、またはトランザクションの実行を許可しません。ハンドルを制限し、デフォルトで読み取り専用にし、監査可能にしてください。不確実性は、権限を動的に拡張するのではなく、人間にルーティングしてください。
AIブラウザ復元ハンドルは、チャレンジ処理を制御されたランタイム機能に変換します。ブラウザコンテキストを所有し、ターゲットを検証し、ページ状態を分類し、決定論的な境界でCapSolver Coreを呼び出し、期待されたページを検証し、削除されたテレメトリーを記録し、制限された試行後に停止します。エージェントフレームワークは、シークレットや制限のないブラウザコントロールへの直接アクセスなしでハンドルを使用できます。
CapSolverから始め、承認されたステージングアプリケーションに対して状態マシンを実装し、プロダクション前に隔離されたフィクスチャと信頼性ゲートを追加してください。
いいえ。エージェントフレームワークが呼び出す独立したランタイムおよびポリシーレイヤーです。ハンドルはブラウザ状態、復元、チェックポイント、テレメトリー、およびクリーンアップを所有しています。
solve_on_pageを使用するのですか?solve_on_pageは、同じPlaywrightページで検出、パラメータ抽出、解決、DOMフィルバックを組み合わせるため、制御されたブラウザ復元境界に適しています。
safe_navigateやread_public_pageなどの狭いハンドルアクションを優先してください。ロウページアクセスは、ターゲット、ナビゲーション、復元ポリシーを強制するのが難しくなります。
デフォルトで1回のみ許可してください。繰り返されるチャレンジや未知のページ状態は、制御されていないループを作成するのではなく、オペレータレビューにルーティングしてください。
ターゲットホスト、ハンドルバージョン、状態遷移、遅延、正規化されたエラー、アーティファクト参照などのメタデータを保存してください。ソリューショントークン、クッキー、APIキー、プロキシ資格情報、ストレージ状態、またはプライベートページコンテンツは保存しないでください。
CapSolverのスキーマ、fixture、トレース評価ツール、アサーション、回帰データセット、およびCIゲートを用いて、AIエージェントのツールコール用CAPTCHA評価ハーネスを構築してください。

クラウドフレア・ターニスティールをLlamaIndexエージェントで解決する方法を学びましょう。CapSolver、FunctionToolのスキーマ、セキュアなトークン処理、リトライ、ブラウザ復旧を使用して。
