feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: scrape_workana_projects
|
||||
kind: function
|
||||
lang: py
|
||||
domain: browser
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def scrape_workana_projects(category: str = 'it-programming', language: str = 'es', extra_query: str = '', pages: int = 1, port: int = 9222, timeout_s: float = 20.0) -> dict"
|
||||
description: "Scraper de proyectos freelance de Workana (https://www.workana.com/jobs) via Chrome DevTools Protocol (CDP). Workana es una SPA Vue: el GET HTTP NO trae los proyectos (0 cards en el HTML inicial), hay que renderizar con JS. Navega con un Chrome remoto, espera a que los cards monten async y extrae cada proyecto con un evaluador JS validado. Pieza 1 de un monitor de captacion de clientes: detecta proyectos freelance nuevos sin abrir el navegador a mano. Shape unificado con el scraper hermano de Upwork. Devuelve un dict con count + lista de proyectos; nunca lanza ni inventa datos."
|
||||
tags: [market-intel, recon, flow-replay, browser, cdp, workana, scraper, freelance, spa, vue, captacion]
|
||||
uses_functions: ["cdp_open_url_and_wait_py_pipelines", "cdp_eval_py_browser"]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_py_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: category
|
||||
desc: "Categoria de Workana (segmento de la URL ?category=). Default 'it-programming'. Otros ejemplos: 'design-multimedia', 'writing-translation'."
|
||||
- name: language
|
||||
desc: "Idioma de los proyectos (?language=). Default 'es'."
|
||||
- name: extra_query
|
||||
desc: "Query libre opcional (?query=...). Si '', se omite. Util para filtrar por palabra clave (ej. 'python', 'scraping')."
|
||||
- name: pages
|
||||
desc: "Numero de paginas de listado a recorrer. Default 1. Cada pagina adicional se navega con &page=N."
|
||||
- name: port
|
||||
desc: "Puerto de remote debugging del Chrome a usar. Default 9222 (chromium-personal de produccion). Para un Chrome aislado (smoke / recon sin mezclar sesion personal) apuntar a 9333 (el del browser_mcp)."
|
||||
- name: timeout_s
|
||||
desc: "Timeout (segundos) por pagina, tanto para la navegacion como para el polling de aparicion de cards. Default 20.0."
|
||||
output: "dict siempre (nunca lanza). En exito: {status:'ok', source:'workana', count:N, projects:[{...}]}. Cada project_dict con claves EXACTAS: source ('workana'), job_id (slug), url (absoluta), title, budget (str|None), posted (str ej 'Hace 4 horas'), bids (str|None nº propuestas), skills (list[str]), snippet (str), country (str|None), scraped_at (ISO8601 UTC). En error (sin cards tras timeout, Chrome muerto, DOM cambiado): {status:'error', error:<mensaje claro>, source:'workana', projects:[]}. NUNCA devuelve filas falsas."
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/browser/scrape_workana_projects.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# fn run mapea args POSICIONALMENTE a la firma (category language extra_query pages port timeout_s).
|
||||
# NO uses flags --category/--language con fn run: el runner los toma como valores posicionales.
|
||||
|
||||
# Smoke contra el Chrome aislado del browser_mcp (port 9333, sin login):
|
||||
fn run scrape_workana_projects it-programming es "" 1 9333 25
|
||||
|
||||
# Produccion (chromium-personal, port 9222 por defecto):
|
||||
fn run scrape_workana_projects it-programming es "" 1 9222 20
|
||||
```
|
||||
|
||||
```bash
|
||||
# Ejecucion directa del modulo SI acepta flags --... (argparse del __main__):
|
||||
python/.venv/bin/python3 python/functions/browser/scrape_workana_projects.py \
|
||||
--category it-programming --language es --port 9222
|
||||
```
|
||||
|
||||
```python
|
||||
import sys, os, json
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from browser.scrape_workana_projects import scrape_workana_projects
|
||||
|
||||
# Detecta proyectos nuevos en it-programming (es), 1 pagina, via Chrome diario.
|
||||
res = scrape_workana_projects(category="it-programming", language="es", port=9222)
|
||||
if res["status"] == "ok":
|
||||
print(f"{res['count']} proyectos")
|
||||
for p in res["projects"][:3]:
|
||||
print("-", p["title"], "|", p["budget"], "|", p["posted"])
|
||||
else:
|
||||
print("error:", res["error"])
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Monitor de captacion: detectar proyectos freelance nuevos en Workana sin abrir el
|
||||
navegador a mano. Lanzala periodicamente (ej. desde el dag_engine) para vigilar una
|
||||
categoria/idioma y alimentar el pipeline de market-intel. Usala cuando necesites el
|
||||
listado renderizado de Workana de forma programatica — el GET HTTP puro NO sirve
|
||||
porque la pagina es una SPA Vue que monta los cards en runtime.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Requiere un Chrome con remote debugging vivo en `port`**: 9222 (chromium-personal
|
||||
de produccion, ya activado global) o 9333 (Chrome aislado del browser_mcp). Sin
|
||||
Chrome escuchando devuelve `{status:'error', error:'no hay Chrome en el puerto N...'}` — no lanza.
|
||||
- **Workana es una SPA Vue: los cards montan ASYNC** tras la hidratacion. El load
|
||||
event NO garantiza que esten en el DOM, por eso la funcion hace polling de
|
||||
`document.querySelectorAll('div.project-item.js-project').length` hasta >0 o timeout.
|
||||
Si la conexion es lenta, sube `timeout_s`.
|
||||
- **HTTP puro NO sirve**: un GET a la URL de listado trae 0 cards (HTML inicial vacio).
|
||||
CDP es obligatorio para renderizar el JavaScript.
|
||||
- **NUNCA inventa datos**: si no aparecen cards tras el timeout (chromium en port no
|
||||
logueado, DOM cambiado), devuelve `status='error'` con `projects:[]`. No hay filas falsas.
|
||||
- **Respeta el rate-limit de Workana**: no abuses (no la lances en bucle agresivo ni
|
||||
con muchas paginas seguidas). Workana puede aplicar anti-bot si detecta scraping intensivo.
|
||||
- **El selector del DOM (`div.project-item.js-project`) y el extractor JS dependen del
|
||||
HTML actual de Workana.** Si Workana cambia su markup, el extractor deja de encontrar
|
||||
cards y la funcion devuelve `status='error'` (no datos corruptos). En ese caso hay que
|
||||
actualizar `_CARD_SELECTOR` y `_EXTRACTOR_JS`.
|
||||
Reference in New Issue
Block a user