# Web Scraping & Data Extraction - Guía Completa

## Introducción y Fundamentos

### ¿Qué es Web Scraping?
- **Definición**: Técnica para extraer información de sitios web de forma automatizada
- **Diferencia con APIs**: No requiere API oficial, accede directamente al HTML
- **Aplicaciones**: Lead generation, investigación de mercado, monitoreo de precios, análisis de competencia

### Cuándo Usar Web Scraping vs APIs
```
Usar API cuando:
✓ La plataforma tiene API oficial
✓ Necesitas datos en tiempo real
✓ Quieres integración estable a largo plazo

Usar Web Scraping cuando:
✓ No existe API oficial
✓ La API tiene limitaciones severas
✓ Necesitas datos específicos no disponibles en API
✓ Quieres datos históricos o de archivo
```

## Arquitectura Técnica de Web Scraping

### Flujo Básico de Scraping
```
1. HTTP Request → 2. Obtener HTML → 3. Parsear contenido → 
4. Extraer datos → 5. Procesar información → 6. Almacenar/Usar datos
```

### Componentes Esenciales

#### 1. HTTP Client
- **Make**: HTTP Make Request module
- **n8n**: HTTP Request node  
- **Configuración**: Headers, User-Agent, cookies

#### 2. HTML Parser
- **Make**: Text Parser (HTML to Text)
- **n8n**: HTML Extract node / Code node
- **Función**: Convertir HTML a formato procesable

#### 3. Data Extractor
- **RegEx**: Para patrones específicos
- **CSS Selectors**: Para elementos HTML específicos
- **XPath**: Para navegación precisa del DOM

## Implementación en Make

### Setup Básico de Scraping

#### HTTP Make Request Configuration:
```json
{
  "url": "https://example-site.com/products",
  "method": "GET",
  "headers": {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "keep-alive"
  }
}
```

#### Análisis de Response:
```
Status Code: 200 (éxito)
Data: Contenido HTML completo de la página
Content-Type: text/html; charset=UTF-8
```

### HTML to Text Processing

#### Text Parser Module:
```json
Module: Text Parser → HTML to Text
Input: {{HTTP_Response.data}}
Output: Texto plano sin etiquetas HTML
```

#### Ventajas de la Conversión:
- Elimina código HTML complejo
- Facilita búsqueda de patrones
- Reduce ruido en los datos
- Permite procesamiento con RegEx

### Extracción con Match Pattern

#### RegEx para URLs:
```regex
# Ejemplo: Extraer URLs de productos de paddle
https:\/\/padel\.zoom\.es\/[^\/]+\/[^\/\s"']+

Componentes:
- https:\/\/ : Protocolo HTTPS
- padel\.zoom\.es : Dominio específico  
- \/[^\/]+ : Primera parte del path
- \/[^\/\s"']+ : Segunda parte (evita espacios y comillas)
```

#### Generación de RegEx con ChatGPT:
```
Prompt para ChatGPT:
"Eres un experto en Make.com. Crea un código RegEx para Match Pattern.
Quiero extraer las URLs de palas de paddle de este texto:
[PEGAR_TEXTO_SCRAPEADO]

Ejemplos de URLs que quiero extraer:
- https://padel.zoom.es/pala/bullpadel-hack-03-master
- https://padel.zoom.es/pala/adidas-metalbone-ctrl

No quiero extraer:
- URLs de artículos generales
- Enlaces a páginas de información"
```

#### Iteración y Refinamiento:
```
Iteración 1: RegEx básico para cualquier URL
Iteración 2: Filtrar solo dominio específico  
Iteración 3: Excluir patrones no deseados
Iteración 4: Optimización final testada
```

### Scraping Multinivel (Deep Scraping)

#### Arquitectura de Scraping Profundo:
```
Página Principal → Extraer URLs → Iterator → 
HTTP Request por cada URL → Extraer datos específicos → Agregar resultados
```

#### Iterator Setup:
```json
Module: Iterator
Array Field: {{Match_Pattern.matches}}
Resultado: Cada URL se procesa individualmente
```

#### HTTP Request por Item:
```json
{
  "url": "{{Iterator.value}}",
  "method": "GET",
  "headers": {
    "User-Agent": "Mozilla/5.0...",
    "Referer": "{{original_page_url}}"
  }
}
```

### Extracción de Datos Específicos

#### Text Parser con RegEx Avanzado:

**Extraer Precios**:
```regex
# Patrón para precios en euros
(\d+[.,]?\d*)\s*€

# Patrón para precios con rango
(\d+[.,]?\d*)\s*€?\s*-\s*(\d+[.,]?\d*)\s*€
```

**Extraer Ratings/Puntuaciones**:
```regex
# Puntuación sobre 5
Puntuación:\s*(\d+[.,]?\d*)/5

# Estrellas
(\d+)\s*estrellas?
```

**Extraer Especificaciones Técnicas**:
```regex
# Potencia, Control, etc.
Potencia:\s*(\d+)
Control:\s*(\d+)
Salida de bola:\s*(\d+)
```

#### Función Split para Múltiples Valores:
```json
Module: Text Parser → Split
Text: {{extracted_specs}}
Separator: " | " o "\n"
Resultado: Array de especificaciones separadas
```

## Implementación en n8n

### HTTP Request Node Configuration

```json
{
  "method": "GET",
  "url": "{{$json.target_url}}",
  "options": {
    "headers": {
      "User-Agent": "Mozilla/5.0 (compatible; Bot/1.0)",
      "Accept": "text/html,application/xhtml+xml"
    },
    "timeout": 30000,
    "followRedirect": true,
    "maxRedirects": 5
  }
}
```

### HTML Parsing con Code Node

#### Cheerio Library (Recomendado):
```javascript
// Code node para parsing con Cheerio
const cheerio = require('cheerio');
const html = $json.body;
const $ = cheerio.load(html);

// Extraer títulos de productos
const products = [];
$('.product-item').each((i, element) => {
  const product = {
    title: $(element).find('.product-title').text().trim(),
    price: $(element).find('.price').text().trim(),
    url: $(element).find('a').attr('href'),
    image: $(element).find('img').attr('src')
  };
  products.push(product);
});

return products.map(product => ({json: product}));
```

#### CSS Selectors vs XPath:
```css
/* CSS Selectors */
.product-card h2           /* Título en card de producto */
[data-price]              /* Elemento con atributo data-price */
.rating .stars            /* Estrellas dentro de rating */
a[href*="/product/"]      /* Enlaces que contienen "/product/" */
```

```xpath
/* XPath Selectors */
//div[@class='product-card']//h2        /* Título en cualquier nivel */
//span[contains(@class,'price')]        /* Span con clase que contiene 'price' */
//a[starts-with(@href,'/product/')]     /* Links que empiecen con '/product/' */
```

### Handling Dynamic Content

#### Esperar Carga de JavaScript:
```javascript
// Code node para esperar contenido dinámico
await new Promise(resolve => setTimeout(resolve, 3000));

// O verificar elemento específico
const checkElement = async () => {
  let attempts = 0;
  while (attempts < 10) {
    const html = await fetchHTML($json.url);
    if (html.includes('product-loaded')) {
      return html;
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
    attempts++;
  }
  throw new Error('Content not loaded');
};
```

#### Manejar AJAX Requests:
```javascript
// Interceptar llamadas AJAX
const interceptedData = await fetch(`${$json.base_url}/api/products`, {
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Referer': $json.original_url
  }
});

const jsonData = await interceptedData.json();
return jsonData.map(item => ({json: item}));
```

## Scraping de Plataformas Específicas

### LinkedIn Scraping

#### Profile Scraping:
```javascript
// Code node para LinkedIn profiles
const profileData = {
  name: $('.pv-text-details__left-panel h1').text(),
  headline: $('.text-body-medium').text(),
  location: $('.pb2 .t-black--light').text(),
  connections: $('.pvs-header__optional-link').text(),
  experience: []
};

$('#experience + div .pvs-list__item').each((i, exp) => {
  profileData.experience.push({
    title: $(exp).find('[data-field="experience-position-title"]').text(),
    company: $(exp).find('[data-field="experience-company-name"]').text(),
    duration: $(exp).find('.pvs-entity__caption-wrapper').text()
  });
});
```

#### Lead Generation Workflow:
```
1. Search Results → 2. Extract Profile URLs → 3. Visit Each Profile → 
4. Extract Data → 5. Enrich with Additional APIs → 6. CRM Integration
```

### Instagram Scraping

#### Post Data Extraction:
```javascript
// Extraer datos de posts de Instagram
const postData = {
  caption: $('meta[property="og:description"]').attr('content'),
  likes: $('.Nm9Fw span').text(),
  comments: $('.EtaWk span').text(),
  hashtags: [],
  mentions: []
};

// Extraer hashtags y mentions del caption
const caption = postData.caption;
const hashtags = caption.match(/#\w+/g) || [];
const mentions = caption.match(/@\w+/g) || [];

postData.hashtags = hashtags;
postData.mentions = mentions;
```

### E-commerce Scraping

#### Product Comparison:
```javascript
// Scraping de múltiples tiendas para comparación
const stores = [
  'https://store1.com/product/',
  'https://store2.com/product/',
  'https://store3.com/product/'
];

const compareProduct = async (productId) => {
  const results = [];
  
  for (const store of stores) {
    const data = await scrapeStore(`${store}${productId}`);
    results.push({
      store: getStoreName(store),
      price: data.price,
      availability: data.stock,
      shipping: data.shipping_cost
    });
  }
  
  return results.sort((a, b) => a.price - b.price);
};
```

## Gestión de Rate Limiting y Anti-Bot

### Técnicas de Evasión

#### User-Agent Rotation:
```javascript
const userAgents = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
];

const randomUA = userAgents[Math.floor(Math.random() * userAgents.length)];
```

#### Proxy Rotation:
```javascript
// n8n HTTP node con proxy
{
  "method": "GET",
  "url": "{{$json.target_url}}",
  "options": {
    "proxy": "http://{{$json.proxy_ip}}:{{$json.proxy_port}}",
    "headers": {
      "User-Agent": "{{$json.random_ua}}"
    }
  }
}
```

#### Request Timing:
```javascript
// Delay aleatorio entre requests
const minDelay = 2000; // 2 segundos mínimo
const maxDelay = 8000; // 8 segundos máximo
const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay;

await new Promise(resolve => setTimeout(resolve, randomDelay));
```

### Error Handling y Retry Logic

#### Exponential Backoff:
```javascript
// Code node para retry con backoff
const maxRetries = 3;
let attempt = 0;

while (attempt < maxRetries) {
  try {
    const response = await fetch($json.url, {
      headers: { 'User-Agent': getRandomUA() }
    });
    
    if (response.status === 200) {
      return [{json: await response.text()}];
    } else if (response.status === 429) {
      // Rate limited, wait longer
      const waitTime = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
  } catch (error) {
    console.log(`Attempt ${attempt + 1} failed:`, error.message);
  }
  
  attempt++;
}

throw new Error('Max retries exceeded');
```

## Data Processing y Enrichment

### Data Cleaning

#### Text Normalization:
```javascript
// Code node para limpiar texto
const cleanText = (text) => {
  return text
    .replace(/\s+/g, ' ')           // Múltiples espacios → uno solo
    .replace(/\n+/g, ' ')           // Saltos de línea → espacio
    .replace(/[^\w\s€$.,]/g, '')    // Solo alfanuméricos y símbolos básicos
    .trim();                        // Eliminar espacios inicio/final
};

const cleanedData = {
  title: cleanText($json.raw_title),
  price: extractPrice($json.raw_price),
  description: cleanText($json.raw_description)
};
```

#### Price Standardization:
```javascript
// Normalizar precios de diferentes formatos
const normalizePrice = (priceString) => {
  // "€ 1.299,99" → 1299.99
  // "$1,299.99" → 1299.99
  // "1299,99 EUR" → 1299.99
  
  const cleaned = priceString.replace(/[^\d.,]/g, '');
  
  if (cleaned.includes(',') && cleaned.includes('.')) {
    // Formato europeo: 1.299,99
    if (cleaned.lastIndexOf(',') > cleaned.lastIndexOf('.')) {
      return parseFloat(cleaned.replace(/\./g, '').replace(',', '.'));
    }
    // Formato americano: 1,299.99
    else {
      return parseFloat(cleaned.replace(/,/g, ''));
    }
  } else if (cleaned.includes(',')) {
    // Solo comas: podría ser decimal europeo
    return parseFloat(cleaned.replace(',', '.'));
  } else {
    return parseFloat(cleaned);
  }
};
```

### Data Enrichment con IA

#### Categorización Automática:
```json
{
  "model": "gpt-4",
  "messages": [
    {
      "role": "system",
      "content": "Eres un experto categorizando productos. Devuelve solo la categoría principal."
    },
    {
      "role": "user", 
      "content": "Categoriza este producto: {{$json.product_title}} - {{$json.product_description}}"
    }
  ],
  "max_tokens": 50
}
```

#### Sentiment Analysis:
```json
{
  "model": "gpt-4",
  "messages": [
    {
      "role": "system",
      "content": "Analiza el sentimiento de este review: POSITIVO, NEGATIVO o NEUTRAL. Responde solo con una palabra."
    },
    {
      "role": "user",
      "content": "{{$json.review_text}}"
    }
  ]
}
```

### Data Storage y Export

#### Database Integration:
```javascript
// Code node para insertar en base de datos
const insertData = async (scrapedData) => {
  const query = `
    INSERT INTO scraped_products (title, price, url, scraped_at, category)
    VALUES (?, ?, ?, NOW(), ?)
  `;
  
  const params = [
    scrapedData.title,
    scrapedData.price,
    scrapedData.url,
    scrapedData.category
  ];
  
  await database.execute(query, params);
};
```

#### Export to Google Sheets:
```json
{
  "method": "POST",
  "url": "https://sheets.googleapis.com/v4/spreadsheets/{{sheet_id}}/values/{{range}}:append",
  "headers": {
    "Authorization": "Bearer {{access_token}}"
  },
  "body": {
    "values": [[
      "{{$json.title}}",
      "{{$json.price}}",
      "{{$json.url}}",
      "{{$now}}"
    ]]
  }
}
```

## Casos de Uso Avanzados

### Lead Generation Masivo

#### Workflow Completo:
```
1. Google Search Results → 2. Extract Company URLs → 
3. Visit Company Pages → 4. Find Contact Info → 
5. LinkedIn Profile Search → 6. Enrich with Apollo/Hunter → 
7. CRM Integration
```

#### Contact Extraction:
```javascript
// Extraer información de contacto
const extractContacts = (html) => {
  const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
  const phoneRegex = /(?:\+?34|0034)?[679]\d{8}/g;
  
  const emails = html.match(emailRegex) || [];
  const phones = html.match(phoneRegex) || [];
  
  return {
    emails: [...new Set(emails)], // Remove duplicates
    phones: [...new Set(phones)],
    company: extractCompanyName(html),
    website: extractWebsite(html)
  };
};
```

### Competitor Monitoring

#### Price Monitoring:
```javascript
// Monitor competitor prices daily
const monitorPrices = async () => {
  const competitors = await getCompetitorUrls();
  const priceChanges = [];
  
  for (const competitor of competitors) {
    const currentPrice = await scrapePrice(competitor.url);
    const lastPrice = await getLastPrice(competitor.id);
    
    if (currentPrice !== lastPrice) {
      priceChanges.push({
        competitor: competitor.name,
        product: competitor.product,
        old_price: lastPrice,
        new_price: currentPrice,
        change_percent: ((currentPrice - lastPrice) / lastPrice) * 100
      });
      
      await updatePrice(competitor.id, currentPrice);
    }
  }
  
  if (priceChanges.length > 0) {
    await sendPriceAlert(priceChanges);
  }
};
```

### Social Media Analysis

#### Hashtag Trend Analysis:
```javascript
// Analizar trending hashtags
const analyzeTrends = async (hashtags) => {
  const trendData = {};
  
  for (const hashtag of hashtags) {
    const posts = await scrapeHashtagPosts(hashtag);
    trendData[hashtag] = {
      post_count: posts.length,
      avg_engagement: calculateAverageEngagement(posts),
      top_posts: posts.slice(0, 5),
      sentiment: await analyzeSentiment(posts.map(p => p.caption))
    };
  }
  
  return trendData;
};
```

## Herramientas y Recursos

### Browser Automation (Advanced)

#### Puppeteer en n8n:
```javascript
// Code node con Puppeteer para SPAs
const puppeteer = require('puppeteer');

const browser = await puppeteer.launch({
  headless: true,
  args: ['--no-sandbox', '--disable-setuid-sandbox']
});

const page = await browser.newPage();
await page.goto($json.url, { waitUntil: 'networkidle2' });

// Esperar elemento específico
await page.waitForSelector('.dynamic-content');

// Hacer scroll para cargar lazy content
await page.evaluate(() => {
  window.scrollTo(0, document.body.scrollHeight);
});

await page.waitForTimeout(2000);

const data = await page.evaluate(() => {
  return Array.from(document.querySelectorAll('.product-item')).map(item => ({
    title: item.querySelector('h2')?.textContent,
    price: item.querySelector('.price')?.textContent
  }));
});

await browser.close();
return data.map(item => ({json: item}));
```

### Anti-Detection Techniques

#### Behavioral Patterns:
```javascript
// Simular comportamiento humano
const humanBehavior = {
  async randomScroll(page) {
    const scrolls = Math.floor(Math.random() * 3) + 1;
    for (let i = 0; i < scrolls; i++) {
      await page.evaluate(() => {
        window.scrollBy(0, Math.random() * 500 + 200);
      });
      await page.waitForTimeout(Math.random() * 2000 + 1000);
    }
  },
  
  async randomClick(page) {
    const clickableElements = await page.$$('a, button');
    if (clickableElements.length > 0 && Math.random() < 0.1) {
      const randomElement = clickableElements[Math.floor(Math.random() * clickableElements.length)];
      await randomElement.click();
      await page.waitForTimeout(Math.random() * 3000 + 2000);
      await page.goBack();
    }
  }
};
```

## Legal y Ético

### Consideraciones Legales:
```
✓ Revisar robots.txt del sitio
✓ Respetar términos de servicio
✓ No sobrecargar servidores (rate limiting)
✓ Cumplir GDPR para datos personales
✓ Usar datos solo para propósitos legítimos
```

### Best Practices Éticas:
```
✓ Identificarse apropiadamente (User-Agent)
✓ Respetar directivas de no-scraping
✓ Cachear datos para evitar requests repetitivos
✓ Ofrecer opt-out cuando sea aplicable
✓ No scraping de contenido protegido por copyright
```

---

*Esta guía proporciona todas las técnicas y herramientas necesarias para implementar web scraping profesional y efectivo en cualquier proyecto de automatización.*