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:
- Automatizar navegação: Seguir links "Próxima página", paginação numérica ou estruturas hierárquicas
- Coletar datasets completos: Extrair todos os produtos de uma categoria, todas as notícias de um portal, etc.
- Respeitar limites: Controlar quantas páginas processar e o tempo entre requisições
- Manter contexto: Rastrear de onde veio cada dado e manter estrutura organizada
- 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:
- Inicia na URL base: Carrega a primeira página da sequência
- Extrai dados da página atual: Coleta informações conforme configurado
- Identifica link de paginação: Busca elemento "Próxima página" usando
paginationSelector - Verifica limites: Checa se atingiu
maxPagesou se não há mais páginas - Aplica delay: Aguarda tempo configurado antes da próxima requisição
- Navega para próxima página: Faz requisição para o link encontrado
- Repete processo: Continua até atingir limite ou fim da paginação
- Agrega resultados: Combina dados de todas as páginas em um único resultado
- 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
- Catálogo completo de e-commerce: "Extrair todos os 500 produtos da categoria 'Eletrônicos'"
- Arquivo de blog: "Coletar todos os artigos do blog desde 2020"
- Listagens de imóveis: "Buscar todos os apartamentos disponíveis em São Paulo (15 páginas)"
- Resultados de pesquisa: "Extrair as primeiras 10 páginas de resultados do Google/Bing"
- 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
maxPagesrazoável - comece com 5-10 e aumente gradualmente - Use
delayde pelo menos 2-3 segundos entre páginas - Ative
ignoreErrors: truepara crawls longos - Teste o
paginationSelectorem uma página antes do crawl completo - Use
respectRobots: truepara crawling ético - Monitore logs para identificar quando a paginação acaba
- Combine com SCHEDULE para crawls periódicos
❌ NÃO:
- Crawl sem limites (
maxPagesmuito 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