e1e9bb7499
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.8 KiB
5.8 KiB
name, kind, lang, domain, version, purity, signature, description, tags, params, output, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path
| name | kind | lang | domain | version | purity | signature | description | tags | params | output | uses_functions | uses_types | returns | returns_optional | error_type | imports | tested | tests | test_file_path | file_path | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| scrape_competitor_prices | function | py | datascience | 1.0.0 | impure | def scrape_competitor_prices(targets: list[dict]) -> list[dict] | Vigila precios de la competencia: dada una lista de objetivos (URL de producto + competidor), hace GET con headers realistas (timeout + 1 reintento) y extrae el precio actual de cada pagina con una cascada de estrategias (CSS selector, JSON-LD offers, meta tags, heuristica de clases). Normaliza a float (tolera coma/punto, simbolos, miles) y detecta in_stock. Devuelve una fila por target con claves 1:1 de la tabla Postgres competitor_prices; si falla un target devuelve price=None sin abortar los demas. |
|
|
Lista de dicts, una fila por target, con EXACTAMENTE estas claves (casan 1:1 con la tabla Postgres competitor_prices, sin id/snapshot_date/scraped_at): competitor (str), product_key (str), product_name (str), url (str), price (float | None), currency (str), in_stock (bool | None). price=None si no se pudo extraer; in_stock=None si la pagina fallo. | false | error_go_core |
|
false | python/functions/datascience/scrape_competitor_prices.py |
Ejemplo
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from datascience.scrape_competitor_prices import scrape_competitor_prices
targets = [
{
"competitor": "books-to-scrape",
"product_key": "light-in-the-attic",
"product_name": "A Light in the Attic",
"url": "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
"price_selector": "p.price_color", # el selector por target es lo mas fiable
"currency": "GBP",
},
{
"competitor": "competidor_b",
"product_key": "SKU-4242",
"product_name": "Filtro de aceite XYZ",
"url": "https://www.ejemplo-tienda.com/producto/4242",
# sin price_selector -> autodeteccion JSON-LD / meta / heuristica de clases
"currency": "EUR",
},
]
rows = scrape_competitor_prices(targets)
# rows[0] -> {"competitor": "books-to-scrape", "product_key": "light-in-the-attic",
# "product_name": "A Light in the Attic", "url": "...",
# "price": 51.77, "currency": "GBP", "in_stock": True}
# Listo para INSERT en la tabla competitor_prices (anade tu snapshot_date/scraped_at).
Cuando usarla
Cuando necesites un snapshot puntual del precio de uno o varios productos de la competencia para alimentar una tabla de market intelligence (competitor_prices). Util en un cron/pipeline que lee una lista de objetivos, scrapea, y persiste una fila por producto. Pasa price_selector por target siempre que conozcas el sitio: es la via mas robusta. Si no lo pasas, la funcion intenta autodetectar (JSON-LD offers.price, meta tags de precio, clases comunes de e-commerce). Las filas salen con las claves exactas de la tabla destino, asi que el caller solo anade snapshot_date/scraped_at antes del INSERT.
Gotchas
- Funcion impura: hace I/O de red (HTTP GET). Depende del HTML real de cada sitio en el momento de la llamada.
- El scraping de precios es muy especifico por sitio. Sin
price_selector, la autodeteccion acierta en muchos e-commerce estandar (los que exponen JSON-LDProduct/Offer, metaog:price:amount/itemprop=price, o clases tipicas.price), pero falla en SPAs / paginas JS-rendered (React/Vue/Angular que pintan el precio tras cargar) y en sitios con anti-bot (Cloudflare, captchas, fingerprinting). Para esos casos el GET devuelve un HTML sin el precio o un challenge, y la fila sale conprice=None. - Para sitios JS-rendered o con anti-bot usa el navegador del ecosistema (browser MCP / CDP:
page_perceive,cdp_get_text,cdp_perceive_outline) para renderizar la pagina y extraer el precio del DOM ya pintado, en lugar de esta funcion de HTTP puro. Esta funcion es para HTML servidor-renderizado. price_selectorpor target es lo mas fiable: evita depender de la heuristica y sobrevive mejor a cambios de plantilla. Define uno por competidor en tu lista de objetivos.- Normalizacion de precio: tolera
1.299,99 €(europeo: punto miles, coma decimal),$1,299.99(US),29,90,1299.99. Heuristica: el separador mas a la derecha es el decimal cuando hay ambos; con solo coma, se trata como decimal si quedan 2 digitos detras, si no como miles. Casos exoticos (3 decimales, formatos regionales raros) pueden malinterpretarse — verifica conprice_selectorapuntando al nodo limpio. in_stockes heuristico:Truesalvo que el texto de la pagina contenga marcadores de agotado (agotado,sin stock,out of stock,sold out, etc.). Falsos positivos/negativos posibles si el sitio usa otra redaccion o muestra esos terminos en contexto no relacionado.Nonesi la pagina fallo al cargar.- Tolerancia a fallos por target: si un target peta (red, timeout, HTML invalido), su fila sale con
price=None/in_stock=Noney el resto del batch continua. Nunca aborta toda la lista por un fallo individual. - Reintento unico: cada GET reintenta una vez ante error de transporte. No hay backoff exponencial ni rotacion de proxies/User-Agent; para scraping a escala o contra anti-bot fuerte, eso queda fuera del alcance de esta funcion.