
Sora Fujimoto
AI Solutions Architect

AntiCloudflareTaskを使用してください。信頼性のあるECサイト在庫モニタリングは、単なるページフェッチングの問題ではなく、証拠の問題です。製品ページは「在庫あり」と表示される一方で、特定のサイズが利用不可である場合があります。マーケットプレイスAPIはマーチャントフィードに遅れを取る場合もあり、Cloudflareチャレンジが期待されるページをインタースティシャル応答に置き換えることもあります。正しいワークフローはAPI第一、バリアント対応、セッション一貫性を備えています。利用可能な公式フィードを使用し、構造化された在庫観測を記録し、認証されたブラウザフォールバックが中断されたときにのみCapSolverを呼び出します。このガイドでは、データモデル、チャレンジ復元フロー、静的プロキシとユーザーエージェントの要件、クッキーの受け渡し、在庫変更検出、アラート制御、小売運用、カタログインテリジェンス、承認された在庫モニタリングのコンプライアンス境界について説明します。
在庫モニタリングは、特定の運用質問に答える必要があります。一般的な例は次の通りです:
「この製品をモニタリングする」といった曖昧なターゲットを避けてください。製品識別子、バリアント、地域、配送文脈、ソース、アラート条件を定義してください。
inventory_job = {
"canonical_product_id": "catalog-7821",
"gtin": "0099999999999",
"variant": {
"color": "black",
"size": "M",
},
"market": "US",
"destination_postal_code": "94107",
"sources": [
"merchant_inventory_feed",
"marketplace_api",
"authorized_product_page",
],
"alert_on": ["OUT_OF_STOCK_TO_IN_STOCK"],
}
CapSolver e-commerceブログでは関連するコマースワークフローがカバーされており、CapSolverウェブスクレイピングFAQでは許可されたパブリックデータ収集の運用上の考慮事項が説明されています。
公式ソースは通常、より安定しており、監査がしやすいです。購入者向けページを読む前に、マーチャントフィード、セラーAPI、マーケットプレイス在庫エンドポイント、ライセンス付きカタログプロバイダーを使用してください。
eBay Browse APIドキュメンテーションはキーワード、カテゴリ、ePID、GTIN、状態、その他のフィルターでアイテム検索をサポートしています。構造化された製品ページを公開する店舗の場合、Schema.org Offerはavailability、price、priceCurrency、seller、および有効な数量などのフィールドを定義します。Googleの製品構造化データドキュメンテーションでは、オファーと在庫データが製品マークアップにどのように表示されるかが説明されています。
| ソース | 推奨役割 | 主な強み | 主な制限 |
|---|---|---|---|
| マーチャント在庫フィード | 所有カタログの主なソース | 直接的なSKUと数量データ | 商業関係に限られる |
| マーケットプレイスAPI | 承認されたマーケットプレイスリストの主なソース | 構造化された識別子とフィルター | クォータとマーケットプレイス固有のフィールド |
| ライセンスプロバイダー | 複数市場の正規化 | 一貫したスキーマ | ライセンス料とカバー範囲 |
| 認証されたパブリックページ | 検証とギャップカバー | 買い手向けの状態を反映 | レイアウト変更とトラフィック検証 |
ブラウザ収集は、既存の公式ソースを置き換えるのではなく、既知のデータギャップを検証または補完する必要があります。
一般的なin_stock: trueフィールドでは不十分です。バリアント、チャネル、市場、販売者、証拠を保持してください。
from dataclasses import dataclass, field
from datetime import datetime, timezone
@dataclass
class InventoryObservation:
source: str
canonical_product_id: str
source_item_id: str | None
gtin: str | None
variant: dict[str, str]
market: str
seller_id: str | None
availability: str
quantity: int | None
quantity_confidence: str
delivery_method: str | None
store_id: str | None
source_url: str | None
evidence: dict
parser_version: str
observed_at: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
制御された在庫用語を用いてください:
VALID_AVAILABILITY = {
"IN_STOCK",
"OUT_OF_STOCK",
"PREORDER",
"BACKORDER",
"LIMITED",
"UNKNOWN",
}
ページに「利用可能」とのみ表示されている場合は、数量をNoneとして記録してください。数値を推測しないでください。
CapSolver Pythonウェブデータガイドは実装の文脈を提供し、CapSolver用語集はチームが用語を標準化するのを助けます。
パーサーは在庫データを読み取る前にページの識別を確認する必要があります。チャレンジページはHTTP 200を返すかもしれませんが、期待される製品要素を含まないこともあります。
CHALLENGE_TITLES = {
"just a moment...",
"attention required!",
}
async def classify_page(page) -> str:
title = (await page.title()).strip().lower()
html = (await page.content()).lower()
if title in CHALLENGE_TITLES:
return "CLOUDFLARE_CHALLENGE"
if "cf-chl-" in html or "challenge-platform" in html:
return "CLOUDFLARE_CHALLENGE"
if await page.locator('[data-product-id]').count():
return "PRODUCT_PAGE"
return "UNKNOWN_PAGE"
これらのマーカーをルーティング信号として扱い、万能の証拠とは見なさないでください。ターゲット固有のfixtureを保持し、承認されたページに対してテストしてください。
CapSolver Cloudflare製品ページではサポートされているチャレンジタスクが説明されており、CapSolver Cloudflareブログにはトラブルシューティングの文脈が含まれています。
CapSolverの公式CloudflareチャレンジドキュメンテーションではAntiCloudflareTaskが定義されています。
| フィールド | 必須 | 在庫モニタリングでの使用 |
|---|---|---|
type |
はい | 固定でAntiCloudflareTask |
websiteURL |
はい | 承認された製品またはリストの正確なURL |
proxy |
はい | ブラウザで使用される静的またはスタックプロキシ |
userAgent |
いいえ | ブラウザからの正確なサポートされているChromeユーザーエージェント |
html |
いいえ | ターゲットが必要な場合の新鮮なインタースティシャルHTML |
解決策にはcf_clearanceクッキー、トークン、ユーザーエージェントが含まれる場合があります。これらの値は一時的なセッションデータであり、監視ランタイムで使用する必要がありますが、分析データウェアハウスに保存しないでください。
Cloudflareのチャレンジドキュメンテーションでは、チャレンジメカニズムの目的と種類が説明されています。技術的スキルがアクセス権を保証するものではないため、ソースポリシーが制御ルールとなります。
アナリスト、モデル、ログ、アラートにプロキシ資格情報を公開しないでください。信頼できるコード内でプロファイルを解決してください。
import os
from urllib.parse import urlparse
import capsolver
capsolver.api_key = os.environ["CAPSOLVER_API_KEY"]
SOURCE_POLICY = {
"shop.example.com": {
"proxy_profile": "inventory_us_west",
"max_checks_per_hour": 4,
}
}
PROXY_VAULT = {
"inventory_us_west": os.environ["INVENTORY_PROXY_US_WEST"],
}
def approved_host(url: str) -> str:
host = urlparse(url).hostname
if host not in SOURCE_POLICY:
raise PermissionError("Inventory source is not approved")
return host
def solve_cloudflare_challenge(
url: str,
chrome_user_agent: str,
fresh_html: str = "",
) -> dict:
host = approved_host(url)
profile = SOURCE_POLICY[host]["proxy_profile"]
task = {
"type": "AntiCloudflareTask",
"websiteURL": url,
"proxy": PROXY_VAULT[profile],
"userAgent": chrome_user_agent,
}
if fresh_html:
task["html"] = fresh_html
solution = capsolver.solve(task)
cookies = solution.get("cookies") or {}
clearance = cookies.get("cf_clearance") or solution.get("token")
if not clearance:
raise RuntimeError("Challenge solution did not include clearance")
return {
"cookies": cookies,
"user_agent": solution.get("userAgent") or chrome_user_agent,
"proxy_profile": profile,
}
静的またはスタックプロキシを使用してください。初期ナビゲーション、解決、ページ復元の間でネットワークアイデンティティをローテートしないでください。
承認されたプロキシとユーザーエージェントでPlaywrightコンテキストを作成し、チャレンジ状態をキャプチャし、解決を取得し、互換性のあるコンテキスト内でクッキーを適用してください。
from urllib.parse import urlparse
async def recover_inventory_page(browser, url: str):
host = approved_host(url)
profile = SOURCE_POLICY[host]["proxy_profile"]
proxy = PROXY_VAULT[profile]
bootstrap_context = await browser.new_context(
proxy={"server": proxy},
)
bootstrap_page = await bootstrap_context.new_page()
await bootstrap_page.goto(url, wait_until="domcontentloaded")
state = await classify_page(bootstrap_page)
if state != "CLOUDFLARE_CHALLENGE":
return bootstrap_context, bootstrap_page, False
user_agent = await bootstrap_page.evaluate("navigator.userAgent")
html = await bootstrap_page.content()
solution = solve_cloudflare_challenge(
url=url,
chrome_user_agent=user_agent,
fresh_html=html,
)
await bootstrap_context.close()
context = await browser.new_context(
proxy={"server": proxy},
user_agent=solution["user_agent"],
)
cookie_domain = urlparse(url).hostname
await context.add_cookies([
{
"name": name,
"value": value,
"domain": cookie_domain,
"path": "/",
"secure": True,
"httpOnly": True,
}
for name, value in solution["cookies"].items()
])
page = await context.new_page()
await page.goto(url, wait_until="domcontentloaded")
return context, page, True
異なるプロキシ形式には異なるPlaywrightフィールドが必要です。必要に応じてプロキシサーバー、ユーザー名、パスワードをvaultアダプターで解析してください。
JSON-LDや安定したページ契約をプレゼンテーションテキストよりも優先してください。
import json
SCHEMA_AVAILABILITY = {
"https://schema.org/InStock": "IN_STOCK",
"https://schema.org/OutOfStock": "OUT_OF_STOCK",
"https://schema.org/PreOrder": "PREORDER",
"https://schema.org/BackOrder": "BACKORDER",
"InStock": "IN_STOCK",
"OutOfStock": "OUT_OF_STOCK",
}
async def read_jsonld_offers(page) -> list[dict]:
blocks = await page.locator(
'script[type="application/ld+json"]'
).all_text_contents()
offers = []
for raw in blocks:
try:
data = json.loads(raw)
except json.JSONDecodeError:
continue
nodes = data if isinstance(data, list) else [data]
for node in nodes:
if not isinstance(node, dict):
continue
offer = node.get("offers")
if isinstance(offer, dict):
offers.append(offer)
elif isinstance(offer, list):
offers.extend(x for x in offer if isinstance(x, dict))
return offers
数量を発明することなく在庫を正規化してください:
def normalize_offer_availability(offer: dict) -> tuple[str, int | None]:
raw = str(offer.get("availability", ""))
availability = SCHEMA_AVAILABILITY.get(raw, "UNKNOWN")
inventory_level = offer.get("inventoryLevel")
quantity = None
if isinstance(inventory_level, dict):
value = inventory_level.get("value")
if isinstance(value, int) and value >= 0:
quantity = value
return availability, quantity
関連する証拠とパーサーのバージョンのハッシュを保存してください。これにより、不要なページコンテンツを保持することなくアラートを再現可能にします。
スナップショットの繰り返しではなく、遷移をアラートしてください。
def inventory_transition(previous: str, current: str) -> str | None:
if previous == current:
return None
if previous in {"OUT_OF_STOCK", "UNKNOWN"} and current == "IN_STOCK":
return "RESTOCKED"
if previous == "IN_STOCK" and current == "OUT_OF_STOCK":
return "SOLD_OUT"
return "STATUS_CHANGED"
ソースがノイジーな場合、2つの観測を必要とします:
def confirmed_transition(observations: list[InventoryObservation]) -> str | None:
if len(observations) < 3:
return None
older, previous, current = observations[-3:]
if previous.availability != current.availability:
return None
return inventory_transition(older.availability, current.availability)
2番目のサンプルは一時的なパーサーやページ状態エラーによるアラートを減らします。ソースの更新頻度に合わせてルールを調整してください。
チャレンジイベントはインフラストラクチャのシグナルです。これは在庫の変更ではありません。
| メトリクス | 意味 | アラートの宛先 |
|---|---|---|
inventory_restock_total |
確認された非利用から利用への遷移 | コマース運用 |
inventory_unknown_total |
パーサーが在庫を判別できなかった | データ品質キュー |
challenge_encounter_total |
承認されたページがチャレンジを提示した | 自動化運用 |
challenge_recovery_success |
復元が完了し、製品ページが戻った | リアビリティダッシュボード |
challenge_loop_total |
復元後にページが依然としてチャレンジ状態だった | オペレーターのレビュー |
チャレンジページ、HTTPエラー、または空のセレクターをOUT_OF_STOCKと分類しないでください。
The CapSolverエラーファクターは診断のガイドラインを提供し、CapSolverオートメーションブログは関連する復旧パターンについてカバーしています。
ボーナスコード: CapSolverダッシュボードでコード WEBS を使用して、毎回のチャージで追加の5%ボーナスを取得してください。
| コントロール | 推奨実装 |
|---|---|
| ソース権限 | ホストごとの承認記録と目的制限 |
| ソース優先度 | ブラウザフォールバックの前にフィードまたはAPI |
| プロキシ | サーバーサイドで解決された静的またはスタックプロファイル |
| ユーザーエージェント | 復旧を通じて同じサポートされているChromeのIDを使用 |
| クッキー | 短期間の暗号化ストレージ; アナリティクスの保持なし |
| リトライ | 1回の復旧試行後、オペレーターによるレビュー |
| レートリミット | ソース固有のクォータとバックオフ、ジッター |
| アラート | デフォルトで読み取り専用の通知 |
| 高影響アクション | 予約または購入の前に明示的な確認 |
CapSolver CAPTCHA解決FAQでタスクフローを理解し、CapSolver製品ページでサポートされている解決カテゴリを確認してください。
許可されたソースのみをモニタリングしてください。マーケットプレイスAPIライセンス、マーチャントの利用規約、レートリミット、プライバーシュアの要件、在庫データ契約に従ってください。プライベートアカウント、制限付きセラーダッシュボード、バイヤーレコード、または非公開在庫にアクセスするためにチャレンジ復元を使用しないでください。別途承認されたサービスが予約またはチェックアウトを処理し、明示的な人間の確認を伴う場合を除き、システムを読み取り専用に保つ必要があります。
Cloudflareチャレンジの復元は、APIファースト、バリアントに気を配った、ポリシー制御のデータパイプライン内で動作する場合、エコマース在庫モニタリングをより信頼性高くします。モニタはページIDを検証し、プロキシとユーザーエージェントの一貫性を維持し、クリアランスクッキーを一時的に消費し、構造化された利用可能性証拠を解析し、インフラストラクチャの障害と本物の在庫変更を分離する必要があります。
CapSolverで承認されたワークフローを開始し、制御されたソースでテストし、スケーリングする前に証拠の保持、レートリミット、オペレーターのレビューを追加してください。
いいえ。マーチャントフィード、マーケットプレイスAPI、セラーAPI、およびライセンス付きデータソースを優先してください。許可されたギャップやバイヤー向け検証のためにのみ認証されたブラウザを使用してください。
正確なターゲットURLと静的またはスタックプロキシを使用して、ドキュメント化されたAntiCloudflareTaskを使用してください。オプションフィールドには、ブラウザがサポートするChromeユーザーエージェントと新しいチャレンジHTMLが含まれます。
いいえ。チャレンジページ、エラーページ、または選択子がない状態はインフラストラクチャまたはパーサーの状態です。UNKNOWNを記録し、在庫遷移とは別にルーティングしてください。
短期間の暗号化されたランタイムストレージにのみ保持してください。モデルコンテキスト、アナリティクステーブル、アラート、または長期ログに配置しないでください。
デフォルトではモニタを読み取り専用に保ちます。予約、チェックアウト、購入には、別途承認されたサービス、新しい価格検証、ポリシー制限、および明示的な人間の確認が必要です。
無効なTurnstileトークンを修正するには、有効期限、サイトキー、アクション、CData、ブラウザの状態、サーバー検証、および制限されたCapSolverのリトライを確認してください。

ポリシー制限付きのMCP Cloudflare TurnstileワークフローをCapSolver、制限付きリトライ、ロギングをマスキングしたセッションチェック、および結果の検証を含むように構築してください。
