feat(browser): auto-commit con 178 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:22:23 +02:00
parent 7d100e7f3e
commit 763e06c127
178 changed files with 19917 additions and 317 deletions
@@ -0,0 +1,61 @@
---
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.