法的注意事項: 本記事で解説する技術は、公開されている検索結果ページのデータ収集のみを対象とします。米国では CFAA(Computer Fraud and Abuse Act) が不正アクセスを規制しており、EUでは GDPR が個人データの処理に適用されます。Amazonの利用規約(ToS)を遵守し、自身の出品や公開データの範囲内で利用してください。
プロキシを使ったAmazonキーワード順位トラッキングとは何か
Amazonの検索結果ページ(SERP)は、Googleのそれとは根本的に異なります。Amazonは検索エンジンであり同時にマーケットプレイスであり、ランキングは関連性と売上ベロシティ(sales velocity)の組み合わせで決定されます。そのため、出品者はキーワードごと、マーケットプレイスごと、ASINごとに有機的な検索順位を継続的にトラッキングする必要があります。
プロキシを使ったAmazonキーワード順位トラッキングとは、リージョンごとにローカライズされたAmazon検索結果をプロキシ経由で取得し、特定のASINの有機的な表示順位を抽出・記録する手法です。Amazonはマーケットプレイスごとに異なる結果を返すため、米国(amazon.com)とドイツ(amazon.de)で同じキーワードを検索しても結果が異なります。これを正しくトラッキングするには、対象マーケットプレイスに対応する国のIPアドレスからのリクエストが不可欠です。
なぜAmazon SERPは独自の仕組みなのか
AmazonのA9アルゴリズムは、テキストの関連性だけでなく、過去の販売実績、在庫状況、レビュー評価、クリック率、コンバージョン率などを総合的に評価します。これにより、新しいASINが上位に表示されるまでに時間がかかり、順位変動も激しくなります。
出品者が順位トラッキングを行う主な理由は以下の通りです:
- キーワード戦略の効果測定(PPC広告との連動)
- インデクセーションの確認(ASINが特定キーワードで検索結果に表示されているか)
- 競合の順位変動の監視
- アルゴリズム変更による影響の早期発見
Amazonの検索結果ページには、有機的な検索結果(organic results)とスポンサー広告(Sponsored placements)が混在しています。正確な順位トラッキングには、これらを区別することが不可欠です。
検索結果ページの構造とパース方法
Amazonの検索結果ページは、各商品を [data-component-type="s-search-result"] 属性を持つdiv要素としてレンダリングします。各結果要素には以下の情報が含まれます:
data-asin属性 — ASIN(Amazon Standard Identification Number、10桁の英数字)- 商品タイトル、価格、レビュー数、評価
- Sponsored ラベル — スポンサー広告であることを示すテキスト
有機的な順位(organic position)を計算するには、スポンサー広告を除外してカウントする必要があります。以下に基本的なパースロジックを示します。
HTML構造の例
<div data-component-type="s-search-result" data-asin="B08N5WRWNW">
<div class="s-result-item">
<span class="a-color-secondary">Sponsored</span>
<h2><a href="/dp/B08N5WRWNW">Product Title</a></h2>
<span class="a-price">$29.99</span>
</div>
</div>
<div data-component-type="s-search-result" data-asin="B07XJ8C8F5">
<h2><a href="/dp/B07XJ8C8F5">Organic Product</a></h2>
</div>
上記の例では、1番目の結果はSponsored(有料)、2番目の結果は有機的(organic)です。有機的な順位としては、2番目の結果が1位となります。
プロキシが必須な理由:Amazonのアンチボットとローカライゼーション
Amazonは強力なアンチボット対策を実装しています。短時間に同じIPアドレスから多数のリクエストを送信すると、CAPTCHAチャレンジが表示されたり、IPが一時的にブロックされたりします。また、Amazonはリクエスト元のIPアドレスの地理的情報に基づいて、検索結果をローカライズします。
これにより、以下の課題が生じます:
- 地理的ローカライゼーション: 日本からamazon.comにアクセスすると、米国在庫とは異なる結果が返される場合があります。
- レート制限: 同一IPからの連続リクエストは制限されます。Amazonは1IPあたり約100〜200リクエスト/時間程度で制限をかけると報告されています。
- ボット検出: TLSフィンガープリント、ヘッダー分析、行動パターンによりボットを検出します。
これらの課題を解決するために、リージョンごとのIPを持つレスデンシャルプロキシが最適です。データセンタープロキシはAmazonによって検出されやすく、ブロックされる頻度が高くなります。
ProxyHatのセットアップとgeo-targeting
ProxyHatのプロキシゲートウェイを使用すると、ユーザー名にフラグを追加するだけで国・都市レベルのジオターゲティングが可能です。また、-session- フラグでスティッキーセッションを維持でき、ページネーション時のIP一貫性を確保できます。
| パラメータ | 値 | 用途 |
|---|---|---|
| ゲートウェイ | gate.proxyhat.com | 全プロキシのエンドポイント |
| HTTP ポート | 8080 | HTTP/HTTPSプロキシ |
| SOCKS5 ポート | 1080 | SOCKS5プロキシ |
| 国指定 | -country-US / -country-DE | マーケットプレイスごとのローカライゼーション |
| セッション | -session-abc123 | ページネーション時のIP固定 |
ProxyHatの設定については 公式ドキュメント を参照してください。また、利用可能なロケーションは ロケーション一覧 で確認できます。
実装例1:curlを使った基本リクエスト
まずはcurlを使った基本的なプロキシリクエストを示します。米国のamazon.comから検索結果を取得する例です。
# 米国IPでamazon.comの検索結果を取得
curl -x "http://user-country-US:pass@gate.proxyhat.com:8080" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept-Language: en-US,en;q=0.9" \
"https://www.amazon.com/s?k=wireless+earbuds&page=1"
# ドイツIPでamazon.deの検索結果を取得
curl -x "http://user-country-DE:pass@gate.proxyhat.com:8080" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept-Language: de-DE,de;q=0.9,en;q=0.7" \
"https://www.amazon.de/s?k=bluetooth+kopfhörer&page=1"
実装例2:Python + curl_cffiでAmazon SERPをスクレイピング
curl_cffiは、ブラウザのTLSフィンガープリントを模倣できるPythonライブラリで、Amazonのボット検知を回避するのに有効です。以下に、ProxyHatプロキシ経由でAmazon検索結果を取得し、ASINの有機的順位を計算する実装を示します。
import re
import time
import logging
from typing import Optional
from curl_cffi import requests
from bs4 import BeautifulSoup
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)
PROXY_BASE = "http://user-country-{country}-session-{session}:pass@gate.proxyhat.com:8080"
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,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
}
def fetch_amazon_search(
keyword: str,
page: int = 1,
country: str = "US",
session_id: str = "ranktrack01",
marketplace_domain: str = "amazon.com",
max_retries: int = 3,
) -> Optional[str]:
"""Amazon検索結果ページのHTMLを取得する"""
proxy = PROXY_BASE.format(country=country, session=session_id)
proxies = {"http": proxy, "https": proxy}
url = f"https://www.{marketplace_domain}/s?k={keyword}&page={page}"
for attempt in range(max_retries):
try:
response = requests.get(
url,
headers=HEADERS,
proxies=proxies,
impersonate="chrome120",
timeout=30,
)
if response.status_code == 200:
html = response.text
if "captcha" in html.lower() or "robot" in html.lower():
logger.warning(f"CAPTCHA detected on page {page}, attempt {attempt+1}")
time.sleep(2 ** attempt)
continue
return html
elif response.status_code == 503:
logger.warning(f"503 on page {page}, retrying after backoff")
time.sleep(2 ** attempt)
else:
logger.error(f"Unexpected status {response.status_code}")
return None
except Exception as e:
logger.error(f"Request failed: {e}")
time.sleep(2 ** attempt)
return None
def parse_search_results(html: str) -> list[dict]:
"""検索結果ページからASIN、有機的順位、スポンサーフラグを抽出する"""
soup = BeautifulSoup(html, "html.parser")
results = []
organic_position = 0
items = soup.select('[data-component-type="s-search-result"]')
for item in items:
asin = item.get("data-asin", "")
if not asin:
continue
# Sponsored ラベルの検出
sponsored_text = item.find(string=re.compile("Sponsored", re.I))
is_sponsored = sponsored_text is not None
if not is_sponsored:
organic_position += 1
# タイトルの取得
title_tag = item.select_one("h2 a")
title = title_tag.get_text(strip=True) if title_tag else ""
results.append({
"asin": asin,
"title": title,
"is_sponsored": is_sponsored,
"organic_position": organic_position if not is_sponsored else None,
"raw_position": len(results) + 1,
})
return results
def find_asin_position(results: list[dict], target_asin: str) -> Optional[int]:
"""対象ASINの有機的順位を返す(見つからない場合はNone)"""
for r in results:
if r["asin"] == target_asin and not r["is_sponsored"]:
return r["organic_position"]
return None
実装例3:複数ページの順位トラッキング
Amazonの検索結果は通常1ページあたり約50〜60件表示されます。対象ASINが上位20件に入らない場合、複数ページを巡回する必要があります。以下に、ページ1〜5を取得してASINの順位を特定する実装を示します。
import json
from datetime import datetime
from pathlib import Path
def track_keyword_rank(
keyword: str,
target_asin: str,
country: str = "US",
marketplace_domain: str = "amazon.com",
max_pages: int = 5,
) -> dict:
"""キーワードに対するASINの有機的順位をトラッキングする"""
session_id = f"track-{keyword.replace(' ', '-')}-{country}"
all_results = []
found_position = None
found_page = None
for page in range(1, max_pages + 1):
logger.info(f"Fetching page {page} for keyword='{keyword}', country={country}")
html = fetch_amazon_search(
keyword=keyword,
page=page,
country=country,
session_id=session_id,
marketplace_domain=marketplace_domain,
)
if not html:
logger.warning(f"Failed to fetch page {page}")
break
results = parse_search_results(html)
all_results.extend(results)
pos = find_asin_position(results, target_asin)
if pos is not None:
found_position = (page - 1) * 50 + pos # ページをまたぐ全体順位
found_page = page
logger.info(f"ASIN {target_asin} found at organic position {found_position} (page {page}, pos {pos})")
break
time.sleep(2) # ページ間の礼儀的な待機
record = {
"timestamp": datetime.utcnow().isoformat(),
"keyword": keyword,
"asin": target_asin,
"country": country,
"marketplace": marketplace_domain,
"organic_position": found_position,
"page_found": found_page,
"total_results_scanned": len(all_results),
"is_indexed": found_position is not None,
}
# 履歴をJSONファイルに保存
history_path = Path(f"rank_history_{target_asin}.jsonl")
with open(history_path, "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return record
# 実行例
if __name__ == "__main__":
record = track_keyword_rank(
keyword="wireless earbuds",
target_asin="B0CX22V3Z4",
country="US",
marketplace_domain="amazon.com",
max_pages=5,
)
print(json.dumps(record, indent=2, ensure_ascii=False))
実装例4:Playwrightを使ったブラウザベースのスクレイピング
JavaScriptレンダリングが必要な場合や、より高度なボット検知を回避する必要がある場合は、Playwrightを使用できます。ProxyHatプロキシをPlaywrightに統合する方法を示します。
import asyncio
from playwright.async_api import async_playwright
from bs4 import BeautifulSoup
import re
async def fetch_with_playwright(
keyword: str,
page_num: int,
country: str = "US",
session_id: str = "pw-session-01",
marketplace_domain: str = "amazon.com",
) -> str:
"""Playwright + ProxyHatでAmazon検索結果を取得"""
proxy_url = f"http://user-country-{country}-session-{session_id}:pass@gate.proxyhat.com:8080"
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=True,
proxy={
"server": "http://gate.proxyhat.com:8080",
"username": f"user-country-{country}-session-{session_id}",
"password": "pass",
},
)
context = await browser.new_context(
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",
locale="en-US",
viewport={"width": 1920, "height": 1080},
)
page = await context.new_page()
url = f"https://www.{marketplace_domain}/s?k={keyword}&page={page_num}"
try:
await page.goto(url, wait_until="domcontentloaded", timeout=30000)
await page.wait_for_selector('[data-component-type="s-search-result"]', timeout=10000)
html = await page.content()
return html
except Exception as e:
print(f"Playwright error: {e}")
return ""
finally:
await browser.close()
async def track_with_playwright(keyword: str, target_asin: str, country: str = "US"):
"""Playwrightベースの順位トラッキング"""
for page_num in range(1, 6):
html = await fetch_with_playwright(keyword, page_num, country)
if not html:
continue
soup = BeautifulSoup(html, "html.parser")
items = soup.select('[data-component-type="s-search-result"]')
organic_pos = 0
for item in items:
asin = item.get("data-asin", "")
if not asin:
continue
is_sponsored = item.find(string=re.compile("Sponsored", re.I)) is not None
if not is_sponsored:
organic_pos += 1
if asin == target_asin:
overall_pos = (page_num - 1) * 50 + organic_pos
print(f"ASIN {target_asin}: organic position {overall_pos} (page {page_num})")
return overall_pos
await asyncio.sleep(2)
print(f"ASIN {target_asin} not found in top {5 * 50} results")
return None
# 実行
if __name__ == "__main__":
asyncio.run(track_with_playwright("wireless earbuds", "B0CX22V3Z4", "US"))
実装例5:複数マーケットプレイスの並列トラッキング
複数のマーケットプレイス(amazon.com、amazon.de、amazon.co.jpなど)を同時にトラッキングする場合は、asyncioを使った並列処理が有効です。各マーケットプレイスに異なる国のプロキシを使用します。
import asyncio
import aiohttp
from datetime import datetime
import json
MARKETPLACES = {
"US": {"domain": "amazon.com", "country": "US", "lang": "en-US,en;q=0.9"},
"DE": {"domain": "amazon.de", "country": "DE", "lang": "de-DE,de;q=0.9,en;q=0.7"},
"JP": {"domain": "amazon.co.jp", "country": "JP", "lang": "ja-JP,ja;q=0.9,en;q=0.7"},
"UK": {"domain": "amazon.co.uk", "country": "GB", "lang": "en-GB,en;q=0.9"},
}
async def fetch_marketplace(
session: aiohttp.ClientSession,
keyword: str,
target_asin: str,
mp_code: str,
mp_config: dict,
) -> dict:
"""単一マーケットプレイスの順位を取得"""
country = mp_config["country"]
domain = mp_config["domain"]
session_id = f"mp-{mp_code}-{keyword.replace(' ', '-')}"
proxy = f"http://user-country-{country}-session-{session_id}:pass@gate.proxyhat.com:8080"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": mp_config["lang"],
}
found_position = None
for page in range(1, 4):
url = f"https://www.{domain}/s?k={keyword}&page={page}"
try:
async with session.get(url, proxy=proxy, headers=headers, timeout=aiohttp.ClientTimeout(total=30)) as resp:
if resp.status != 200:
continue
html = await resp.text()
if "captcha" in html.lower():
print(f"CAPTCHA on {mp_code} page {page}")
await asyncio.sleep(5)
continue
# parse_search_results を呼び出し(前述の関数)
from bs4 import BeautifulSoup
import re
soup = BeautifulSoup(html, "html.parser")
items = soup.select('[data-component-type="s-search-result"]')
organic_pos = 0
for item in items:
asin = item.get("data-asin", "")
if not asin:
continue
is_sponsored = item.find(string=re.compile("Sponsored", re.I)) is not None
if not is_sponsored:
organic_pos += 1
if asin == target_asin:
found_position = (page - 1) * 50 + organic_pos
break
if found_position:
break
except Exception as e:
print(f"Error on {mp_code} page {page}: {e}")
await asyncio.sleep(1)
return {
"marketplace": mp_code,
"domain": domain,
"keyword": keyword,
"asin": target_asin,
"organic_position": found_position,
"is_indexed": found_position is not None,
"timestamp": datetime.utcnow().isoformat(),
}
async def track_all_marketplaces(keyword: str, target_asin: str):
"""全マーケットプレイスを並列トラッキング"""
async with aiohttp.ClientSession() as session:
tasks = [
fetch_marketplace(session, keyword, target_asin, code, config)
for code, config in MARKETPLACES.items()
]
results = await asyncio.gather(*tasks, return_exceptions=True)
for r in results:
if isinstance(r, dict):
print(json.dumps(r, indent=2, ensure_ascii=False))
return results
if __name__ == "__main__":
asyncio.run(track_all_marketplaces("wireless earbuds", "B0CX22V3Z4"))
プロダクション運用のベストプラクティス
日次スケジューリング
順位トラッキングは1日1回程度の頻度で十分です。以下はcronエントリの例です:
# 毎日午前6時にトラッキングを実行
0 6 * * * /usr/bin/python3 /opt/ranktracker/main.py >> /var/log/ranktracker.log 2>&1
リトライとバックオフ
ネットワークエラーや503レスポンスに対しては、指数バックオフを適用します。上記の fetch_amazon_search 関数では、最大3回のリトライを2秒、4秒、8秒の間隔で実装しています。
CAPTCHA検出
レスポンスHTMLに「captcha」や「robot」という文字列が含まれている場合、CAPTCHAチャレンジが表示されています。この場合は、セッションIDを変更して新しいIPでリトライするか、リクエスト頻度を下げてください。
インデクセーションチェック
ASINが特定キーワードで検索結果に全く表示されない場合、インデクセーションの問題が考えられます。is_indexed フラグを記録し、長期間にわたって false が続く場合はアラートを発生させましょう。
データ保存
順位履歴はJSONL形式またはSQLiteデータベースに保存し、時系列でのトレンド分析に使用します。少なくとも90日分の履歴を保持することで、季節変動とアルゴリズム変更の影響を区別できます。
よくある間違いとエッジケース
- スポンサー広告を有機的順位に含めてしまう:
Sponsoredラベルの検出を怠ると、順位データが不正確になります。 - ページネーション中にIPが変わる: スティッキーセッション(
-session-)を使用しないと、ページごとに異なるIPからリクエストが送られ、結果の一貫性が失われます。 - Accept-Languageヘッダーを設定しない: ヘッダーとプロキシの国が一致していないと、Amazonが不自然なリクエストと判断する可能性があります。
- 頻度が高すぎる: 1時間に数百回のリクエストを送ると、IPがブロックされます。1キーワードあたり1日1回が安全な頻度です。
- モバイルとデスクトップの混在: AmazonのモバイルSERPはデスクトップとは異なる構造を持つ場合があります。トラッキングの際はデバイスを統一してください。
倫理的配慮とAmazon SP-API
スクレイピングは強力な手法ですが、Amazonの利用規約と抵触する可能性があります。以下のガイドラインを推奨します:
- 自身の出品の順位トラッキングに限定する、または公開データの範囲内で利用する。
- リクエスト頻度を抑え、サーバーに負荷をかけない(1日1回程度)。
- 可能な場合は Amazon SP-API(Selling Partner API) の使用を検討する。SP-APIは公式のAPIで、出品レポートやカタログデータにアクセスできます。
robots.txtを確認し、クロールが許可されていないパスを避ける。
ProxyHatの プラン一覧 で利用可能なプロキシタイプと価格を確認できます。また、ウェブスクレイピングのユースケース や SERPトラッキング の詳細も参照してください。
Key Takeaways
- AmazonのSERPはGoogleとは異なり、売上ベロシティと関連性でランキングが決まるため、継続的な順位トラッキングが重要。
[data-component-type="s-search-result"]とdata-asinを使って結果をパースし、Sponsored ラベルで有料と有機的を区別する。- マーケットプレイスごとのローカライゼーションには、レスデンシャルプロキシとジオターゲティング(
-country-US、-country-DE)が必須。- ページネーション時は
-session-フラグでスティッキーセッションを維持し、IPの一貫性を確保する。- curl_cffiやPlaywrightでブラウザのTLSフィンガープリントを模倣し、ボット検知を回避する。
- 日次頻度でトラッキングし、CAPTCHA検出、リトライバックオフ、インデクセーションチェックを実装する。
- 可能な場合はAmazon SP-APIの使用を検討し、スクレイピングは公開データと自身の出品に限定する。
FAQ
プロキシを使ったAmazonキーワード順位トラッキングとは何ですか?
プロキシを使ったAmazonキーワード順位トラッキングとは、リージョンごとにローカライズされたAmazon検索結果をプロキシ経由で取得し、特定のASINの有機的な検索順位を抽出・記録する手法です。Amazonはマーケットプレイスごとに異なる検索結果を返すため、対象国のIPアドレスからのアクセスが必要です。レスデンシャルプロキシを使用することで、アンチボット対策を回避しつつ正確な地域別順位データを収集できます。
なぜプロキシを使ったAmazonキーワード順位トラッキングがプロキシユーザーにとって重要なのですか?
Amazonは強力なアンチボット対策とIPベースのレート制限を実装しており、同一IPからの連続リクエストはブロックされます。また、検索結果はリクエスト元IPの地理的情報に基づいてローカライズされるため、データセン�ーIPでは正確な地域別順位が取得できません。プロキシを使うことで、各マーケットプレイスに対応する国のIPからリクエストを送信し、ブロックを回避しながら正確な順位データを継続的に収集できます。
Amazonキーワード順位トラッキングに最適なプロキシタイプはどれですか?
レスデンシャルプロキシが最適です。データセン�ープロキシはAmazonによって検出されやすく、ブロックされる頻度が高くなります。レスデンシャルプロキシは実際のISPから割り当てられたIPアドレスを使用するため、Amazonのボット検知システムを回避しやすくなります。モバイルプロキシも有効ですが、コストが高くなります。ProxyHatではユーザー名に -country-US や -country-DE を指定するだけでジオターゲティングが可能です。
プロキシを使ったAmazonキーワード順位トラッキングでブロックを回避するにはどうすればよいですか?
ブロックを回避するには、以下の対策を組み合わせます:(1)レスデンシャルプロキシを使用する。(2)ページネーション時に -session- フラグでスティッキーセッションを維持する。(3)curl_cffiやPlaywrightでブラウザのTLSフィンガープリントを模倣する。(4)Accept-Languageヘッダーをプロキシの国に合わせる。(5)リクエスト頻度を1日1回程度に抑える。(6)CAPTCHA検出ロジックを実装し、検出時はセッションを変更してリトライする。
Amazon SP-APIとスクレイピングのどちらを使うべきですか?
可能な場合はSP-APIの使用を優先すべきです。SP-APIはAmazonが公式に提供するAPIで、利用規約に準拠しつつデータにアクセスできます。ただし、SP-APIではリアルタイムの検索順位データは提供されていないため、キーワード順位トラッキングにはスクレイピングが必要な場合があります。自身の出品のトラッキングに限定し、公開データの範囲内で利用し、適切な頻度でリクエストを行うことが重要です。



