feat(browser): funciones anti-deteccion + perfiles para web_scraping

Funciones nuevas del dominio browser (grupo navegator):
- cdp_move_mouse_human / cdp_click_human: movimiento de raton con curva
  de Bezier cubica, easing y micro-jitter para imitar comportamiento
  humano y reducir deteccion de automatizacion.
- cdp_wait_idle: espera network-idle contando requests en vuelo via
  eventos CDP Network.*; inmune a extensiones que mutan el DOM
  (Dark Reader, uBlock) y a animaciones JS.
- list_chrome_profiles: lista perfiles de un user-data-dir (extensiones,
  nombre legible, preferencias).
- prepare_chrome_profile (bash): clona un user-data-dir conservando solo
  una whitelist de extensiones (default uBlock Origin Lite).

Modificadas:
- chrome_launch: Linux-first (chromium/google-chrome/brave antes que
  chrome.exe), KeepExtensions y Setpgid para matar el arbol con cdp_close.
- cdp_close: kill por grupo de proceso.

Todas con tests verdes (go test ./functions/browser ok).
This commit is contained in:
Egutierrez
2026-06-05 16:25:11 +02:00
parent 729921e16e
commit ccfa5bc78b
17 changed files with 1603 additions and 45 deletions
+52 -8
View File
@@ -7,6 +7,7 @@ import (
"os/exec"
"regexp"
"strings"
"syscall"
"time"
)
@@ -25,6 +26,13 @@ type ChromeLaunchOpts struct {
ChromePath string
// ExtraArgs permite pasar flags adicionales a Chrome.
ExtraArgs []string
// KeepExtensions, si es true, NO añade --disable-extensions (mantiene las
// extensiones del perfil cargadas). Por defecto false (comportamiento actual).
KeepExtensions bool
// ProfileDirectory selecciona el perfil dentro del user-data-dir (--profile-directory).
// Vacío = no se pasa el flag (Chrome usa su default o muestra el selector si hay varios perfiles).
// Ej: "Default", "Automation".
ProfileDirectory string
}
// reWindowsPath coincide con rutas absolutas de Windows (C:\... D:\... etc.).
@@ -74,20 +82,43 @@ func defaultWindowsUserDataDir() (string, error) {
return translateUserDataDirForWindows(linuxPath)
}
// chromePaths lista los ejecutables de Chrome conocidos en WSL2/Linux.
var chromePaths = []string{
"chrome.exe",
"google-chrome",
"chromium-browser",
// chromePathsLinux lista los binarios Linux-nativos de Chrome en orden de preferencia.
var chromePathsLinux = []string{
"chromium",
"chromium-browser",
"google-chrome",
"google-chrome-stable",
"brave-browser",
}
// chromePathsWSL lista los ejecutables de Chrome para WSL2 (Windows .exe primero).
var chromePathsWSL = []string{
"chrome.exe",
"/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
"/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
"/mnt/c/Users/Public/Desktop/chrome.exe",
// binarios Linux como ultimo recurso en WSL
"chromium",
"chromium-browser",
"google-chrome",
"google-chrome-stable",
}
// findChrome localiza el ejecutable de Chrome en el sistema.
// En Linux nativo busca primero binarios Linux; en WSL2 busca primero chrome.exe.
func findChrome() (string, error) {
for _, p := range chromePaths {
var paths []string
if isWSL2() {
paths = chromePathsWSL
} else {
// Linux nativo: primero binarios nativos, despues .exe como ultimo recurso
paths = append(chromePathsLinux,
"chrome.exe",
"/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
"/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
)
}
for _, p := range paths {
if path, err := exec.LookPath(p); err == nil {
return path, nil
}
@@ -95,7 +126,7 @@ func findChrome() (string, error) {
return p, nil
}
}
return "", fmt.Errorf("chrome: ejecutable no encontrado en PATH ni en rutas conocidas de Windows")
return "", fmt.Errorf("chrome: ejecutable no encontrado en PATH ni en rutas conocidas")
}
// waitCDPReady espera hasta que el puerto CDP responda conexiones TCP.
@@ -187,7 +218,6 @@ func ChromeLaunch(opts ChromeLaunchOpts) (int, error) {
"--disable-background-networking",
"--disable-client-side-phishing-detection",
"--disable-default-apps",
"--disable-extensions",
"--disable-hang-monitor",
"--disable-popup-blocking",
"--disable-prompt-on-repost",
@@ -197,6 +227,12 @@ func ChromeLaunch(opts ChromeLaunchOpts) (int, error) {
"--safebrowsing-disable-auto-update",
"--remote-allow-origins=*",
}
if !opts.KeepExtensions {
args = append(args, "--disable-extensions")
}
if opts.ProfileDirectory != "" {
args = append(args, fmt.Sprintf("--profile-directory=%s", opts.ProfileDirectory))
}
if opts.Headless {
args = append(args, "--headless=new", "--disable-gpu")
@@ -222,6 +258,14 @@ func ChromeLaunch(opts ChromeLaunchOpts) (int, error) {
cmd.Stdout = nil
cmd.Stderr = nil
// En Linux nativo (no WSL+Windows), crear un grupo de proceso propio para que
// el proceso sobreviva al fin del padre y para poder matar el arbol completo
// (chromium lanza zygote, gpu-process, renderers como hijos).
// No aplicar en WSL+Windows: chrome.exe se gestiona de forma distinta.
if !wsl2WindowsMode {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
if err := cmd.Start(); err != nil {
return 0, fmt.Errorf("chrome: arrancar proceso: %w", err)
}