Files
fn_registry/python/functions/pipelines/monitor_freelance_projects.md
T
egutierrez 763e06c127 feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-20 18:22:23 +02:00

6.1 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 = 9222, 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 9222 (chromium-personal logueado). Usa 9333 para el Chrome aislado 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

# Requiere un Chrome con remote debugging vivo en el puerto indicado.
# Produccion (chromium-personal logueado, port 9222) con los paths por defecto:
fn run monitor_freelance_projects

# Probar contra el Chrome aislado del browser_mcp (port 9333) con 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. Produccion = 9222 (chromium-personal logueado); Chrome aislado = 9333 (browser_mcp).
  • 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).