Pular para conteúdo

WEB_SCRAPER (EXTRACT) - Extração Manual com Seletores CSS

O que é este Node?

O WEB_SCRAPER (EXTRACT) é o node responsável por extrair dados específicos de websites usando seletores CSS personalizados. Diferente do modo automático, você tem controle total sobre quais elementos extrair, permitindo precisão cirúrgica na coleta de dados estruturados.

Por que este Node existe?

Nem sempre a extração automática encontra exatamente o que você precisa. O WEB_SCRAPER (EXTRACT) existe para:

  1. Precisão total: Extrair exatamente os dados que você quer, não o que o sistema acha que é relevante
  2. Dados estruturados: Criar objetos JSON com campos específicos (título, preço, autor, etc.)
  3. Sites complexos: Lidar com estruturas HTML não convencionais ou mal formatadas
  4. Performance: Extrair apenas o necessário sem processar toda a página
  5. Dados tabulares: Extrair tabelas, listas e dados repetitivos de forma organizada

Como funciona internamente?

Quando o WEB_SCRAPER (EXTRACT) é executado, o sistema:

  1. Valida configuração: Verifica se os seletores CSS foram fornecidos
  2. Faz requisição HTTP: Busca o HTML da página alvo
  3. Parseia HTML: Carrega o HTML no Cheerio (jQuery-like para Node.js)
  4. Remove elementos indesejados: Elimina elementos especificados em excludeSelectors
  5. Aplica seletores CSS: Navega pelo DOM usando os seletores fornecidos
  6. Extrai texto/atributos: Coleta o conteúdo dos elementos encontrados
  7. Estrutura dados: Se seletores for objeto, cria estrutura JSON com as chaves definidas
  8. Limpa resultado: Aplica trim, normalização de espaços se configurado
  9. Retorna dados estruturados: Devolve objeto organizado ou string conforme configuração

Código interno (web-scraper-executor.service.ts:450-464):

private async extractManual($: any, config: WebScraperNodeData): Promise<string> {
  // Single selector - returns text content
  if (typeof config.selectors === 'string') {
    return $(config.selectors).text().trim();
  }

  // Object with named selectors - returns structured JSON
  if (typeof config.selectors === 'object') {
    const results: Record<string, string> = {};
    for (const [key, selector] of Object.entries(config.selectors)) {
      results[key] = $(selector).text().trim();
    }
    return JSON.stringify(results, null, 2);
  }

  return '';
}

Quando você DEVE usar este Node?

Use WEB_SCRAPER (EXTRACT) sempre que precisar de extração precisa com controle total:

Casos de uso

  1. Scraping de e-commerce: "Extrair título do produto (.product-title), preço (.price) e estoque (.availability)"
  2. Listagens de vagas: "Coletar título da vaga (h2.job-title), empresa (.company-name) e salário (.salary-range)"
  3. Tabelas de dados: "Extrair todas as linhas da tabela #data-table tbody tr"
  4. Informações estruturadas: "Criar JSON com nome, email, telefone e endereço usando seletores específicos"
  5. Sites com estrutura conhecida: "Sempre extrair os mesmos campos do mesmo site"

Quando NÃO usar WEB_SCRAPER (EXTRACT)

  • Estrutura desconhecida: Use mode "auto" quando não conhece a estrutura do site
  • Múltiplos layouts: Sites que mudam estrutura frequentemente vão quebrar seus seletores
  • JavaScript pesado: Seletores CSS só funcionam em HTML estático, não conteúdo gerado por JS
  • Conteúdo dinâmico: Use ferramentas com browser real (Puppeteer) para SPAs

Parâmetros Detalhados

selectors (string ou object, obrigatório)

O que é: Seletores CSS para extrair elementos específicos. Pode ser uma string (retorna texto) ou objeto com múltiplos seletores (retorna JSON estruturado).

Padrão: Nenhum (obrigatório no mode "manual")

Exemplos de seletores: - Classe: .product-title, .price, .author - ID: #main-content, #article-body - Tag: h1, p, article - Atributo: [data-price], [itemprop="price"] - Hierarquia: .product .title, article h1 - Pseudo: :first-child, :nth-child(2)

Flow completo para testar (Seletor único):

{
  "name": "Teste Extract - Seletor Único",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Extrair Título",
        "parameters": {
          "operation": "extract",
          "url": "https://example.com",
          "mode": "manual",
          "selectors": "h1"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Mostrar",
        "parameters": {
          "message": "Título extraído: {{scraper_1.data.data[0].text}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Extrai o conteúdo da primeira tag <h1> da página.

Flow completo para testar (Múltiplos seletores estruturados):

{
  "name": "Teste Extract - Seletores Estruturados",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Extrair Produto",
        "parameters": {
          "operation": "extract",
          "url": "https://www.amazon.com.br/dp/B08N5WRWNW",
          "mode": "manual",
          "selectors": {
            "titulo": "#productTitle",
            "preco": ".a-price-whole",
            "avaliacao": ".a-icon-alt",
            "disponibilidade": "#availability span"
          }
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Mostrar Dados",
        "parameters": {
          "message": "Produto extraído:\n{{scraper_1.data.data[0].text}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Extrai múltiplos campos de uma página de produto Amazon em formato JSON estruturado.

excludeSelectors (array de strings, opcional)

O que é: Lista de seletores CSS de elementos que devem ser removidos ANTES da extração. Útil para eliminar menus, rodapés, ads, popups que atrapalham.

Padrão: [] (array vazio)

Flow completo para testar:

{
  "name": "Teste Extract - Remover Elementos",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Extrair Limpo",
        "parameters": {
          "operation": "extract",
          "url": "https://example.com/article",
          "mode": "manual",
          "selectors": "article",
          "excludeSelectors": [
            ".advertisement",
            ".sidebar",
            "nav",
            "footer",
            ".social-share"
          ]
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Conteúdo Limpo",
        "parameters": {
          "message": "Artigo sem elementos indesejados:\n{{scraper_1.data.data[0].text}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Remove ads, sidebar e elementos de navegação antes de extrair o conteúdo principal.

includeOnlySelectors (array de strings, opcional)

O que é: Quando especificado, APENAS estes elementos são processados. Funciona como um filtro whitelist.

Padrão: [] (processa tudo)

Flow completo para testar:

{
  "name": "Teste Extract - Incluir Apenas",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Apenas Parágrafos",
        "parameters": {
          "operation": "extract",
          "url": "https://example.com/article",
          "mode": "manual",
          "selectors": "article",
          "includeOnlySelectors": ["p", "h2", "h3"]
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "Apenas texto dos parágrafos e títulos:\n{{scraper_1.data.data[0].text}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Extrai apenas parágrafos e títulos, ignorando divs, spans e outros elementos.

Parâmetros

Campo Tipo Obrigatório Descrição
operation string Sim Tipo de operação ("extract")
url string Sim URL do website a extrair
mode string Sim Deve ser "manual"
selectors string ou object Sim Seletor CSS único ou objeto com múltiplos
excludeSelectors string[] Não Elementos a remover antes da extração
includeOnlySelectors string[] Não Processar apenas estes elementos
delay number Não Delay entre requisições em ms
timeout number Não Timeout da requisição
headers object Não Headers HTTP customizados
cleanText boolean Não Limpar e formatar texto
outputFormat string Não Formato de saída

Exemplo 1: Extração de Listagem de Produtos

Objetivo: Extrair todos os produtos de uma página de categoria e-commerce

JSON para Importar

{
  "name": "Extrator de Lista de Produtos",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "URL Categoria",
        "parameters": {
          "message": "Digite a URL da categoria:",
          "variableName": "category_url"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Extrair Produtos",
        "parameters": {
          "operation": "extract",
          "url": "{{category_url}}",
          "mode": "manual",
          "selectors": {
            "produtos": ".product-item",
            "titulos": ".product-title",
            "precos": ".product-price",
            "imagens": ".product-image img"
          },
          "excludeSelectors": [".advertisement", ".recommended"],
          "cleanText": true
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Parsear JSON",
        "parameters": {
          "variableName": "produtos_data",
          "value": "{{JSON.parse(scraper_1.data.data[0].text)}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Resumo",
        "parameters": {
          "message": "Produtos extraídos:\n{{produtos_data}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Digite a URL da categoria:
Usuário: https://example-shop.com/eletronicos
Sistema: Produtos extraídos:
{
  "produtos": "25 produtos encontrados",
  "titulos": "Notebook Dell, Mouse Logitech, Teclado Mecânico...",
  "precos": "R$ 3.500,00, R$ 89,90, R$ 450,00...",
  "imagens": "https://example.com/img1.jpg, ..."
}

Exemplo 2: Extração de Vagas de Emprego

Objetivo: Coletar dados estruturados de vagas em site de empregos

JSON para Importar

{
  "name": "Extrator de Vagas de Emprego",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Extrair Vagas",
        "parameters": {
          "operation": "extract",
          "url": "https://www.linkedin.com/jobs/search/?keywords=desenvolvedor",
          "mode": "manual",
          "selectors": {
            "titulo": ".job-card-list__title",
            "empresa": ".job-card-container__company-name",
            "localizacao": ".job-card-container__metadata-item",
            "descricao": ".job-card-list__description",
            "data_postagem": ".job-card-list__date"
          },
          "delay": 2000,
          "respectRobots": true
        }
      }
    },
    {
      "id": "loop_1",
      "type": "loop",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Processar Vagas",
        "parameters": {
          "items": "{{JSON.parse(scraper_1.data.data[0].text)}}",
          "itemVariable": "vaga"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Filtrar Senior",
        "parameters": {
          "condition": "{{vaga.titulo.includes('Senior') || vaga.titulo.includes('Sênior')}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 50 },
      "data": {
        "label": "Vaga Qualificada",
        "parameters": {
          "message": "✅ Vaga Senior encontrada!\nTítulo: {{vaga.titulo}}\nEmpresa: {{vaga.empresa}}\nLocal: {{vaga.localizacao}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "loop_1" },
    { "source": "loop_1", "target": "condition_1" },
    { "source": "condition_1", "target": "message_1", "label": "true" },
    { "source": "condition_1", "target": "end_1", "label": "false" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: ✅ Vaga Senior encontrada!
Título: Desenvolvedor Senior Full Stack
Empresa: Tech Company
Local: São Paulo, SP (Remoto)

Sistema: ✅ Vaga Senior encontrada!
Título: Engenheiro de Software Sênior
Empresa: StartupXYZ
Local: Rio de Janeiro, RJ

Exemplo 3: Comparação de Preços

Objetivo: Extrair preços do mesmo produto em diferentes lojas

JSON para Importar

{
  "name": "Comparador de Preços Multi-Loja",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Definir Lojas",
        "parameters": {
          "variableName": "lojas",
          "value": [
            {
              "nome": "Amazon",
              "url": "https://www.amazon.com.br/dp/B08N5WRWNW",
              "seletor_preco": ".a-price-whole"
            },
            {
              "nome": "Mercado Livre",
              "url": "https://produto.mercadolivre.com.br/MLB-123456",
              "seletor_preco": ".price-tag-fraction"
            },
            {
              "nome": "Magazine Luiza",
              "url": "https://www.magazineluiza.com.br/produto/123",
              "seletor_preco": "[data-testid='price-value']"
            }
          ]
        }
      }
    },
    {
      "id": "loop_1",
      "type": "loop",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Cada Loja",
        "parameters": {
          "items": "{{lojas}}",
          "itemVariable": "loja"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Extrair Preço",
        "parameters": {
          "operation": "extract",
          "url": "{{loja.url}}",
          "mode": "manual",
          "selectors": "{{loja.seletor_preco}}",
          "delay": 3000,
          "timeout": 15000
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Comparar",
        "parameters": {
          "message": "{{loja.nome}}: R$ {{scraper_1.data.data[0].text}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "variable_1" },
    { "source": "variable_1", "target": "loop_1" },
    { "source": "loop_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Amazon: R$ 349,00
Sistema: Mercado Livre: R$ 329,90
Sistema: Magazine Luiza: R$ 359,00

Resposta do Node

{
  "success": true,
  "data": {
    "totalPages": 1,
    "successfulPages": 1,
    "errors": [],
    "totalProcessingTime": 1234,
    "data": [
      {
        "url": "https://example.com/product",
        "text": "{\n  \"titulo\": \"Echo Dot 4ª Geração\",\n  \"preco\": \"R$ 349,00\",\n  \"avaliacao\": \"4.8 de 5 estrelas\",\n  \"disponibilidade\": \"Em estoque\"\n}",
        "timestamp": "2025-10-13T12:30:00.000Z",
        "processingTime": 1234
      }
    ]
  },
  "executionTime": 1236,
  "logs": [
    "Scraped 1 URLs",
    "Total items found: 1",
    "Processing time: 1236ms"
  ]
}

Boas Práticas

SIM:

  • Use DevTools (F12) do navegador para encontrar seletores CSS corretos
  • Teste seletores no console: document.querySelector('.classe')
  • Use seletores específicos (.product-price) ao invés de genéricos (div)
  • Crie objetos estruturados com múltiplos seletores para dados relacionados
  • Use excludeSelectors para remover ads e elementos que atrapalham
  • Combine IDs e classes para seletores mais precisos (#produto .preco)
  • Documente quais seletores funcionam para cada site

NÃO:

  • Usar seletores muito genéricos (div, span) que podem pegar elementos errados
  • Confiar em seletores baseados em estrutura (div > div > span) - são frágeis
  • Esquecer de testar se o seletor realmente encontra o elemento
  • Usar índices numéricos (:nth-child(5)) sem necessidade
  • Criar seletores complexos demais - dificulta manutenção
  • Ignorar que sites mudam sua estrutura HTML periodicamente

Dicas

💡 Dica 1: Use o Chrome DevTools para testar seletores. Clique com botão direito no elemento → Inspect → Copy → Copy selector.

💡 Dica 2: Para extrair atributos (src, href, data-*) ao invés de texto, você precisará combinar com código customizado ou usar o modo template.

💡 Dica 3: Seletores de classe múltipla funcionam: .product.featured encontra elementos com ambas as classes.

💡 Dica 4: Use :contains('texto') (Cheerio) para encontrar elementos por conteúdo: a:contains('Comprar').

💡 Dica 5: Quando o site tem dados estruturados em JSON-LD, use extractSchemaOrg: true ao invés de seletores CSS - é mais confiável.

💡 Dica 6: Para tabelas, use table tbody tr para pegar linhas e depois processe cada célula td.

💡 Dica 7: Combine Extract com node LOOP para processar listas de elementos (produtos, vagas, artigos) individualmente.

Próximo Node

WEB_SCRAPER (SCRAPE) - Extração automática sem seletores → WEB_SCRAPER (CRAWL) - Navegação por múltiplas páginas → LOOP - Processar múltiplos itens extraídos → VARIABLE - Armazenar dados extraídos