Files
fn_registry/python/functions/pipelines/monitor_freelance_projects.md
T
egutierrez bcc1fe1738 feat(captacion_clientes): scraping freelance en perfil headless dedicado, no chromium-personal
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>
2026-06-22 20:11:26 +02:00

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).
market-intel
recon
launcher
pipelines
freelance
workana
upwork
duckdb
excel
scrape_workana_projects_py_browser
scrape_upwork_projects_py_browser
duckdb_execute_py_infra
duckdb_upsert_py_infra
duckdb_query_readonly_py_infra
write_xlsx_sheets_py_infra
false error_go_core
false
python/functions/pipelines/monitor_freelance_projects.py
name desc
category Categoria de Workana (segmento ?category= de la URL de listado). Default 'it-programming'.
name desc
language Idioma de los proyectos de Workana (?language=). Default 'es'.
name desc
query Query libre aplicada a ambas fuentes. En Workana va como extra_query; en Upwork sobrescribe upwork_query si no esta vacia.
name desc
pages Numero de paginas de listado a recorrer por fuente. Default 1.
name desc
include_upwork Si True, scrapea Upwork ademas de Workana. Default False (selectores Upwork sin validar en vivo + requiere login); si Upwork falla, el pipeline sigue solo con Workana.
name desc
upwork_query Query para Upwork cuando include_upwork. Default 'custom software'. El param 'query' lo sobrescribe si se pasa.
name desc
duckdb_path Ruta del archivo DuckDB de persistencia. Si vacia, usa ~/.fn_freelance/freelance.duckdb (crea el directorio).
name desc
xlsx_path Ruta del .xlsx de salida. Si vacia, usa ~/.fn_freelance/freelance_projects.xlsx (crea el directorio). Se sobrescribe en cada corrida.
name desc
port Puerto de remote debugging del Chrome que usan los scrapers (CDP). Default 9334 (perfil headless dedicado del scraping). NUNCA 9222 por defecto: ese es el chromium-personal del usuario. Para la corrida programada usa el wrapper monitor_freelance_projects_headless (levanta el Chrome headless en 9334 y lo cierra). 9333 = Chrome aislado interactivo del browser_mcp.
name desc
timeout_s Timeout en segundos por pagina para los scrapers (navegacion + espera de cards). Default 25.0.
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 devuelve status:'error' con el detalle. Por defecto 9334 (perfil headless dedicado, lo levanta/cierra monitor_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 devuelve status:'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_at lo posee la DB: el upsert usa ownership selectivo (no esta en update_cols), asi que una re-corrida conserva la primera vez que se vio cada proyecto. new_count cuenta solo urls que no existian antes de esta corrida.
  • Rate-limit / anti-bot: scrapear muchas paginas seguidas puede disparar defensas de las plataformas. Mantener pages bajo 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 proyectos is_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_db sigue en 9 (no duplica) y first_seen_at preservado (ownership del upsert por url).