Wildberries、Ozon、または他の任意のウェブサイトを完全なHTMLページを通じてパースしている場合、プロキシのトラフィックに5〜10倍のコストを支払っています。商品カードの各ページは、200〜800 KBのマークアップ、スクリプト、スタイルで構成されており、実際に必要なフィールドはほんの数つ:価格、在庫、評価です。この記事では、ウェブサイトの隠れたAPIを見つけ、同じデータをコンパクトなJSON形式で直接取得する方法を説明します。
なぜHTMLのパースがプロキシのトラフィックを消費するのか
パーサーが通常のHTTPリクエストまたはヘッドレスブラウザ(Selenium、Puppeteer、Playwright)を通じてページを読み込むと、サーバーは完全なHTMLドキュメントを返します:マークアップ、インラインスクリプト、スタイル、時にはbase64画像や広告ウィジェット用のデータを含む数百行のJSONが含まれていますが、これらは必要ありません。Wildberriesの平均的な商品カードは300〜600 KB、Ozonではすべての関連リソース(CSS、フォント、トラッカー)を考慮すると800 KBに達します。
もし1日に10,000商品を3つのプロキシセッションで監視している場合、これは簡単に月に数十ギガバイトのトラフィックに膨れ上がります。レジデンシャルプロキシやモバイルプロキシは通常、トラフィックに基づいて販売されるため、余分なメガバイトは直接的なコストになります。一方、実際に必要なデータ(価格、割引、在庫、評価)は、JSONレスポンス内で1〜5 KBを占めます。1商品あたりのボリュームの差は100〜200倍であり、ブラウザのレンダリングにかかるオーバーヘッドを考慮すると、時間とCPUの節約はさらに大きくなります。
HTMLパースの追加の問題は、その脆弱性です。マーケットプレイスのウェブサイトは定期的にレイアウト、CSSクラス、DOM構造を変更します。このような変更は、XPathやCSSセレクタに基づいて構築されたパーサーを壊します。内部APIは、モバイルアプリケーションとウェブサイトのフロントエンドの両方の動作に依存しているため、はるかに頻繁には変更されません。
隠れたAPIとは何か、どこから来るのか
ほぼすべての現代のウェブサイトは、SPA(シングルページアプリケーション)またはハイブリッドアプリケーションであり、ブラウザは最初にページの「スケルトン」を読み込み、その後JavaScriptを介して内部APIに対して実際のデータ(価格、在庫、レビュー、推奨)を取得するための追加リクエストを行います。これらのリクエストは隠れたAPIまたは内部APIと呼ばれ、公開されていませんが、ブラウザのトラフィックでは完全にオープンです。
技術的には、これは通常、JSON形式でデータを返すRESTまたはGraphQLエンドポイントです。たとえば、Wildberriesでは商品カードがcard.wb.ruやwbx-content-v2.wbstatic.netのようなリクエストを介して読み込まれ、価格や在庫はbasket-01.wb.ruや類似のドメインへの別のリクエストで取得されます。Ozonでも同様のロジックがあり、フロントエンドは内部のcomposer APIにアクセスし、マイクロサービスからデータを集約します。
重要なのは、このようなAPIの使用は形式的にはハッキングではないということです。あなたは単に通常のユーザーブラウザが行うのと同じリクエストを繰り返しているだけです。しかし、ウェブサイトはこれらのエンドポイントをアンチボットシステムを通じて保護しているため、次に必要なのは、質の高いプロキシを使用して実際のクライアントの動作を慎重に模倣することです。
DevToolsを使って隠れたAPIを見つける方法
内部APIは、ChromeやFirefoxの組み込みツールを使用して、コードの行なしで見つけることができます。以下はステップバイステップのアルゴリズムです:
- Chromeで必要な商品ページを開き、F12を押してNetworkタブに移動します。
- リクエストフィルターでFetch/XHRタイプを選択します。これにより、画像、フォント、スタティックの読み込みを除外できます。
- ページを更新(F5)し、ページのスケルトンの読み込み後に表示されたリクエストのリストを確認します。
- レスポンスタブで商品の価格、名前、または他の必要なフィールドがJSON形式で見えるリクエストを見つけます。
- そのリクエストをクリックし、cURLとしてコピーします(右クリック→コピー→cURLとしてコピー)— これにより、すべてのヘッダー、クッキー、パラメータの完全なセットが得られます。
- URL内の必須パラメータ(商品ID、地域、APIバージョン)を確認し、データを失うことなく削除できるものを特定します。
その後、必要な商品IDまたはIDを代入して、通常のHTTPライブラリを介してこのリクエストを繰り返すだけで済みます。これにより、ページ全体をレンダリングすることなく、ほとんどのマーケットプレイス(Wildberries、Ozon、Avito、さらにはAmazonやeBayなどの多くの海外プラットフォーム)で機能します。
トラフィックの比較:HTML vs JSON API
データ量の違いは非常に大きいため、数字で示す価値があります。以下は、人気のあるマーケットプレイスでの1商品カードの平均測定値です。
| パース方法 | 平均レスポンスサイズ | 読み込み時間 | JSのレンダリングが必要か |
|---|---|---|---|
| Seleniumを通じた完全なHTML | 400-800 KB | 1.5-4秒 | はい |
| シンプルなHTTPリクエスト(requests) | 150-300 KB | 0.3-0.8秒 | いいえ |
| 隠れたJSON API | 3-15 KB | 0.1-0.3秒 | いいえ |
1日に50,000商品を監視する場合、ヘッドレスブラウザからAPIへの直接リクエストに切り替えることで、トラフィックが約30〜40 GBから月に300〜700 MBに削減されます。これはプロキシトラフィックの節約だけでなく、パーサーのサーバーインフラストラクチャへの負荷を軽減することにもつながります—レンダリングにかかるCPUが少なく、メモリが少なく、データ収集が迅速になります。
Pythonの実用例
簡略化された例を考えてみましょう:完全なページを読み込む代わりに、内部APIへの直接リクエストを通じて商品の価格と在庫を取得します。これは学習用のテンプレートであり、正確なエンドポイントとパラメータは特定のウェブサイトのDevToolsを通じて特定する必要があります。リクエストの構造は地域やAPIのバージョンによって異なる場合があります。
import requests
def get_product_data(product_id: str, proxies: dict = None) -> dict:
"""
完全なHTMLの代わりに内部APIを通じて商品データを取得します。
proxies — requests形式のプロキシ辞書:{"http": "...", "https": "..."}
"""
url = f"https://card.example-marketplace.ru/v2/detail"
params = {
"nm": product_id,
"dest": "-1257786", # 地域、DevToolsを通じて特定
"spp": "0"
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0 Safari/537.36",
"Accept": "application/json",
"Referer": f"https://www.example-marketplace.ru/catalog/{product_id}/detail.aspx"
}
response = requests.get(
url,
params=params,
headers=headers,
proxies=proxies,
timeout=10
)
response.raise_for_status()
data = response.json()
product = data["products"][0]
return {
"id": product["id"],
"name": product["name"],
"price": product["salePriceU"] / 100,
"stock": product.get("totalQuantity", 0),
"rating": product.get("reviewRating", None)
}
if __name__ == "__main__":
proxy = {
"http": "http://user:pass@proxy-host:port",
"https": "http://user:pass@proxy-host:port"
}
result = get_product_data("123456789", proxies=proxy)
print(result)
この例での3つのポイントに注意してください。まず、Refererヘッダーを指定しています。多くのAPIは、リクエストが「ブラウザから」来たことを確認します。次に、デフォルトのrequestsライブラリから検出されやすいユーザーエージェントではなく、現実的なUser-Agentを使用しています。最後に、すべてのリクエストがレンダリングなしの1つのHTTP呼び出しに収まるため、トラフィックと速度の大幅な向上を実現します。
GraphQLエンドポイントの場合も同様のロジックですが、GETパラメータの代わりにJSON形式のリクエストボディを持つPOSTリクエストを送信し、必要なフィールドを明示的に列挙します。これにより、サーバーは要求されたデータのみを返すため、レスポンスのサイズがさらに縮小されます。
APIリクエスト時のプロキシの使用
コンパクトなJSON形式に移行しても、プロキシは依然として必要です—マーケットプレイスは1つのIPからのリクエスト数を制限し、異常なアクティビティがあるとブロックします。プロキシの種類の正しい選択は、パーサーの安定性に直接影響します。
WildberriesやOzonのようなマーケットプレイスのAPIを大量に回避するには、データセンターのプロキシが適しています—これにより、高速で低コストのトラフィックが得られ、軽量なJSONエンドポイントへの頻繁なリクエストにおいて重要です。しかし、特定のAPIがより厳しいアンチボットで保護されており、データセンターのサブネット全体をブロックする場合は、レジデンシャルプロキシに切り替える方が賢明です—これらは家庭ユーザーの実際のIPアドレスを使用し、サブネットによるブロックを受けにくいです。
モバイルアプリケーションに依存するAPI(特定のバージョンのAvitoエンドポイントやマーケットプレイスは、モバイルトラフィックのみでデータを提供する)には、モバイルプロキシを介して接続する必要がある場合があります—これらは実際の携帯通信事業者のトラフィックを模倣し、通常のIPをブロックするチェックを通過します。
パーサーでプロキシを設定する際には、リクエストを時間的に分散させ、IPのローテーションを使用することも重要です—コンパクトなJSONリクエストを1分間に1000回同じアドレスから繰り返すと、保護システムに疑念を抱かれます。複数のプロキシセッションのプールを設定し、リクエスト間に1〜3秒のランダムな遅延を追加して負荷を分散させてください。
落とし穴:トークン、署名、アンチボット
隠れたAPIは必ずしもオープンではありません。いくつかのウェブサイトは、パーサーの構築時に考慮すべき追加のメカニズムでエンドポイントを保護しています。
- セッショントークンの有効期限 — 一部のAPIは、トークンを取得するための事前リクエストを要求し、その後、次のリクエストのヘッダーに渡され、制限された時間(通常5〜30分)だけ有効です。
- リクエストの署名(signature) — リクエストパラメータは、ページのJSコードからの秘密鍵でクライアント側でハッシュ化されます。この署名は、アルゴリズムを解析して手動で再現するか、トークン取得時にヘッドレスブラウザを介してのみ実行し、その後は軽量リクエストを直接送信する必要があります。
- IPおよびUser-Agentによるレート制限 — リクエスト頻度を超えると、ウェブサイトは一時的にアクセスをブロックします。これはプロキシのローテーションと合理的な遅延で解決できます。
- ヘッダーのフィンガープリンティング — 一部のシステムは、ヘッダーの完全なセット(順序、Accept-Language、Sec-Fetch-*の存在)を確認し、「不完全な」セットからのリクエストをブロックします。これはスクリプトではなくブラウザに特有です。
- データの地域依存性 — マーケットプレイスの価格や在庫は地域によって異なる場合があるため、リクエストに正しい地域/倉庫パラメータを渡すことが重要です。さもなければ、データは関連性がなくなります。
もしAPIが再現が難しいリクエスト署名で閉じられている場合、妥協案として、ヘッドレスブラウザ(Playwright、Puppeteer)を使用してネットワークリクエストをキャッチし、DOMのパースなしで準備されたJSONレスポンスを抽出することができます。これは直接のHTTPリクエストよりも遅いですが、ページのレイアウトを完全にパースするよりもまだ速く、軽量です。
パーサーを起動する前のチェックリスト
- DevToolsを通じてエンドポイントを見つけ、cURLとしてコピーし、Postmanまたはrequestsでテストしました。
- 必須のリクエストパラメータ(商品ID、地域、APIバージョン)を特定し、冗長なものを除外しました。
- User-Agent、Referer、Accept-Languageのヘッダーを現実的に設定しました。
- セッショントークンまたはリクエスト署名が必要かどうかを確認し、それらを取得する方法を考えました。
- プロキシのローテーションとリクエスト間のランダムな遅延を設定しました。
- 特定のウェブサイトの保護に適したプロキシの種類(データセンター、レジデンシャル、モバイル)を選択しました。
- 自動的に他のプロキシに切り替えるエラー429および403の処理を追加しました。
- 実際の節約を監視するためにトラフィック量のロギングを設定しました。
結論
完全なHTMLのパースから隠れたAPIの使用に移行することは、単なる技術的な最適化ではなく、プロキシトラフィックとインフラストラクチャのコストを直接削減することです。数百キロバイトの余分なマークアップを読み込む代わりに、価格、在庫、評価の監視に必要なフィールドだけを持つコンパクトなJSONを取得します。追加のボーナスは、ウェブサイトのレイアウトの変更に対するパーサーの耐性です。内部APIはフロントエンドよりも変更が少ないためです。
ただし、APIを見つける手法自体は、質の高いプロキシの必要性を排除するものではありません。マーケットプレイスのアンチボットシステムは、HTMLリクエストとJSONエンドポイントへのリクエストの両方を同様に注意深く監視しています。WildberriesやOzonを大量に監視している場合は、コストを削減するために迅速なデータセンターのプロキシから始め、ブロックの兆候が見られたら、より安定したパーサーの動作のためにレジデンシャルまたはモバイルIPプールに切り替えてください。