---
name: scrape_aliexpress_cdp
kind: function
lang: py
domain: browser
version: "1.0.0"
purity: impure
signature: "def scrape_aliexpress_cdp(query: str, sort: str = 'total_tranpro_desc', limit: int = 40, port: int = 9222, timeout_s: float = 25.0) -> dict"
description: "Scrapea productos de AliExpress por Chrome DevTools Protocol (CDP) sobre el navegador diario logueado (chromium-personal, puerto 9222, IP residencial), evitando el captcha que bloquea el scraper HTTP. Capta coste en China (EUR) y numero de pedidos (demanda real) como senal de dropshipping: que importar de China. Ordena por defecto por numero de pedidos."
tags: [market-intel, aliexpress, cdp, dropship, scraper, browser]
uses_functions: [cdp_eval_py_browser]
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: [json, os, re, sys, time, datetime, browser.cdp_eval]
params:
  - name: query
    desc: "Termino de busqueda (ej. 'organizador maletero coche'). Los espacios se convierten en guiones para la URL de busqueda."
  - name: sort
    desc: "Orden de resultados. 'total_tranpro_desc' = por numero de pedidos (demanda real, default util para dropshipping). Otros: 'default', 'price_asc', 'price_desc'."
  - name: limit
    desc: "Numero objetivo de productos a recolectar. El scroll itera (cap de 8 scrolls) hasta acercarse a este valor o hasta que el conteo de cards deja de crecer."
  - name: port
    desc: "Puerto de remote debugging de Chrome. Default 9222 (chromium-personal)."
  - name: timeout_s
    desc: "Timeout en segundos para cada evaluacion CDP. Default 25.0."
output: "dict autosuficiente {status: 'ok'|'error'|'captcha', source:'aliexpress', query, url, count, products:[...]}. Cada product: item_id(str), url(str), title(str), price(float EUR|None), price_orig(float|None), rating(float|None), orders(str crudo p.ej. '10.000+ vendidos'|None), orders_num(int aprox), ship_from(str|None), scraped_at(iso). Nunca inventa datos: sin cards -> status='error' products=[]; captcha -> status='captcha' products=[]. Nunca lanza."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/scrape_aliexpress_cdp.py"
---

## Ejemplo

```bash
# Requiere chromium-personal con remote debugging en 9222 y sesion logueada.
cd "$HOME/fn_registry"
python/.venv/bin/python3 python/functions/browser/scrape_aliexpress_cdp.py "organizador maletero coche" "total_tranpro_desc" 40
```

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

res = scrape_aliexpress_cdp("organizador maletero coche", sort="total_tranpro_desc", limit=40)
print(res["status"], res["count"])
for p in res["products"][:3]:
    print(p["price"], "EUR -", p["orders_num"], "pedidos -", p["title"][:50])
# ok 7
# 14.49 EUR - 10000 pedidos - Caja organizadora de maletero de coche, gran capac...
# 56.87 EUR - 5000 pedidos - YZ para Tesla Model Y Juniper 2021-2026, caja de al...
```

## Cuando usarla

Cuando necesites el **coste en China + la demanda (numero de pedidos)** de un producto
para decidir que importar (market intelligence de dropshipping, proyecto
`captacion_clientes`). Usala en lugar de `scrape_aliexpress_trending_py_datascience`
cuando ese scraper HTTP devuelva captcha: esta via opera el navegador diario logueado
con IP residencial y no dispara el muro anti-bot. La persistencia (DuckDB/Postgres/Excel)
la hace un componente aparte: el dict de salida es autosuficiente y no casa con ninguna tabla.

## Gotchas

- **Impura, depende del navegador diario**: requiere `chromium-personal` corriendo con
  `--remote-debugging-port=9222` y la sesion de AliExpress logueada (IP residencial). Sin
  CDP vivo, `cdp_eval` devuelve `ok=False` y la funcion retorna `status='error'`.
- **Pisa la pestana activa de AliExpress**: navega via `location.href` sobre el primer
  target `page` cuya URL contenga "aliexpress" (o el primer page si no hay). Si tienes una
  pestana de AliExpress con trabajo en curso, la reemplaza.
- **Volumen real bajo por pagina**: la galeria `/w/wholesale-...` suele exponer solo
  ~7-12 cards reales (el resto son banners promocionales "GRATIScon una compra" sin precio,
  que se descartan). `count` reflejara los productos reales disponibles en la pagina, no
  siempre llegara a `limit`. Para mas volumen hay que paginar (`&page=2`), fuera del alcance
  de esta funcion.
- **Fragil ante cambios de HTML de AliExpress**: depende del selector
  `.search-item-card-wrapper-gallery` y del formato del texto de la card
  (`14,49€32,2€ -55%4.610.000+ vendidos`). Si AliExpress cambia el markup, la extraccion
  devolvera campos None o `status='error'` (no inventa datos).
- **Lee `textContent`, no `innerText`**: las cards fuera del viewport devuelven `innerText`
  vacio; por eso se usa `textContent` normalizado. El texto viene pegado sin saltos de
  linea y los regex no dependen de `\n`.
- **Captcha posible**: si AliExpress muestra el slider "nc" / punish page, la funcion lo
  detecta y devuelve `status='captcha'` sin intentar resolverlo. Reaccion correcta:
  handoff humano (activar la pestana y resolver a mano).
- `orders_num` es aproximado: `'10.000+'` -> 10000, `'5.000+'` -> 5000, `'1.234'` -> 1234
  (quita puntos de millar y el `+`). El `+` significa "al menos ese numero".
