Seguimiento de Ranking de Palabras Clave en Amazon con Proxies: Guía para Desarrolladores

Guía práctica para implementar seguimiento de ranking de palabras clave en Amazon con proxies residenciales. Incluye ejemplos en Python, curl_cffi y Playwright.

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

Aviso legal: Este artículo cubre el seguimiento de posiciones orgánicas en búsquedas públicas de Amazon. En EE. UU., la Computer Fraud and Abuse Act (CFAA) regula el acceso no autorizado a sistemas informáticos. En la UE, el Reglamento General de Protección de Datos (GDPR) aplica al procesamiento de datos personales. Rastrea únicamente listings propios o datos de búsqueda pública, respeta los Términos de Servicio de Amazon y considera la SP-API oficial cuando esté disponible.

El seguimiento de ranking de palabras clave en Amazon con proxies responde a una necesidad concreta: los vendedores necesitan saber en qué posición aparece su ASIN para cada término de búsqueda, en cada marketplace, cada día. A diferencia de Google, Amazon es un motor de búsqueda transaccional donde el ranking depende de relevancia y velocidad de ventas, no de backlinks. Sin proxies residenciales con geo-segmentación, los intentos de automatizar este seguimiento fracasan por bloqueos de IP y localización incorrecta.

Por qué el seguimiento de ranking de palabras clave en Amazon con proxies es diferente

Amazon no es Google. Su algoritmo de ranking, conocido como A9, prioriza dos señales principales: relevancia (título, bullets, descripción, categoría) y velocidad de ventas (conversion rate, ventas recientes, stock disponible). Esto significa que un producto puede aparecer en la posición 3 para "botella de agua" y en la posición 47 para "termo de acero", aunque sea el mismo ASIN.

Para los vendedores, esto crea la necesidad de un amazon keyword rank tracker que monitorice la posición orgánica de cada ASIN por palabra clave, marketplace y día. Los datos públicos de búsqueda de Amazon muestran:

  • Posición orgánica del ASIN en la página de resultados
  • Presencia de productos patrocinados (Sponsored) que desplazan resultados orgánicos
  • Variación de ranking entre marketplaces (amazon.com ≠ amazon.de ≠ amazon.co.uk)
  • Cambios diarios por velocity de ventas y competencia

Un vendedor típico rastrea entre 20 y 200 palabras clave por ASIN. Con 10 ASINs, eso son 2,000 consultas diarias mínimo — un volumen que dispara bloqueos de IP en cuestión de minutos si se hace sin proxies.

Anatomía de la página de resultados de Amazon

La página de búsqueda de Amazon tiene una estructura HTML específica que debes parsear correctamente. Cada resultado orgánico está contenido en un div con data-component-type="s-search-result". Dentro de ese contenedor:

  • data-asin contiene el identificador del producto (ej. B08N5WRWNW)
  • El texto "Sponsored" indica un anuncio pagado, no un resultado orgánico
  • El título del producto está en una etiqueta h2 a span
  • El precio puede estar en múltiples formatos según promociones activas

La posición orgánica se calcula contando solo resultados no patrocinados, de arriba hacia abajo. Un resultado "Sponsored" en la posición visual 1 no cuenta como posición orgánica 1 — el primer resultado orgánico podría estar en la posición visual 4.

Separar resultados orgánicos de Sponsored

Amazon marca los anuncios con un texto visible "Sponsored" dentro del resultado. En el HTML, aparece en un span o div pequeño. Tu parser debe:

  1. Iterar sobre todos los [data-component-type="s-search-result"]
  2. Verificar si contiene el texto "Sponsored"
  3. Si es Sponsored, incrementar el contador de anuncios pero no el de posición orgánica
  4. Si es orgánico, incrementar la posición orgánica y registrar el ASIN

Por qué necesitas proxies residenciales con geo-segmentación

Amazon aplica varias capas de protección anti-bot y localización por marketplace:

  • Geolocalización por IP: Una solicitud desde una IP alemana recibe resultados de amazon.de automáticamente. Si intentas rastrear amazon.com desde una IP europea, Amazon puede redirigirte o mostrar resultados localizados.
  • Detección de datacenter: Amazon bloquea rangos de IP conocidos de proveedores cloud (AWS, GCP, Azure). Las IPs de datacenter tienen una tasa de bloqueo significativamente mayor que las residenciales.
  • Rate limiting por IP: Demasiadas solicitudes desde la misma IP en poco tiempo disparan CAPTCHAs o bloqueos temporales.
  • Variación de resultados por ubicación: Incluso dentro de EE. UU., los resultados pueden variar ligeramente por región. Para datos consistentes, necesitas una IP del país correcto.

Los proxies residenciales de ProxyHat resuelven estos problemas. Cada solicitud sale desde una IP residencial real en el país que especifiques, con la posibilidad de mantener sesiones estables para paginación consistente.

Tipo de Proxy Detección anti-bot Geo-targeting Costo relativo Ideal para Amazon SERP
Residencial Bajo País / Ciudad Medio-Alto Sí — recomendado
Datacenter Alto Limitado Bajo No — bloqueo frecuente
Móvil Muy bajo País Alto Sí, pero costoso

Implementación práctica en Python

A continuación, implementamos un amazon keyword rank tracker completo usando Python. Empezamos con un ejemplo básico en curl y luego escalamos a curl_cffi y Playwright.

1. Solicitud básica con curl y proxy ProxyHat

curl -x "http://user-country-US-session-track01:PASSWORD@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=water+bottle&page=1"

El flag -country-US en el nombre de usuario asegura que la solicitud salga desde una IP residencial en EE. UU. El flag -session-track01 mantiene la misma IP para todas las solicitudes de esa sesión, lo que es crítico para paginación consistente.

2. Fetch con curl_cffi y proxy residencial

curl_cffi imita las huellas TLS de navegadores reales, lo que reduce la detección comparado con requests estándar:

from curl_cffi import requests
from typing import Optional

PROXY_URL = 'http://user-country-US-session-track01:PASSWORD@gate.proxyhat.com:8080'

def fetch_amazon_page(keyword: str, page: int = 1, marketplace: str = 'com') -> str:
    """Fetch Amazon search results page HTML."""
    base_url = f'https://www.amazon.{marketplace}/s'
    params = {'k': keyword, 'page': str(page)}
    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',
    }
    try:
        response = requests.get(
            base_url,
            params=params,
            headers=headers,
            proxies={'http': PROXY_URL, 'https': PROXY_URL},
            impersonate='chrome120',
            timeout=30,
        )
        response.raise_for_status()
        return response.text
    except requests.RequestException as e:
        print(f'Error fetching page {page}: {e}')
        return ''

html = fetch_amazon_page('water bottle', page=1, marketplace='com')
print(f'Page length: {len(html)} chars')

3. Parser de resultados de Amazon SERP

from bs4 import BeautifulSoup
from dataclasses import dataclass

@dataclass
class SearchResult:
    asin: str
    position: int  # organic position (1-indexed)
    is_sponsored: bool
    title: str

def parse_amazon_serp(html: str, target_asin: str) -> Optional[SearchResult]:
    """Parse Amazon SERP and find target ASIN position."""
    soup = BeautifulSoup(html, 'html.parser')
    results = soup.select('[data-component-type="s-search-result"]')
    
    organic_position = 0
    for result in results:
        asin = result.get('data-asin', '')
        if not asin:
            continue
        
        # Detect Sponsored placement
        sponsored_text = result.find(string=lambda t: 'Sponsored' in t if t else False)
        is_sponsored = sponsored_text is not None
        
        if not is_sponsored:
            organic_position += 1
        
        title_elem = result.select_one('h2 a span')
        title = title_elem.get_text(strip=True) if title_elem else ''
        
        if asin == target_asin:
            return SearchResult(
                asin=asin,
                position=organic_position if not is_sponsored else -1,
                is_sponsored=is_sponsored,
                title=title,
            )
    
    return None  # ASIN not found on this page

# Example usage
result = parse_amazon_serp(html, target_asin='B08N5WRWNW')
if result:
    if result.is_sponsored:
        print(f'ASIN {result.asin} is Sponsored (not organic)')
    else:
        print(f'ASIN {result.asin} at organic position {result.position}')
else:
    print('ASIN not found on this page')

4. Tracking multi-página con Playwright y proxy

Para páginas que requieren renderizado JavaScript o cuando curl_cffi no es suficiente, Playwright con proxy ofrece una alternativa robusta:

import asyncio
from playwright.async_api import async_playwright

async def track_keyword_ranking(
    keyword: str,
    target_asin: str,
    max_pages: int = 5,
    marketplace: str = 'com',
) -> dict:
    """Track ASIN ranking across multiple pages using Playwright."""
    proxy_config = {
        'server': 'http://gate.proxyhat.com:8080',
        'username': 'user-country-US-session-track01',
        'password': 'PASSWORD',
    }
    
    positions = []
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            proxy=proxy_config,
            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',
        )
        page = await context.new_page()
        
        for pg in range(1, max_pages + 1):
            url = f'https://www.amazon.{marketplace}/s?k={keyword.replace(" ", "+")}&page={pg}'
            try:
                await page.goto(url, wait_until='domcontentloaded', timeout=30000)
                html = await page.content()
                
                result = parse_amazon_serp(html, target_asin)
                if result and not result.is_sponsored:
                    positions.append({'page': pg, 'position': result.position})
                    break  # Found, no need to check more pages
                elif result and result.is_sponsored:
                    positions.append({'page': pg, 'position': -1, 'sponsored': True})
                    
            except Exception as e:
                print(f'Error on page {pg}: {e}')
                continue
        
        await browser.close()
    
    return {
        'keyword': keyword,
        'asin': target_asin,
        'marketplace': marketplace,
        'positions': positions,
        'found': len(positions) > 0,
    }

# Run tracker
result = asyncio.run(track_keyword_ranking(
    keyword='water bottle',
    target_asin='B08N5WRWNW',
    max_pages=5,
    marketplace='com',
))
print(result)

5. Almacenamiento de historial de posiciones en SQLite

import sqlite3
from datetime import datetime

def init_db(db_path: str = 'rankings.db') -> sqlite3.Connection:
    conn = sqlite3.connect(db_path)
    conn.execute('''
        CREATE TABLE IF NOT EXISTS rank_history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            asin TEXT NOT NULL,
            keyword TEXT NOT NULL,
            marketplace TEXT NOT NULL,
            organic_position INTEGER,
            is_sponsored INTEGER DEFAULT 0,
            tracked_at TEXT NOT NULL,
            UNIQUE(asin, keyword, marketplace, tracked_at)
        )
    ''')
    conn.commit()
    return conn

def save_ranking(
    conn: sqlite3.Connection,
    asin: str,
    keyword: str,
    marketplace: str,
    position: int,
    is_sponsored: bool = False,
) -> None:
    tracked_at = datetime.now().isoformat()
    conn.execute(
        '''INSERT OR REPLACE INTO rank_history
           (asin, keyword, marketplace, organic_position, is_sponsored, tracked_at)
           VALUES (?, ?, ?, ?, ?, ?)''',
        (asin, keyword, marketplace, position, int(is_sponsored), tracked_at),
    )
    conn.commit()

def get_rank_history(
    conn: sqlite3.Connection,
    asin: str,
    keyword: str,
    marketplace: str,
    days: int = 30,
) -> list:
    cursor = conn.execute(
        '''SELECT tracked_at, organic_position, is_sponsored
           FROM rank_history
           WHERE asin = ? AND keyword = ? AND marketplace = ?
           ORDER BY tracked_at DESC LIMIT ?''',
        (asin, keyword, marketplace, days),
    )
    return cursor.fetchall()

# Usage
conn = init_db()
save_ranking(conn, 'B08N5WRWNW', 'water bottle', 'com', 3)
save_ranking(conn, 'B08N5WRWNW', 'water bottle', 'com', 2)  # next day
history = get_rank_history(conn, 'B08N5WRWNW', 'water bottle', 'com', 30)
for row in history:
    print(f'Date: {row[0]}, Position: {row[1]}, Sponsored: {bool(row[2])}')

6. Reintentos con backoff exponencial

import time
import random
from functools import wraps

def retry_with_backoff(max_retries: int = 3, base_delay: float = 2.0):
    """Decorator for exponential backoff retries."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
                    print(f'Attempt {attempt + 1} failed: {e}. Retrying in {delay:.1f}s')
                    time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry_with_backoff(max_retries=3, base_delay=2.0)
def fetch_with_retry(keyword: str, page: int = 1) -> str:
    """Fetch Amazon page with automatic retries."""
    html = fetch_amazon_page(keyword, page=page)
    if not html or len(html) < 5000:
        raise ValueError('Empty or suspiciously short response — possible block')
    if 'Type the characters you see in this image' in html:
        raise ValueError('CAPTCHA detected — consider rotating session ID')
    return html

7. Programación diaria con APScheduler

from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

def daily_ranking_job():
    """Daily job to track all keyword/ASIN pairs."""
    keywords_to_track = [
        ('water bottle', 'B08N5WRWNW', 'com'),
        ('stainless steel bottle', 'B08N5WRWNW', 'com'),
        ('wasserflasche', 'B08N5WRWNW', 'de'),
    ]
    conn = init_db()
    
    for keyword, asin, marketplace in keywords_to_track:
        try:
            html = fetch_with_retry(keyword, page=1)
            result = parse_amazon_serp(html, asin)
            if result:
                save_ranking(conn, asin, keyword, marketplace,
                           result.position, result.is_sponsored)
                print(f'[{marketplace}] {keyword}: pos {result.position}')
            else:
                save_ranking(conn, asin, keyword, marketplace, -1)
                print(f'[{marketplace}] {keyword}: not in top results')
        except Exception as e:
            print(f'Error tracking {keyword}: {e}')
        time.sleep(5)  # Throttle between keywords
    
    conn.close()

scheduler = BlockingScheduler()
scheduler.add_job(
    daily_ranking_job,
    CronTrigger(hour=9, minute=0),  # Daily at 9 AM
n    id='daily_ranking',
)
print('Scheduler started. Press Ctrl+C to exit.')
scheduler.start()

Consejos de producción

Detección de CAPTCHA

Amazon muestra CAPTCHAs cuando detecta comportamiento automatizado. Señales a monitorear:

  • Respuestas HTML menores a 5,000 caracteres — probablemente un bloqueo o CAPTCHA
  • Presencia del texto "Type the characters you see in this image" en el HTML
  • Redirecciones a /errors/validateCaptcha
  • Código de estado 503 repetido

Cuando detectes un CAPTCHA, rota el identificador de sesión (-session-xxx) para obtener una nueva IP residencial. Si el problema persiste, aumenta el delay entre solicitudes a 10-15 segundos.

Verificación de indexación

Antes de rastrear posiciones, verifica si el ASIN está indexado en absoluto. Un ASIN que no aparece en las primeras 5 páginas (posiciones 1-75) puede estar:

  • No indexado para esa palabra clave
  • Temporalmente fuera de stock
  • Suspendido por Amazon

Registra position = -1 para ASINs no encontrados, y configura alertas cuando un ASIN que solía rankear desaparece por más de 3 días consecutivos.

Geo-segmentación por marketplace

Cada marketplace de Amazon requiere una IP del país correspondiente:

Marketplace Dominio Flag ProxyHat
EE. UU. amazon.com -country-US
Alemania amazon.de -country-DE
Reino Unido amazon.co.uk -country-GB
Francia amazon.fr -country-FR
España amazon.es -country-ES

Consulta todas las ubicaciones disponibles en nuestra página de ubicaciones de ProxyHat.

Concurrencia controlada

Para escalar el seguimiento a cientos de palabras clave, usa concurrencia limitada. Con asyncio.Semaphore puedes controlar cuántas solicitudes se ejecutan en paralelo:

import asyncio

MAX_CONCURRENT = 5  # Limit concurrent requests

async def track_multiple_keywords(keywords: list, asin: str, marketplace: str):
    semaphore = asyncio.Semaphore(MAX_CONCURRENT)
    
    async def track_one(keyword: str):
        async with semaphore:
            result = await track_keyword_ranking(keyword, asin, marketplace=marketplace)
            await asyncio.sleep(3)  # Throttle
            return result
    
    tasks = [track_one(kw) for kw in keywords]
    return await asyncio.gather(*tasks, return_exceptions=True)

Con 5 solicitudes concurrentes y un delay de 3 segundos entre cada una, puedes rastrear 200 palabras clave en aproximadamente 2 minutos sin disparar bloqueos.

Consideraciones éticas y legales

El amazon serp scraping opera en una zona gris. Aquí hay principios para mantener tu práctica dentro de límites razonables:

  • Rastrea listings propios o públicos: Los datos de búsqueda de Amazon son públicos. Rastrear tus propios ASINs es claramente legítimo. Rastrear ASINs de competidores para análisis de mercado es común pero debe hacerse con moderación.
  • Respeta los Términos de Servicio: Los TOS de Amazon prohíben el scraping automatizado. La SP-API es la alternativa oficial para vendedores registrados, aunque tiene limitaciones de cuota y no cubre todos los casos de uso de tracking de ranking.
  • Throttlea tus solicitudes: Mantén un rate de 1-2 solicitudes por segundo por sesión. Usa delays aleatorios (jitter) para evitar patrones detectables.
  • Cumple con GDPR: Si almacenas datos que podrían identificar a vendedores individuales (especialmente en marketplaces europeos), asegúrate de tener una base legal y minimizar la retención de datos.
  • Considera la SP-API: Para vendedores con cuenta profesional, la SP-API de Amazon ofrece acceso autorizado a datos de catálogo y reportes. Sin embargo, no proporciona datos de ranking orgánico por palabra clave, lo que hace que el scraping siga siendo necesario para este caso de uso.

Para más información sobre casos de uso de scraping ético, consulta nuestra guía de web scraping con proxies y SERP tracking.

Puntos clave

El seguimiento de ranking en Amazon requiere un enfoque diferente al de Google: el algoritmo A9 prioriza velocidad de ventas y relevancia, no backlinks. Cada marketplace necesita su propia IP geolocalizada, y los proxies residenciales son la única opción confiable para evitar bloqueos anti-bot.

  • Amazon ≠ Google SERP: El ranking depende de relevancia y velocity de ventas. Rastrea por ASIN, keyword y marketplace.
  • Parsea con precisión: Usa data-component-type="s-search-result" y data-asin. Separa Sponsored de orgánico.
  • Proxies residenciales obligatorios: Las IPs de datacenter son bloqueadas rápidamente. Usa -country-US para amazon.com, -country-DE para amazon.de.
  • Sesiones estables: Usa -session-xxx para paginación consistente. Rota sesiones solo cuando detectes CAPTCHAs.
  • Producción: Programa tracking diario, implementa reintentos con backoff, detecta CAPTCHAs, y registra ASINs no indexados como position = -1.
  • Ética: Rastrea listings propios o públicos, throttlea, y considera la SP-API donde aplique.

¿Listo para empezar? Revisa los planes de ProxyHat y la documentación oficial para más detalles sobre configuración.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog