Отслеживание позиций ключевых слов Amazon через прокси: руководство для разработчиков

Полное руководство по отслеживанию позиций ключевых слов Amazon через residential-прокси. Примеры на Python с curl_cffi и Playwright, парсинг SERP, production-настройки.

Amazon Keyword Rank Tracking with Proxies: A Developer's Guide

Юридическая заметка: Эта статья посвящена сбору публично доступных данных поисковой выдачи Amazon. В США несанкционированный доступ к компьютерным системам регулируется CFAA (18 U.S.C. § 1030), а в ЕС обработка персональных данных подпадает под GDPR. Соблюдайте Условия использования Amazon, не обходите технические средства защиты и не собирайте персональные данные. Для коммерческого использования рассмотрите официальный Amazon SP-API.

Отслеживание позиций ключевых слов Amazon через прокси — критически важная задача для продавцов на Amazon и marketplace SEO-разработчиков. Позиция товара в поисковой выдаче напрямую влияет на видимость, клики и продажи. В этом руководстве мы разберём, как построить надёжный amazon keyword rank tracker на Python, используя residential-прокси для обхода гео-ограничений и анти-бот защиты Amazon.

Почему Amazon — это отдельный SERP: отслеживание позиций ключевых слов через прокси

Amazon — не просто поисковик; это торговая платформа, где ранжирование определяется комбинацией релевантности и коммерческих факторов. В отличие от Google, где основную роль играют ссылки и контент, алгоритм Amazon A9 учитывает:

  • Исторические продажи (sales velocity) — товары с высоким объёмом продаж ранжируются выше
  • Релевантность запроса — соответствие названия, буллетов и описания поисковому запросу
  • Click-through rate и conversion rate — кликабельность и конверсия карточки
  • Доступность и цену — out-of-stock товары понижаются в выдаче

Для продавца это означает: позиция по ключевому слову на странице 1 и странице 5 — разница в десятки процентов выручки. Поэтому необходимо регулярно отслеживать органическую позицию каждого ASIN по целевым ключевым словам, в конкретном marketplace (amazon.com, amazon.de, amazon.co.uk) и в конкретном регионе доставки.

Amazon SERP scraping осложняется тем, что результаты локализованы: один и тот же запрос в Германии и в США даёт разные товары на разных позициях. Кроме того, Amazon активно применяет анти-бот защиту — CAPTCHA, rate limiting и блокировка подозрительных IP-адресов. По данным исследований, datacenter-прокси имеют success rate всего 40–70% на Amazon, тогда как residential-прокси достигают 95–99%.

Парсинг страницы результатов Amazon

Страница результатов поиска Amazon имеет относительно стабильную HTML-структуру. Каждый результат поиска заключён в элемент с атрибутом data-component-type="s-search-result". Внутри этого элемента:

  • data-asin — идентификатор товара (ASIN)
  • Текст "Sponsored" или локализованный аналог — признак платного размещения
  • Заголовок и цена — во вложенных элементах

Органическая позиция — это порядковый номер результата, исключая спонсированные размещения. Например, если на странице 10 результатов, из которых 2 спонсированных, органическая позиция ASIN может быть от 1 до 8.

Разметка: что искать в HTML

<div data-component-type="s-search-result" data-asin="B0XXXXXXXX">
  <div data-component-type="sp-sponsored-result"> ... </div>
  <span>Sponsored</span>
  <h2><a href="/dp/B0XXXXXXXX">Product Title</a></h2>
  <span class="a-price">$29.99</span>
</div>

Ключевые признаки спонсированного размещения: текст "Sponsored" (или локализованный аналог — "Реклама", "Gesponsert", "Sponsorisé"), а также атрибут data-component-type="sp-sponsored-result" внутри карточки.

Почему нужны residential-прокси с гео-таргетингом

Amazon локализует выдачу по IP-адресу и marketplace. Запрос из дата-центра в США к amazon.de может вернуть упрощённую или искажённую выдачу. Для точного отслеживания позиций необходимо:

  1. Гео-таргетинг по стране — residential IP в той же стране, что и marketplace
  2. Sticky-сессии — один IP для всех страниц одного запроса (пагинация)
  3. Ротация между запросами — разные IP для разных ключевых слов, чтобы избежать rate limiting

ProxyHat предоставляет residential-прокси с гео-таргетингом через параметры в username:

  • user-country-US — IP из США для amazon.com
  • user-country-DE — IP из Германии для amazon.de
  • user-session-abc123 — sticky-сессия для стабильной пагинации

Datacenter-прокси чаще блокируются Amazon, потому что IP-диапазоны дата-центров хорошо известны. Residential-прокси используют IP реальных провайдеров, что делает их неотличимыми от обычных пользователей. Подробнее о типах прокси — на странице use-cases web scraping.

Практическая реализация: отслеживание позиций Amazon на Python

Ниже — несколько рабочих примеров: от простого curl до production-скрипта с Playwright. Каждый пример использует ProxyHat на gate.proxyhat.com:8080.

1. Базовый запрос через curl с ProxyHat

# Поиск на amazon.com через residential-прокси США

curl -s \
  -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"

2. Python: curl_cffi + ProxyHat (имперсонация браузера)

Библиотека curl_cffi имитирует TLS-отпечаток реального браузера, что снижает риск блокировок. Сравните: raw-прокси (без гео-таргетинга) и ProxyHat с country-флагом:

from curl_cffi import requests

# --- Raw proxy (без гео-таргетинга) ---
raw_proxy = "http://user:pass@gate.proxyhat.com:8080"

# --- ProxyHat с гео-таргетингом и sticky-сессией ---
def make_proxyhat_url(country: str, session_id: str,
                      user: str = "user", pw: str = "pass") -> str:
    """Создаёт URL прокси ProxyHat с country и session флагами."""
    username = f"{user}-country-{country}-session-{session_id}"
    return f"http://{username}:{pw}@gate.proxyhat.com:8080"

def fetch_amazon_page(keyword: str, page: int, country: str = "US") -> str:
    session_id = f"kw-{keyword.replace(' ', '-')}-pg{page}"
    proxy = make_proxyhat_url(country, session_id)

    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-Language": "en-US,en;q=0.9",
        "Accept": "text/html,application/xhtml+xml",
    }

    marketplace_domains = {
        "US": "amazon.com", "DE": "amazon.de", "UK": "amazon.co.uk",
        "FR": "amazon.fr", "IT": "amazon.it", "ES": "amazon.es",
        "JP": "amazon.co.jp",
    }
    domain = marketplace_domains.get(country, "amazon.com")
    url = f"https://www.{domain}/s?k={keyword.replace(' ', '+')}&page={page}"

    response = requests.get(url, headers=headers, proxy=proxy,
                            impersonate="chrome120", timeout=30)
    response.raise_for_status()
    return response.text

3. Парсинг SERP: извлечение ASIN, позиции и Sponsored

from bs4 import BeautifulSoup
from dataclasses import dataclass
from typing import List

@dataclass
class AmazonResult:
    asin: str
    organic_position: int
    is_sponsored: bool
    title: str
    page: int

SPONSORED_LABELS = {"Sponsored", "Реклама", "Gesponsert",
                    "Sponsorisé", "Sponsorizzato", "Patrocinado"}

def parse_amazon_serp(html: str, page: int) -> List[AmazonResult]:
    soup = BeautifulSoup(html, "html.parser")
    results = []
    organic_counter = 0

    for div in soup.select('[data-component-type="s-search-result"]'):
        asin = div.get("data-asin", "")
        if not asin:
            continue

        is_sponsored = False
        for label in div.find_all(string=True):
            if label.strip() in SPONSORED_LABELS:
                is_sponsored = True
                break

        sponsored_div = div.select_one(
            '[data-component-type="sp-sponsored-result"]'
        )
        if sponsored_div:
            is_sponsored = True

        title_tag = div.select_one("h2 a span") or div.select_one("h2 span")
        title = title_tag.get_text(strip=True) if title_tag else ""

        if not is_sponsored:
            organic_counter += 1

        results.append(AmazonResult(
            asin=asin,
            organic_position=organic_counter if not is_sponsored else 0,
            is_sponsored=is_sponsored,
            title=title,
            page=page,
        ))

    return results

def find_asin_position(results: List[AmazonResult],
                        target_asin: str) -> dict:
    """Находит позицию целевого ASIN в органической выдаче."""
    for r in results:
        if r.asin == target_asin and not r.is_sponsored:
            return {
                "found": True,
                "organic_position": r.organic_position,
                "page": r.page,
                "is_sponsored": False,
            }
        if r.asin == target_asin and r.is_sponsored:
            return {
                "found": True,
                "organic_position": 0,
                "page": r.page,
                "is_sponsored": True,
            }
    return {"found": False, "organic_position": 0, "page": 0}

4. Playwright + ProxyHat: для JS-рендеренных страниц

Иногда Amazon отдаёт динамический контент, требующий рендеринга JavaScript. Playwright справляется с этим, используя прокси через launch-параметры:

import asyncio
from playwright.async_api import async_playwright

async def track_keyword_playwright(
    keyword: str,
    target_asin: str,
    country: str = "US",
    max_pages: int = 5,
) -> list[dict]:
    marketplace_domains = {
        "US": "amazon.com", "DE": "amazon.de", "UK": "amazon.co.uk",
        "FR": "amazon.fr", "IT": "amazon.it", "ES": "amazon.es",
    }
    domain = marketplace_domains.get(country, "amazon.com")

    positions = []
    session_id = f"track-{keyword.replace(' ', '-')}-{country}"
    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(
            proxy={"server": proxy_url},
            headless=True,
        )
        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" if country == "US" else "de-DE",
        )

        for page_num in range(1, max_pages + 1):
            url = (
                f"https://www.{domain}/s?k="
                f"{keyword.replace(' ', '+')}&page={page_num}"
            )
            page = await context.new_page()
            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()
                results = parse_amazon_serp(html, page_num)
                pos = find_asin_position(results, target_asin)
                if pos["found"]:
                    positions.append({
                        "keyword": keyword,
                        "asin": target_asin,
                        "page": pos["page"],
                        "organic_position": pos["organic_position"],
                        "sponsored": pos["is_sponsored"],
                        "country": country,
                    })
                    break
            except Exception as e:
                print(f"Page {page_num} error: {e}")
            finally:
                await page.close()

        await browser.close()

    return positions

# Запуск
# asyncio.run(track_keyword_playwright("wireless earbuds", "B0DXXXXXXX", "US"))

5. Production-скрипт: планировщик с retry, backoff и историей позиций

import asyncio
import json
import logging
import sqlite3
from datetime import datetime, timezone
from typing import Optional

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("amazon-tracker")

DB_PATH = "rank_history.db"

def init_db():
    conn = sqlite3.connect(DB_PATH)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS rank_history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            keyword TEXT NOT NULL,
            asin TEXT NOT NULL,
            country TEXT NOT NULL,
            organic_position INTEGER,
            page INTEGER,
            is_sponsored INTEGER,
            found INTEGER,
            tracked_at TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()

async def fetch_with_retry(
    keyword: str,
    page: int,
    country: str,
    max_retries: int = 3,
) -> Optional[str]:
    """Fetch с экспоненциальным backoff."""
    for attempt in range(max_retries):
        try:
            html = fetch_amazon_page(keyword, page, country)
            if "captcha" in html.lower() or "robot" in html.lower():
                logger.warning(f"CAPTCHA detected on page {page}, "
                               f"attempt {attempt + 1}")
                await asyncio.sleep(2 ** (attempt + 2))
                continue
            return html
        except Exception as e:
            logger.error(f"Fetch error page {page}: {e}")
            wait = 2 ** (attempt + 1)
            logger.info(f"Retrying in {wait}s...")
            await asyncio.sleep(wait)
    return None

async def track_keyword(
    keyword: str,
    target_asin: str,
    country: str = "US",
    max_pages: int = 5,
) -> dict:
    """Отслеживает позицию ASIN по ключевому слову на 1-5 страницах."""
    init_db()

    for page_num in range(1, max_pages + 1):
        html = await fetch_with_retry(keyword, page_num, country)
        if not html:
            logger.warning(f"Failed to fetch page {page_num}")
            continue

        results = parse_amazon_serp(html, page_num)
        pos = find_asin_position(results, target_asin)

        if pos["found"]:
            record = {
                "keyword": keyword,
                "asin": target_asin,
                "country": country,
                "organic_position": pos["organic_position"],
                "page": pos["page"],
                "is_sponsored": pos["is_sponsored"],
                "found": True,
                "tracked_at": datetime.now(timezone.utc).isoformat(),
            }
            _save_record(record)
            logger.info(
                f"Found {target_asin} for '{keyword}' "
                f"at organic pos {pos['organic_position']} "
                f"on page {pos['page']} ({country})"
            )
            return record

    record = {
        "keyword": keyword,
        "asin": target_asin,
        "country": country,
        "organic_position": 0,
        "page": 0,
        "is_sponsored": False,
        "found": False,
        "tracked_at": datetime.now(timezone.utc).isoformat(),
    }
    _save_record(record)
    logger.warning(f"ASIN {target_asin} not found for '{keyword}' "
                   f"in {max_pages} pages ({country})")
    return record

def _save_record(record: dict):
    conn = sqlite3.connect(DB_PATH)
    conn.execute(
        "INSERT INTO rank_history "
        "(keyword, asin, country, organic_position, page, "
        "is_sponsored, found, tracked_at) "
        "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
        (
            record["keyword"], record["asin"], record["country"],
            record["organic_position"], record["page"],
            int(record["is_sponsored"]), int(record["found"]),
            record["tracked_at"],
        ),
    )
    conn.commit()
    conn.close()

# Пример запуска
# asyncio.run(track_keyword("wireless earbuds", "B0DXXXXXXX", "US", 5))

Production-настройки: что нужно знать

Ежедневное расписание

Позиции на Amazon меняются ежедневно. Используйте APScheduler или cron для регулярного запуска:

# cron: каждый день в 06:00 UTC
0 6 * * * /usr/bin/python3 /path/to/amazon_tracker.py

Retry и backoff

Amazon может вернуть 503 или CAPTCHA. Стандартная стратегия — экспоненциальный backoff: 2s, 4s, 8s между попытками. Не более 3 retry на страницу. Если CAPTCHA повторяется — смените сессию (новый session ID = новый IP).

Обнаружение CAPTCHA

Amazon перенаправляет на /captcha/ или показывает страницу с заголовком "Robot Check". Проверяйте URL и контент:

def is_captcha_page(html: str, url: str) -> bool:
    if "/captcha/" in url:
        return True
    lower = html.lower()
    indicators = ["robot check", "captcha", "automated access"]
    return any(ind in lower for ind in indicators)

Проверка индексации

Если ASIN не найден ни на 5 страницах — возможно, он не индексируется по этому ключевому слову. Проверьте:

  • Существует ли карточка товара (direct URL: /dp/ASIN)
  • Доступен ли товар в выбранном marketplace
  • Не заблокирован ли ASIN

Управление concurrency

Не делайте более 10 параллельных запросов с одного IP. ProxyHat позволяет до 50 параллельных сессий на аккаунт. Используйте asyncio.Semaphore для ограничения:

sem = asyncio.Semaphore(10)  # max 10 concurrent requests

async def limited_fetch(keyword, page, country):
    async with sem:
        return await fetch_with_retry(keyword, page, country)

Сравнение: residential vs datacenter vs mobile прокси для Amazon

Тип прокси Success rate на Amazon Скорость Риск блокировки Стоимость
Residential 95–99% ~200ms Низкий Средняя
Datacenter 40–70% ~50ms Высокий Низкая
Mobile 98–99% ~300ms Очень низкий Высокая

Для track amazon rankings python оптимальный выбор — residential-прокси: баланс надёжности и стоимости. Mobile-прокси дают максимальный success rate, но стоят дороже. Datacenter-прокси не рекомендуются для Amazon — блокировки слишком часты.

Этика и легальность

Track amazon rankings python — мощный инструмент, но используйте его ответственно:

  • Отслеживайте свои и публичные листинги. Не собирайте данные конкурентов с целью недобросовестной конкуренции.
  • Соблюдайте robots.txt. Проверьте https://www.amazon.com/robots.txt перед скрапингом.
  • Троттлите запросы. Не более 1 запроса в 2–3 секунды на одну сессию. Это снижает нагрузку на серверы Amazon.
  • Рассмотрите Amazon SP-API. Для коммерческого использования у Amazon есть официальный SP-API с эндпоинтами для аналитики каталога. SP-API требует регистрации продавца и одобрения, но легален и стабилен.
  • GDPR. Если вы собираете данные в ЕС, убедитесь, что не обрабатываете персональные данные (отзывы с именами пользователей и т.д.). Подробнее — на gdpr-info.eu.

Ключевые выводы

  • Amazon SERP scraping требует residential-прокси с гео-таргетингом по стране marketplace
  • Парсинг: ищите [data-component-type="s-search-result"], читайте data-asin, отделяйте Sponsored от органики
  • Sticky-сессии (-session-id) необходимы для корректной пагинации — один IP на все страницы одного запроса
  • Production: ежедневное расписание, retry с backoff, CAPTCHA-детекция, проверка индексации
  • Этика: троттлите запросы, соблюдайте ToS, рассмотрите SP-API для коммерческого использования

Готовы начать? Изучите тарифы ProxyHat, проверьте доступные локации и прочитайте о SERP tracking и web scraping. Полная документация по подключению — на docs.proxyhat.com.

Готовы начать?

Доступ к более чем 50 млн резидентных IP в 148+ странах с AI-фильтрацией.

Смотреть ценыРезидентные прокси
← Вернуться в Блог