El monitor de captación scrapeaba Workana sobre el navegador personal del usuario (chromium-personal, CDP 9222), interfiriendo con su navegación. El scraping CDP debe correr siempre en un perfil headless dedicado. - Nuevo pipeline monitor_freelance_projects_headless: levanta un Chromium headless aislado con perfil dedicado (~/.config/fn_scrape_chrome, CDP 9334) vía systemd-run, ejecuta monitor_freelance_projects contra ese puerto y cierra la instancia al terminar (finally). Reutiliza el patrón de lifecycle de ingest_market_trends_headless. Reutiliza un CDP vivo si el puerto ya responde (no cierra lo ajeno). - scrape_workana_projects y monitor_freelance_projects: default de `port` cambiado de 9222 (chromium-personal) a 9334 (perfil dedicado). Default seguro: correr a pelo sin Chrome en 9334 falla limpio, no contamina el 9222 personal. Verificado: el wrapper arranca headless en 9334, scrapea 8 proyectos reales de Workana, cierra la instancia (9334 muerto, sin proceso colgado) y deja el 9222 personal intacto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.6 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, params, output
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | tested | tests | test_file_path | file_path | params | output | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| monitor_freelance_projects | pipeline | py | pipelines | 1.0.0 | impure | def monitor_freelance_projects(category: str = 'it-programming', language: str = 'es', query: str = '', pages: int = 1, include_upwork: bool = False, upwork_query: str = 'custom software', duckdb_path: str = '', xlsx_path: str = '', port: int = 9334, timeout_s: float = 25.0) -> dict | Monitor de captacion de clientes freelance: scrapea proyectos nuevos de Workana (+ Upwork opcional) via CDP, los persiste en DuckDB con dedup por url, marca los de software a medida y exporta a Excel (hojas Nuevos y Todos). |
|
|
false | error_go_core | false | python/functions/pipelines/monitor_freelance_projects.py |
|
dict. En exito: {status:'ok', new_count:int (proyectos nuevos de esta corrida), total_in_db:int, new_projects:[...], xlsx_path:'<abs>', duckdb_path:'<abs>', sources:{workana:{count,status}, upwork:{count,status}|'skipped'}}. En error (sin lanzar): {status:'error', error:str, sources:{...}}. |
Ejemplo
# Para la corrida programada usa el wrapper headless (levanta Chrome en 9334 y lo
# cierra): fn run monitor_freelance_projects_headless. Este pipeline asume que YA hay
# un Chrome con remote debugging vivo en `port`.
# Contra el perfil headless dedicado (port 9334 por defecto), paths por defecto:
fn run monitor_freelance_projects
# Probar contra el Chrome aislado interactivo del browser_mcp (port 9333), paths efimeros:
fn run monitor_freelance_projects --port 9333 \
--duckdb-path /tmp/freelance.duckdb --xlsx-path /tmp/freelance.xlsx
import os, sys
sys.path.insert(0, os.path.join("python", "functions"))
from pipelines.monitor_freelance_projects import monitor_freelance_projects
out = monitor_freelance_projects(
category="it-programming",
language="es",
pages=1,
port=9222, # chromium-personal logueado
)
print(out["new_count"], "proyectos nuevos;", out["total_in_db"], "en la DB")
print("Excel:", out["xlsx_path"])
Cuando usarla
Monitor de captacion de clientes: detecta proyectos freelance NUEVOS de Workana
(programacion / software a medida) y los deja en DuckDB + Excel para revisar de un
vistazo. Resalta los que pintan a "software a medida" (is_custom_software) sin
filtrar el resto. Idempotente por url: re-correrlo no duplica ni pisa el
first_seen_at. Agendable con dag_engine (step function:) para una foto diaria de
oportunidades nuevas.
Gotchas
- Requiere un Chrome con CDP vivo en
port: los scrapers (Workana/Upwork son SPAs) renderizan via Chrome DevTools Protocol. Sin remote debugging escuchando en ese puerto el pipeline devuelvestatus:'error'con el detalle. Por defecto 9334 (perfil headless dedicado, lo levanta/cierramonitor_freelance_projects_headless). NO usa 9222 (chromium-personal del usuario) por defecto. 9333 = browser_mcp para smoke interactivo. - Upwork OFF por defecto: sus selectores no estan validados en vivo (sin sesion
Upwork). Con
include_upwork=True, si Upwork devuelvestatus:'error'el pipeline loguea un WARN a stderr y sigue solo con Workana — nunca aborta por Upwork. - El Excel se sobrescribe por completo en cada corrida (
write_xlsx_sheets). La fuente de verdad acumulativa es la DuckDB, no el .xlsx. first_seen_atlo posee la DB: el upsert usa ownership selectivo (no esta enupdate_cols), asi que una re-corrida conserva la primera vez que se vio cada proyecto.new_countcuenta solo urls que no existian antes de esta corrida.- Rate-limit / anti-bot: scrapear muchas paginas seguidas puede disparar
defensas de las plataformas. Mantener
pagesbajo y espaciar las corridas. - Skills se guardan como
skills_json(TEXT con JSON) porque DuckDB no usa una columna lista aqui; en el Excel se re-expanden a una cadena separada por comas.
Notas
Pipeline impuro: compone seis funciones del registry sin reescribir su logica
(2 scrapers CDP del dominio browser + 3 primitivas del grupo duckdb + el exporter
write_xlsx_sheets). El flag is_custom_software se calcula con la constante
CUSTOM_SW_KEYWORDS (keywords fuertes de desarrollo a medida) sobre title + snippet
- skills, normalizados a minusculas y sin acentos.
Validado end-to-end contra Workana real (CDP 9333) el 17/06/2026:
- Golden:
new_count=9,total_in_db=9, 4 proyectosis_custom_software=True, .xlsx con hojas "Nuevos" (9 filas + cabecera) y "Todos", DuckDB con 9 filas. - Edge dedup: 2a corrida identica ->
new_count=0,total_in_dbsigue en 9 (no duplica) yfirst_seen_atpreservado (ownership del upsert porurl).