---
name: scrape_amazon_bestsellers
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def scrape_amazon_bestsellers(marketplace: str = 'amazon.es', categories: list[str] | None = None, list_type: str = 'bestsellers', max_items: int = 50) -> list[dict]"
description: "Scrapea los rankings de Amazon (Best Sellers y Movers & Shakers) de un marketplace via HTTP (requests) para captar señales de demanda de productos: rank, ASIN, titulo, precio, rating, reseñas y, en movers, el cambio porcentual. Delega el parsing en el parser puro parse_amazon_ranking_html."
tags: [amazon, scraping, trends, market-intel, datascience]
uses_functions: [parse_amazon_ranking_html_py_datascience]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [requests]
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/datascience/scrape_amazon_bestsellers.py"
params:
  - name: marketplace
    desc: "Dominio Amazon objetivo (amazon.es, amazon.com, amazon.co.uk, amazon.de, ...). Determina la URL, el Accept-Language enviado y la moneda fallback."
  - name: categories
    desc: "Lista de slugs de categoria a scrapear (ej. 'electronics', 'videogames'). Si es None, scrapea la portada general del ranking elegido. Cada slug genera una pagina/peticion."
  - name: list_type
    desc: "Tipo de ranking: 'bestsellers' (URL /gp/bestsellers/<cat>) o 'movers_shakers' (URL /gp/movers-and-shakers/<cat>). Cualquier otro valor lanza ValueError."
  - name: max_items
    desc: "Numero maximo de productos recolectados por categoria. Default 50 (una pagina de ranking suele tener ~50 items)."
output: "Lista de dicts, uno por producto, con exactamente estas claves: marketplace, list_type, category, rank, asin, title, price, currency, rating, reviews, pct_change, url. None donde no haya dato. price/rating/pct_change son float; rank/reviews son int. pct_change solo se rellena en movers_shakers. Casa 1:1 con la tabla Postgres amazon_bestsellers (el ingest añade id/snapshot_date/scraped_at)."
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from datascience.scrape_amazon_bestsellers import scrape_amazon_bestsellers

# Best Sellers de electronica y videojuegos en Amazon.es
rows = scrape_amazon_bestsellers(
    marketplace="amazon.es",
    categories=["electronics", "videogames"],
    list_type="bestsellers",
    max_items=50,
)
print(len(rows), "items")
print(rows[0])
# {'marketplace': 'amazon.es', 'list_type': 'bestsellers', 'category': 'electronics',
#  'rank': 1, 'asin': 'B0...', 'title': '...', 'price': 29.99, 'currency': 'EUR',
#  'rating': 4.5, 'reviews': 1234, 'pct_change': None, 'url': 'https://www.amazon.es/dp/B0...'}

# Movers & Shakers (productos que mas suben) — incluye pct_change
movers = scrape_amazon_bestsellers(
    marketplace="amazon.com",
    list_type="movers_shakers",
    max_items=30,
)
```

## Cuando usarla

Usala cuando necesites captar señales de demanda de mercado desde Amazon: que se esta vendiendo mas (Best Sellers) o que esta subiendo de golpe en ventas (Movers & Shakers), por marketplace y categoria. Util como fuente de un pipeline de market intelligence / trend detection que luego ingesta a la tabla `amazon_bestsellers` y cruza snapshots diarios para detectar productos al alza. Llamala antes de cualquier analisis de tendencias de catalogo; el dict devuelto esta listo para insertar tras añadir `snapshot_date`/`scraped_at`.

## Gotchas

- **Anti-bot fuerte**: Amazon detecta scraping HTTP puro y puede devolver captcha, `503` o `429`. La funcion detecta el bloqueo (status 429/503 o markers de captcha en el HTML) y, tras agotar reintentos, lanza `RuntimeError` con el status. **Si HTTP puro falla repetidamente, la alternativa es el navegador del ecosistema (browser MCP / CDP)** sobre una pestaña real de Chrome, que pasa el anti-bot mejor que `requests`.
- **HTML fragil**: Amazon cambia las plantillas del DOM con frecuencia y sirve varias a la vez segun A/B test. Los selectores estan escritos defensivamente (varios fallbacks por campo) pero **pueden necesitar mantenimiento** cuando Amazon rota plantillas. Si un campo no aparece en ninguna plantilla conocida, se devuelve `None` en vez de petar.
- **Campos opcionales = None**: no todos los items traen precio/rating/reviews/pct_change. `pct_change` solo se rellena en `list_type="movers_shakers"`; en bestsellers siempre es `None`.
- **rank fallback posicional**: si Amazon no renderiza el badge de rank, se usa la posición (1-indexada) del item en la pagina como rank.
- **Una peticion por categoria**: cada slug en `categories` dispara una peticion HTTP independiente (con 2 reintentos + backoff). Listas largas de categorias multiplican el riesgo de throttling — espacia las llamadas si scrapeas muchas.
- **Moneda best-effort**: `currency` se infiere del simbolo en el precio (€, $, £, R$) y, si no hay simbolo reconocible, del TLD del marketplace. Puede ser `None` si no se pudo determinar.
