3253828fef
Add complete navegator system for stealthy browser automation: - CDP client with WebSocket communication - Browser API with navigation, storage, network, runtime - Stealth flags and anti-detection scripts - Persistent profile support - Examples and comprehensive documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
167 lines
4.4 KiB
Go
167 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"navegator/pkg/browser"
|
|
)
|
|
|
|
// Resultado representa un resultado de búsqueda
|
|
type Resultado struct {
|
|
Titulo string `json:"titulo"`
|
|
URL string `json:"url"`
|
|
Descripcion string `json:"descripcion"`
|
|
}
|
|
|
|
func main() {
|
|
// Definir flags/parámetros
|
|
query := flag.String("q", "", "Consulta de búsqueda (requerido)")
|
|
maxResults := flag.Int("n", 10, "Número máximo de resultados (default: 10)")
|
|
headless := flag.Bool("headless", true, "Modo headless (default: true)")
|
|
outputJSON := flag.String("output", "", "Guardar resultados en archivo JSON")
|
|
profileName := flag.String("profile", "search-bot", "Nombre del perfil a usar")
|
|
|
|
// NUEVO: Directorio de perfiles compartido
|
|
profilesDir := flag.String("profiles-dir", "", "Directorio de perfiles (default: ~/.navegator/profiles)")
|
|
|
|
flag.Parse()
|
|
|
|
// Validar que se proporcionó la consulta
|
|
if *query == "" {
|
|
fmt.Println("Error: debes proporcionar una consulta con -q")
|
|
fmt.Println("\nEjemplo:")
|
|
fmt.Println(" ./buscar -q \"golang tutorial\" -n 20")
|
|
fmt.Println("\nOpciones:")
|
|
flag.PrintDefaults()
|
|
os.Exit(1)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Determinar directorio de perfiles
|
|
var perfilesPath string
|
|
if *profilesDir != "" {
|
|
perfilesPath = *profilesDir
|
|
} else {
|
|
// Default: ~/.navegator/profiles
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
log.Fatalf("❌ Error al obtener home dir: %v", err)
|
|
}
|
|
perfilesPath = filepath.Join(homeDir, ".navegator", "profiles")
|
|
}
|
|
|
|
// Crear directorio si no existe
|
|
if err := os.MkdirAll(perfilesPath, 0755); err != nil {
|
|
log.Fatalf("❌ Error al crear directorio de perfiles: %v", err)
|
|
}
|
|
|
|
config := browser.DefaultConfig()
|
|
config.ProfilesBaseDir = perfilesPath
|
|
config.ProfileName = *profileName
|
|
config.StealthFlags.Headless = *headless
|
|
config.StealthFlags.WindowSize = [2]int{1280, 720}
|
|
|
|
log.Printf("🔍 Buscando: %s", *query)
|
|
log.Printf("📊 Máximo de resultados: %d", *maxResults)
|
|
log.Printf("👤 Usando perfil: %s", *profileName)
|
|
log.Printf("📂 Perfiles en: %s", perfilesPath)
|
|
|
|
// Lanzar navegador
|
|
b, err := browser.Launch(ctx, config)
|
|
if err != nil {
|
|
log.Fatalf("❌ Error al lanzar navegador: %v", err)
|
|
}
|
|
defer b.Close()
|
|
|
|
// Navegar a DuckDuckGo
|
|
searchURL := fmt.Sprintf("https://duckduckgo.com/?q=%s", *query)
|
|
log.Println("🌐 Navegando a DuckDuckGo...")
|
|
|
|
if err := b.Navigate(ctx, searchURL, nil); err != nil {
|
|
log.Fatalf("❌ Error al navegar: %v", err)
|
|
}
|
|
|
|
// Esperar a que carguen los resultados
|
|
time.Sleep(3 * time.Second)
|
|
|
|
log.Println("📥 Extrayendo resultados...")
|
|
|
|
// Script para extraer resultados
|
|
extractScript := fmt.Sprintf(`
|
|
(() => {
|
|
const results = [];
|
|
const maxResults = %d;
|
|
|
|
const items = document.querySelectorAll('article[data-testid="result"]');
|
|
|
|
for (let i = 0; i < Math.min(items.length, maxResults); i++) {
|
|
const item = items[i];
|
|
|
|
const titleEl = item.querySelector('h2 a');
|
|
const titulo = titleEl ? titleEl.textContent : '';
|
|
const url = titleEl ? titleEl.href : '';
|
|
|
|
const descEl = item.querySelector('[data-result="snippet"]');
|
|
const descripcion = descEl ? descEl.textContent : '';
|
|
|
|
if (titulo && url) {
|
|
results.push({
|
|
titulo: titulo.trim(),
|
|
url: url,
|
|
descripcion: descripcion.trim()
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
})()
|
|
`, *maxResults)
|
|
|
|
result, err := b.Evaluate(ctx, extractScript)
|
|
if err != nil {
|
|
log.Fatalf("❌ Error al extraer resultados: %v", err)
|
|
}
|
|
|
|
// Parsear resultados
|
|
resultadosJSON, err := json.Marshal(result.Value)
|
|
if err != nil {
|
|
log.Fatalf("❌ Error al parsear resultados: %v", err)
|
|
}
|
|
|
|
var resultados []Resultado
|
|
if err := json.Unmarshal(resultadosJSON, &resultados); err != nil {
|
|
log.Fatalf("❌ Error al deserializar: %v", err)
|
|
}
|
|
|
|
// Mostrar resultados
|
|
log.Printf("\n✅ Encontrados %d resultados:\n", len(resultados))
|
|
|
|
for i, r := range resultados {
|
|
fmt.Printf("\n%d. %s\n", i+1, r.Titulo)
|
|
fmt.Printf(" 🔗 %s\n", r.URL)
|
|
if r.Descripcion != "" {
|
|
fmt.Printf(" 📝 %s\n", r.Descripcion)
|
|
}
|
|
}
|
|
|
|
// Guardar en JSON si se especificó
|
|
if *outputJSON != "" {
|
|
data, _ := json.MarshalIndent(resultados, "", " ")
|
|
if err := os.WriteFile(*outputJSON, data, 0644); err != nil {
|
|
log.Printf("⚠️ Error al guardar JSON: %v", err)
|
|
} else {
|
|
log.Printf("\n💾 Resultados guardados en: %s", *outputJSON)
|
|
}
|
|
}
|
|
|
|
log.Println("\n✨ Búsqueda completada!")
|
|
}
|