Pular para conteúdo

WEB_SCRAPER (CRAWL) - Navegação Automática por Múltiplas Páginas

O que é este Node?

O WEB_SCRAPER (CRAWL) é o node responsável por navegar automaticamente por múltiplas páginas de um website, seguindo links de paginação e coletando dados de forma sistemática. Diferente do scrape simples, ele segue estruturas de navegação e pode extrair dados de dezenas ou centenas de páginas sequencialmente.

Por que este Node existe?

Extrair dados de uma única página é útil, mas muitas vezes você precisa coletar informações de múltiplas páginas. O WEB_SCRAPER (CRAWL) existe para:

  1. Automatizar navegação: Seguir links "Próxima página", paginação numérica ou estruturas hierárquicas
  2. Coletar datasets completos: Extrair todos os produtos de uma categoria, todas as notícias de um portal, etc.
  3. Respeitar limites: Controlar quantas páginas processar e o tempo entre requisições
  4. Manter contexto: Rastrear de onde veio cada dado e manter estrutura organizada
  5. Lidar com paginação complexa: Suportar diferentes padrões de paginação (números, next/prev, infinite scroll simulado)

Como funciona internamente?

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

  1. Inicia na URL base: Carrega a primeira página da sequência
  2. Extrai dados da página atual: Coleta informações conforme configurado
  3. Identifica link de paginação: Busca elemento "Próxima página" usando paginationSelector
  4. Verifica limites: Checa se atingiu maxPages ou se não há mais páginas
  5. Aplica delay: Aguarda tempo configurado antes da próxima requisição
  6. Navega para próxima página: Faz requisição para o link encontrado
  7. Repete processo: Continua até atingir limite ou fim da paginação
  8. Agrega resultados: Combina dados de todas as páginas em um único resultado
  9. Retorna dataset completo: Devolve array com dados de todas as páginas processadas

Código interno (web-scraper-executor.service.ts:138-169):

// Execute scraping with pagination support
const results: ScrapingResult[] = [];
const maxPages = Math.min(data.maxPages || 1, 50); // Limit max pages for safety
const delay = Math.max(data.delay || 1000, 500); // Minimum 500ms delay

for (const url of urls.slice(0, maxPages)) {
  try {
    const scrapingResult = await this.scrapeUrl(url, data);
    results.push(scrapingResult);

    // Rate limiting
    if (urls.indexOf(url) < urls.length - 1) {
      await this.sleep(delay);
    }
  } catch (error) {
    this.logger.error(`Failed to scrape ${url}:`, error.message);

    if (!data.ignoreErrors) {
      return {
        success: false,
        error: `Failed to scrape ${url}: ${error.message}`,
        executionTime: Date.now() - startTime
      };
    } else {
      results.push({
        url,
        error: error.message,
        timestamp: new Date().toISOString(),
        processingTime: 0
      });
    }
  }
}

Quando você DEVE usar este Node?

Use WEB_SCRAPER (CRAWL) sempre que precisar de dados de múltiplas páginas sequenciais:

Casos de uso

  1. Catálogo completo de e-commerce: "Extrair todos os 500 produtos da categoria 'Eletrônicos'"
  2. Arquivo de blog: "Coletar todos os artigos do blog desde 2020"
  3. Listagens de imóveis: "Buscar todos os apartamentos disponíveis em São Paulo (15 páginas)"
  4. Resultados de pesquisa: "Extrair as primeiras 10 páginas de resultados do Google/Bing"
  5. Histórico de notícias: "Coletar todas as manchetes dos últimos 30 dias"

Quando NÃO usar WEB_SCRAPER (CRAWL)

  • Única página: Use operation "scrape" simples se não há paginação
  • URLs conhecidas: Se você já tem lista de URLs, use array de urls ao invés de crawling
  • Estrutura não linear: Sites com navegação complexa (árvore, grafo) precisam lógica customizada
  • Infinite scroll JavaScript: Paginação via JavaScript não funciona - use Puppeteer

Parâmetros Detalhados

maxPages (number, opcional)

O que é: Número máximo de páginas a serem processadas durante o crawling. Limite de segurança para evitar crawling infinito.

Padrão: 1

Máximo: 50 (limite forçado pelo sistema)

Flow completo para testar:

{
  "name": "Teste Crawl - Múltiplas Páginas",
  "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": "Crawl Notícias",
        "parameters": {
          "operation": "crawl",
          "url": "https://news.ycombinator.com",
          "mode": "auto",
          "maxPages": 5,
          "followPagination": true,
          "paginationSelector": ".morelink",
          "delay": 2000,
          "extractText": true
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "Crawling concluído!\nPáginas processadas: {{scraper_1.data.totalPages}}\nTempo total: {{scraper_1.data.totalProcessingTime}}ms"
        }
      }
    },
    {
      "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: Processa até 5 páginas do Hacker News com 2 segundos de intervalo entre cada.

followPagination (boolean, opcional)

O que é: Quando true, o crawler segue automaticamente links de paginação encontrados na página. Deve ser usado em conjunto com paginationSelector.

Padrão: false

Flow completo para testar:

{
  "name": "Teste Crawl - Seguir Paginação",
  "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": "Crawl com Paginação",
        "parameters": {
          "operation": "crawl",
          "url": "https://example-shop.com/produtos?page=1",
          "mode": "template",
          "template": "ecommerce",
          "maxPages": 10,
          "followPagination": true,
          "paginationSelector": "a[rel='next']",
          "delay": 3000
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Contar Produtos",
        "parameters": {
          "variableName": "total_produtos",
          "value": "{{scraper_1.data.data.length}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Resumo",
        "parameters": {
          "message": "Total de produtos coletados: {{total_produtos}}\nPáginas navegadas: {{scraper_1.data.totalPages}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Segue automaticamente links "Next" até processar 10 páginas ou acabar a paginação.

paginationSelector (string, opcional)

O que é: Seletor CSS do elemento de paginação a seguir. Geralmente é um link "Próxima", "Next", "›" ou número da próxima página.

Padrão: Nenhum

Exemplos comuns: - a[rel='next'] - Link com atributo rel="next" - .pagination .next - Classe next dentro de pagination - a:contains('Próxima') - Link com texto "Próxima" - .page-numbers.next - WordPress padrão - [aria-label='Next'] - ARIA label

Flow completo para testar:

{
  "name": "Teste Crawl - Seletor de Paginação",
  "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": "Crawl Blog",
        "parameters": {
          "operation": "crawl",
          "url": "https://example-blog.com/articles",
          "mode": "template",
          "template": "blog",
          "maxPages": 20,
          "followPagination": true,
          "paginationSelector": ".pagination-next",
          "delay": 2000,
          "extractText": true
        }
      }
    },
    {
      "id": "loop_1",
      "type": "loop",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Cada Página",
        "parameters": {
          "items": "{{scraper_1.data.data}}",
          "itemVariable": "page_data"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Artigo",
        "parameters": {
          "message": "Artigo: {{JSON.parse(page_data.text).title}}\nAutor: {{JSON.parse(page_data.text).author}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "loop_1" },
    { "source": "loop_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Usa seletor customizado para encontrar botão "próxima página" específico do site.

urls (array de strings, opcional - alternativa a paginação)

O que é: Array com lista de URLs a serem processadas sequencialmente. Alternativa ao followPagination quando você já conhece as URLs exatas.

Padrão: []

Flow completo para testar:

{
  "name": "Teste Crawl - Lista de URLs",
  "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 URLs",
        "parameters": {
          "variableName": "urls_produtos",
          "value": [
            "https://store.com/produto-1",
            "https://store.com/produto-2",
            "https://store.com/produto-3",
            "https://store.com/produto-4",
            "https://store.com/produto-5"
          ]
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Crawl URLs Conhecidas",
        "parameters": {
          "operation": "crawl",
          "urls": "{{urls_produtos}}",
          "mode": "template",
          "template": "ecommerce",
          "delay": 2000,
          "ignoreErrors": true
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Resultado",
        "parameters": {
          "message": "Processados: {{scraper_1.data.successfulPages}}/{{scraper_1.data.totalPages}} produtos\nErros: {{scraper_1.data.errors.length}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "variable_1" },
    { "source": "variable_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Processa lista predefinida de URLs com controle de erros.

Parâmetros

Campo Tipo Obrigatório Descrição
operation string Sim Tipo de operação ("crawl")
url string Sim* URL inicial do crawling
urls string[] Sim* Array de URLs (alternativa a url+pagination)
maxPages number Não Número máximo de páginas (padrão: 1, máx: 50)
followPagination boolean Não Seguir links de paginação (padrão: false)
paginationSelector string Não** Seletor CSS do link "próxima página"
delay number Não Delay entre páginas em ms (padrão: 1000, mín: 500)
mode string Não Modo de extração por página
extractText boolean Não Extrair texto de cada página
extractLinks boolean Não Extrair links de cada página
extractImages boolean Não Extrair imagens de cada página
ignoreErrors boolean Não Continuar mesmo se uma página falhar
respectRobots boolean Não Respeitar robots.txt
outputFormat string Não Formato de saída dos dados agregados

Obrigatório fornecer url OU urls *Obrigatório se followPagination for true

Exemplo 1: Coletar Catálogo Completo de Produtos

Objetivo: Extrair todos os produtos de uma categoria com paginação

JSON para Importar

{
  "name": "Extrator Completo de Catálogo",
  "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 primeira página da categoria:",
          "variableName": "categoria_url"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Crawl Completo",
        "parameters": {
          "operation": "crawl",
          "url": "{{categoria_url}}",
          "mode": "template",
          "template": "ecommerce",
          "maxPages": 30,
          "followPagination": true,
          "paginationSelector": "a.next-page",
          "delay": 3000,
          "respectRobots": true,
          "ignoreErrors": true,
          "outputFormat": "json"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Processar Dados",
        "parameters": {
          "variableName": "catalogo_completo",
          "value": "{{scraper_1.data.data.map(p => JSON.parse(p.text))}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Estatísticas",
        "parameters": {
          "message": "Catálogo extraído com sucesso!\n\nPáginas processadas: {{scraper_1.data.totalPages}}\nProdutos coletados: {{catalogo_completo.length}}\nTempo total: {{(scraper_1.data.totalProcessingTime / 1000).toFixed(1)}}s\n\nExemplo do primeiro produto:\nTítulo: {{catalogo_completo[0].title}}\nPreço: {{catalogo_completo[0].price}}"
        }
      }
    },
    {
      "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 primeira página da categoria:
Usuário: https://store.com/eletronicos?page=1
Sistema: Catálogo extraído com sucesso!

Páginas processadas: 30
Produtos coletados: 720
Tempo total: 92.4s

Exemplo do primeiro produto:
Título: Notebook Dell Inspiron 15
Preço: R$ 3.499,00

Exemplo 2: Arquivo Histórico de Blog

Objetivo: Coletar todos os artigos de um blog dos últimos anos

JSON para Importar

{
  "name": "Extrator de Arquivo de Blog",
  "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": "Crawl Blog Completo",
        "parameters": {
          "operation": "crawl",
          "url": "https://blog.example.com/archive",
          "mode": "template",
          "template": "blog",
          "maxPages": 50,
          "followPagination": true,
          "paginationSelector": ".older-posts",
          "delay": 2000,
          "extractMeta": true,
          "cleanText": true
        }
      }
    },
    {
      "id": "loop_1",
      "type": "loop",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Processar Artigos",
        "parameters": {
          "items": "{{scraper_1.data.data}}",
          "itemVariable": "artigo"
        }
      }
    },
    {
      "id": "ai_1",
      "type": "ai",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Categorizar",
        "parameters": {
          "prompt": "Categorize este artigo em uma das categorias: Tecnologia, Negócios, Marketing, Tutorial, Notícias. Retorne apenas o nome da categoria.\n\nTítulo: {{JSON.parse(artigo.text).title}}\nConteúdo: {{JSON.parse(artigo.text).content.substring(0, 500)}}",
          "model": "gpt-4",
          "variableName": "categoria"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Salvar Info",
        "parameters": {
          "message": "[{{categoria}}] {{JSON.parse(artigo.text).title}} - {{JSON.parse(artigo.text).date}}"
        }
      }
    },
    {
      "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": "ai_1" },
    { "source": "ai_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: [Tecnologia] Como usar IA em seu negócio - 13/10/2025
Sistema: [Marketing] 10 estratégias de SEO - 12/10/2025
Sistema: [Tutorial] Configurando ambiente Node.js - 11/10/2025
Sistema: [Negócios] Tendências do mercado 2025 - 10/10/2025
...
(continua para todas as páginas)

Exemplo 3: Monitor de Novas Vagas (Agendado)

Objetivo: Checar diariamente novas vagas em site de empregos

JSON para Importar

{
  "name": "Monitor Diário de Vagas",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "schedule_1",
      "type": "schedule",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Executar Diariamente",
        "parameters": {
          "cron": "0 9 * * *",
          "timezone": "America/Sao_Paulo"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Crawl Vagas",
        "parameters": {
          "operation": "crawl",
          "url": "https://jobs.example.com/search?q=nodejs&location=remote",
          "mode": "manual",
          "selectors": {
            "titulo": ".job-title",
            "empresa": ".company-name",
            "salario": ".salary",
            "data": ".post-date"
          },
          "maxPages": 3,
          "followPagination": true,
          "paginationSelector": "a[aria-label='Next']",
          "delay": 2000
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Filtrar Novas",
        "parameters": {
          "variableName": "vagas_hoje",
          "value": "{{scraper_1.data.data.filter(v => JSON.parse(v.text).data.includes('hoje') || JSON.parse(v.text).data.includes('1 hora'))}}"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Tem Novas?",
        "parameters": {
          "condition": "{{vagas_hoje.length > 0}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 1100, "y": 50 },
      "data": {
        "label": "Alertar",
        "parameters": {
          "message": "🔔 {{vagas_hoje.length}} novas vagas encontradas!\n\n{{vagas_hoje.map(v => `${JSON.parse(v.text).titulo} - ${JSON.parse(v.text).empresa}`).join('\n')}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "schedule_1" },
    { "source": "schedule_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "variable_1" },
    { "source": "variable_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: 🔔 3 novas vagas encontradas!

Desenvolvedor Node.js Senior - TechCorp
Full Stack Developer (Remote) - StartupXYZ
Backend Engineer - BigCompany

Resposta do Node

{
  "success": true,
  "data": {
    "totalPages": 15,
    "successfulPages": 15,
    "errors": [],
    "totalProcessingTime": 32456,
    "data": [
      {
        "url": "https://example.com?page=1",
        "title": "Página 1",
        "text": "Conteúdo da página 1...",
        "timestamp": "2025-10-13T12:30:00.000Z",
        "processingTime": 2123
      },
      {
        "url": "https://example.com?page=2",
        "title": "Página 2",
        "text": "Conteúdo da página 2...",
        "timestamp": "2025-10-13T12:30:03.000Z",
        "processingTime": 2089
      }
    ]
  },
  "executionTime": 32458,
  "logs": [
    "Scraped 15 URLs",
    "Total items found: 15",
    "Processing time: 32458ms"
  ]
}

Boas Práticas

SIM:

  • Defina maxPages razoável - comece com 5-10 e aumente gradualmente
  • Use delay de pelo menos 2-3 segundos entre páginas
  • Ative ignoreErrors: true para crawls longos
  • Teste o paginationSelector em uma página antes do crawl completo
  • Use respectRobots: true para crawling ético
  • Monitore logs para identificar quando a paginação acaba
  • Combine com SCHEDULE para crawls periódicos

NÃO:

  • Crawl sem limites (maxPages muito alto ou sem definir)
  • Usar delays muito curtos (causa bloqueios)
  • Ignorar que sites mudam estrutura de paginação
  • Tentar crawl em sites com infinite scroll JavaScript
  • Crawl massivo sem autorização do site
  • Esquecer de validar se chegou ao fim da paginação

Dicas

💡 Dica 1: Combine crawl com node ACCUMULATOR para ir salvando dados gradualmente ao invés de manter tudo na memória.

💡 Dica 2: Use ignoreErrors: true e verifique o array errors no resultado para identificar páginas problemáticas.

💡 Dica 3: Para sites com paginação numérica (page=1, page=2...), você pode gerar o array de URLs manualmente com um LOOP.

💡 Dica 4: Teste sempre com maxPages: 1 primeiro para validar que a extração está funcionando corretamente.

💡 Dica 5: Monitore totalProcessingTime - se uma página demora muito, ajuste o timeout.

💡 Dica 6: Alguns sites bloqueiam após X páginas. Use respectRobots e delays adequados para evitar.

💡 Dica 7: Combine com node DATABASE para salvar resultados em tempo real durante o crawling.

Próximo Node

WEB_SCRAPER (SCRAPE) - Extração simples de uma página → WEB_SCRAPER (EXTRACT) - Extração manual com seletores → SCHEDULE - Agendar crawls periódicos → LOOP - Processar resultados do crawl