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-asincontiene 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:
- Iterar sobre todos los
[data-component-type="s-search-result"] - Verificar si contiene el texto "Sponsored"
- Si es Sponsored, incrementar el contador de anuncios pero no el de posición orgánica
- 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"ydata-asin. Separa Sponsored de orgánico. - Proxies residenciales obligatorios: Las IPs de datacenter son bloqueadas rápidamente. Usa
-country-USpara amazon.com,-country-DEpara amazon.de. - Sesiones estables: Usa
-session-xxxpara 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.






