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:
- Precisão total: Extrair exatamente os dados que você quer, não o que o sistema acha que é relevante
- Dados estruturados: Criar objetos JSON com campos específicos (título, preço, autor, etc.)
- Sites complexos: Lidar com estruturas HTML não convencionais ou mal formatadas
- Performance: Extrair apenas o necessário sem processar toda a página
- Dados tabulares: Extrair tabelas, listas e dados repetitivos de forma organizada
Como funciona internamente?
Quando o WEB_SCRAPER (EXTRACT) é executado, o sistema:
- Valida configuração: Verifica se os seletores CSS foram fornecidos
- Faz requisição HTTP: Busca o HTML da página alvo
- Parseia HTML: Carrega o HTML no Cheerio (jQuery-like para Node.js)
- Remove elementos indesejados: Elimina elementos especificados em
excludeSelectors - Aplica seletores CSS: Navega pelo DOM usando os seletores fornecidos
- Extrai texto/atributos: Coleta o conteúdo dos elementos encontrados
- Estrutura dados: Se seletores for objeto, cria estrutura JSON com as chaves definidas
- Limpa resultado: Aplica trim, normalização de espaços se configurado
- 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
- Scraping de e-commerce: "Extrair título do produto (.product-title), preço (.price) e estoque (.availability)"
- Listagens de vagas: "Coletar título da vaga (h2.job-title), empresa (.company-name) e salário (.salary-range)"
- Tabelas de dados: "Extrair todas as linhas da tabela #data-table tbody tr"
- Informações estruturadas: "Criar JSON com nome, email, telefone e endereço usando seletores específicos"
- 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