--- name: parse_amazon_ranking_html kind: function lang: py domain: datascience version: "1.0.0" purity: pure signature: "def parse_amazon_ranking_html(html: str, marketplace: str = 'amazon.es', list_type: str = 'bestsellers', max_items: int = 50) -> list[dict]" description: "Parser PURO de HTML de rankings Amazon (Best Sellers y Movers & Shakers): recibe el HTML de la pagina (de requests o de outerHTML renderizado por CDP) y devuelve una lista de productos (rank, ASIN, titulo, precio, rating, reseñas, pct_change). Nucleo compartido por el scraper HTTP y el scraper CDP." tags: [amazon, scraping, parser, market-intel, datascience, dropship] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [bs4] tested: true tests: ["test_parsea_dos_cards_con_todos_los_campos", "test_contrato_de_claves_exacto", "test_pct_change_solo_en_movers_shakers", "test_html_vacio_devuelve_lista_vacia", "test_max_items_limita_resultados"] test_file_path: "python/functions/datascience/parse_amazon_ranking_html_test.py" file_path: "python/functions/datascience/parse_amazon_ranking_html.py" params: - name: html desc: "HTML crudo de una pagina de ranking Amazon, o el outerHTML del contenedor del grid (.p13n-desktop-grid) renderizado via CDP. Puede ser el documento entero o solo el grid." - name: marketplace desc: "Dominio Amazon (amazon.es, amazon.com, ...). Se usa para construir URLs absolutas de producto y para inferir la moneda fallback cuando el precio no trae simbolo." - name: list_type desc: "'bestsellers' o 'movers_shakers'. Solo afecta a si se parsea pct_change (movers) o se fuerza a None (bestsellers)." - name: max_items desc: "Numero maximo de productos devueltos. Default 50." output: "Lista de dicts, uno por producto, con exactamente estas claves: marketplace, list_type, category (siempre None aqui — lo rellena el caller que conoce la URL), 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. HTML vacio o sin cards -> lista vacia (nunca lanza)." --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from datascience.parse_amazon_ranking_html import parse_amazon_ranking_html # `html` puede venir de requests.get(...).text o de un outerHTML renderizado por CDP. html = open("/tmp/amazon_grid.html").read() rows = parse_amazon_ranking_html(html, marketplace="amazon.es", list_type="movers_shakers", max_items=30) print(len(rows), "productos") print(rows[0]) # {'marketplace': 'amazon.es', 'list_type': 'movers_shakers', 'category': None, # 'rank': 1, 'asin': 'B0...', 'title': '...', 'price': 13.95, 'currency': 'EUR', # 'rating': 4.0, 'reviews': 666, 'pct_change': 150.0, 'url': 'https://www.amazon.es/dp/B0...'} ``` ## Cuando usarla Usala cuando ya tengas el HTML de una pagina de ranking de Amazon (Best Sellers o Movers & Shakers) y quieras extraer los productos sin volver a escribir selectores DOM. Es el bloque de parsing reutilizable: la usan tanto `scrape_amazon_bestsellers` (fetch HTTP con requests) como `scrape_amazon_movers_cdp` (fetch renderizado via Chrome DevTools Protocol). Si construyes otro fetcher (proxy, browser MCP, HAR replay), pasale el HTML aqui en vez de duplicar el parser. ## Notas - **Funcion pura**: sin red ni I/O; para un HTML fijo devuelve siempre lo mismo. Por eso es testeable con fixtures y compartible entre estrategias de fetch. - **Plantillas multiples**: Amazon sirve varias plantillas DOM a la vez (A/B test) y las rota. Cada campo usa varios selectores fallback; un campo que ninguna plantilla conocida expone se devuelve `None` en vez de petar. - **Seleccion de cards**: prioriza el wrapper del grid (`div[id="gridItemRoot"]`) sobre el faceout interno, porque el badge de rank (`span.zg-bdg-text`) es hermano del faceout DENTRO del wrapper — seleccionar el faceout solo perderia el rank. - **pct_change defensivo**: apunta solo al badge de subida de ranking de movers (`.zg-percent-change` y variantes), NO al `%` generico de descuento/ahorro (`apex-savings-percent`) de cards de oferta, que daria un pct_change falso. - **category = None**: el parser no conoce la URL, asi que deja `category` en `None`; el caller (que si sabe que categoria pidio) lo rellena. - **rank fallback posicional**: si Amazon no renderiza el badge de rank, se usa la posicion (1-indexada) del item en el grid.