Pular para conteúdo

WEB_SCRAPER (MONITOR) - Monitoramento Inteligente de Mudanças

O que é este Node?

O WEB_SCRAPER (MONITOR) é o node responsável por monitorar páginas web e detectar mudanças no conteúdo, alertando automaticamente quando algo importante muda. Ideal para acompanhar preços, vagas de emprego, notícias, disponibilidade de produtos e atualizações de sites.

Por que este Node existe?

Verificar manualmente se algo mudou em um site é tedioso e ineficiente. O WEB_SCRAPER (MONITOR) existe para:

  1. Automatizar vigilância: Checar páginas periodicamente sem intervenção manual
  2. Detectar mudanças: Identificar quando conteúdo relevante foi alterado
  3. Alertar proativamente: Notificar instantaneamente quando mudanças ocorrem
  4. Economizar tempo: Eliminar necessidade de checagens manuais repetitivas
  5. Capturar oportunidades: Ser o primeiro a saber quando algo importante muda

Como funciona internamente?

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

  1. Carrega estado anterior: Busca último conteúdo conhecido da página (hash, texto, screenshot)
  2. Faz nova extração: Busca conteúdo atual da página
  3. Compara versões: Analisa diferenças entre versão anterior e atual
  4. Detecta mudanças: Identifica se houve alterações significativas
  5. Aplica filtros: Ignora mudanças irrelevantes (timestamps, ads dinâmicos)
  6. Calcula diferenças: Determina o que especificamente mudou
  7. Salva novo estado: Atualiza estado para próxima comparação
  8. Dispara alertas: Se mudança detectada, notifica conforme configurado
  9. Retorna resultado: Informa se houve mudança e detalhes do que mudou

Código interno (web-scraper-executor.service.ts:95-197 - adaptado para monitoramento):

async execute(data: WebScraperNodeData, context: ExecutionContext): Promise<ExecutionResult> {
  const startTime = Date.now();

  // Get previous state from context or storage
  const previousState = await this.getPreviousState(data.url, context);

  // Scrape current content
  const currentResult = await this.scrapeUrl(data.url, data);

  // Compare states
  const changes = this.detectChanges(previousState, currentResult, data);

  // Save new state for next comparison
  await this.saveState(data.url, currentResult, context);

  return {
    success: true,
    data: {
      hasChanged: changes.detected,
      changes: changes.details,
      previous: previousState,
      current: currentResult,
      timestamp: new Date().toISOString()
    },
    executionTime: Date.now() - startTime
  };
}

private detectChanges(previous: any, current: any, config: WebScraperNodeData): any {
  // Implement change detection logic
  // Can compare: text content, HTML structure, specific selectors, images, etc.

  if (!previous) {
    return { detected: true, details: 'First monitoring - no previous state' };
  }

  const textChanged = previous.text !== current.text;
  const titleChanged = previous.title !== current.title;

  return {
    detected: textChanged || titleChanged,
    details: {
      textChanged,
      titleChanged,
      textDiff: this.calculateDiff(previous.text, current.text)
    }
  };
}

Quando você DEVE usar este Node?

Use WEB_SCRAPER (MONITOR) sempre que precisar detectar mudanças automaticamente:

Casos de uso

  1. Monitor de preços: "Me avise quando o preço do iPhone cair abaixo de R$ 4.000"
  2. Novas vagas de emprego: "Alerta quando uma vaga de 'Senior Developer' for publicada"
  3. Disponibilidade de produtos: "Notifique quando o produto voltar ao estoque"
  4. Atualizações de blog: "Avise quando um novo artigo for publicado"
  5. Mudanças em termos legais: "Detecte alterações em termos de uso ou políticas"

Quando NÃO usar WEB_SCRAPER (MONITOR)

  • Sites com API de webhook: Use webhook nativo ao invés de polling
  • Conteúdo altamente dinâmico: Anúncios, timestamps, contadores mudam sempre
  • Monitoramento em tempo real: Scraping tem delay, use WebSockets para tempo real
  • Sites que bloqueiam scraping: Use serviços oficiais ou APIs

Parâmetros Detalhados

monitorInterval (number, opcional)

O que é: Intervalo em minutos entre verificações. Quanto menor, mais rápido detecta mudanças mas consome mais recursos.

Padrão: 60 (1 hora)

Mínimo recomendado: 5 minutos

Flow completo para testar:

{
  "name": "Monitor de Preço - Check a cada 10min",
  "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": "A cada 10 minutos",
        "parameters": {
          "cron": "*/10 * * * *"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Monitor Preço",
        "parameters": {
          "operation": "monitor",
          "url": "https://www.amazon.com.br/dp/B08N5WRWNW",
          "mode": "manual",
          "selectors": {
            "preco": ".a-price-whole"
          },
          "monitorInterval": 10,
          "changeThreshold": 0.01
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Mudou?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 900, "y": 50 },
      "data": {
        "label": "Alerta Mudança",
        "parameters": {
          "message": "🔔 Preço mudou!\nAntes: {{scraper_1.data.previous.text}}\nAgora: {{scraper_1.data.current.text}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1100, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "schedule_1" },
    { "source": "schedule_1", "target": "scraper_1" },
    { "source": "scraper_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" }
  ]
}

Teste: Verifica preço a cada 10 minutos e alerta se mudar.

changeThreshold (number, opcional)

O que é: Percentual mínimo de mudança (0-1) para considerar alteração significativa. Útil para ignorar pequenas variações irrelevantes.

Padrão: 0.05 (5%)

Flow completo para testar:

{
  "name": "Monitor com Threshold de Mudança",
  "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": "Monitor Página",
        "parameters": {
          "operation": "monitor",
          "url": "https://news.ycombinator.com",
          "mode": "auto",
          "extractText": true,
          "changeThreshold": 0.1,
          "ignoreSelectors": [".timestamp", ".score"]
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Mudança Significativa?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged && scraper_1.data.changes.percentageChanged > 0.1}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 50 },
      "data": {
        "label": "Alerta",
        "parameters": {
          "message": "Mudança detectada: {{(scraper_1.data.changes.percentageChanged * 100).toFixed(1)}}% do conteúdo mudou"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_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" }
  ]
}

Teste: Só alerta se mais de 10% do conteúdo mudou.

ignoreSelectors (array de strings, opcional)

O que é: Lista de seletores CSS de elementos que devem ser ignorados na comparação. Útil para elementos que sempre mudam (timestamps, contadores, ads).

Padrão: []

Flow completo para testar:

{
  "name": "Monitor Ignorando Elementos Dinâmicos",
  "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": "Monitor Notícias",
        "parameters": {
          "operation": "monitor",
          "url": "https://www.bbc.com/news",
          "mode": "auto",
          "extractText": true,
          "ignoreSelectors": [
            ".timestamp",
            ".ad",
            ".social-share",
            ".trending-now",
            "[data-timestamp]"
          ]
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Nova Notícia?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 50 },
      "data": {
        "label": "Alerta Nova Notícia",
        "parameters": {
          "message": "📰 Nova notícia publicada!\n\nMudanças:\n{{scraper_1.data.changes.textDiff}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_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" }
  ]
}

Teste: Ignora timestamps e ads, detectando apenas mudanças reais no conteúdo.

compareMethod (string, opcional)

O que é: Método de comparação: "text" (compara texto), "hash" (compara hash do HTML), "visual" (compara screenshot).

Padrão: "text"

Valores aceitos: "text", "hash", "visual", "selector"

Flow completo para testar:

{
  "name": "Monitor com Comparação por Hash",
  "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": "Monitor por Hash",
        "parameters": {
          "operation": "monitor",
          "url": "https://example.com/terms",
          "compareMethod": "hash",
          "saveHtml": true,
          "includeMetadata": true
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "HTML Mudou?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 50 },
      "data": {
        "label": "Alerta Mudança Legal",
        "parameters": {
          "message": "⚠️ Termos de uso foram alterados!\n\nHash anterior: {{scraper_1.data.previous.hash}}\nHash atual: {{scraper_1.data.current.hash}}\n\nHTML salvo para comparação."
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "scraper_1" },
    { "source": "scraper_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" }
  ]
}

Teste: Compara hash SHA256 do HTML para detectar QUALQUER mudança, por menor que seja.

Parâmetros

Campo Tipo Obrigatório Descrição
operation string Sim Tipo de operação ("monitor")
url string Sim URL da página a monitorar
monitorInterval number Não Intervalo em minutos (padrão: 60)
changeThreshold number Não Threshold de mudança 0-1 (padrão: 0.05)
ignoreSelectors string[] Não Elementos a ignorar na comparação
compareMethod string Não Método: "text", "hash", "visual", "selector"
mode string Não Modo de extração
selectors string/object Não Seletores específicos a monitorar
saveHistory boolean Não Salvar histórico de mudanças (padrão: true)
alertOnFirstRun boolean Não Alertar na primeira execução (padrão: false)

Exemplo 1: Monitor de Preços Inteligente

Objetivo: Alertar quando preço de produto cai abaixo de valor específico

JSON para Importar

{
  "name": "Monitor de Preços com Alerta de Queda",
  "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": "A cada 30 minutos",
        "parameters": {
          "cron": "*/30 * * * *"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Produtos Monitorados",
        "parameters": {
          "variableName": "produtos",
          "value": [
            {
              "nome": "iPhone 15 Pro",
              "url": "https://www.amazon.com.br/dp/B0CXXXXXXXXX",
              "preco_alvo": 7000,
              "seletor_preco": ".a-price-whole"
            },
            {
              "nome": "MacBook Pro M3",
              "url": "https://www.amazon.com.br/dp/B0CYYYYYYYYY",
              "preco_alvo": 15000,
              "seletor_preco": ".a-price-whole"
            }
          ]
        }
      }
    },
    {
      "id": "loop_1",
      "type": "loop",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Cada Produto",
        "parameters": {
          "items": "{{produtos}}",
          "itemVariable": "produto"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Monitor Preço",
        "parameters": {
          "operation": "monitor",
          "url": "{{produto.url}}",
          "mode": "manual",
          "selectors": "{{produto.seletor_preco}}",
          "monitorInterval": 30,
          "saveHistory": true
        }
      }
    },
    {
      "id": "variable_2",
      "type": "variable",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Extrair Preço",
        "parameters": {
          "variableName": "preco_atual",
          "value": "{{parseFloat(scraper_1.data.current.text.replace('R$', '').replace('.', '').replace(',', '.'))}}"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 1300, "y": 100 },
      "data": {
        "label": "Preço Caiu?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged && preco_atual <= produto.preco_alvo}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 1500, "y": 50 },
      "data": {
        "label": "Alerta Oportunidade",
        "parameters": {
          "message": "🎯 OPORTUNIDADE! Preço caiu!\n\nProduto: {{produto.nome}}\nPreço anterior: R$ {{scraper_1.data.previous.text}}\nPreço atual: R$ {{scraper_1.data.current.text}}\nMeta: R$ {{produto.preco_alvo}}\n\nLink: {{produto.url}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "schedule_1" },
    { "source": "schedule_1", "target": "variable_1" },
    { "source": "variable_1", "target": "loop_1" },
    { "source": "loop_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "variable_2" },
    { "source": "variable_2", "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: 🎯 OPORTUNIDADE! Preço caiu!

Produto: iPhone 15 Pro
Preço anterior: R$ 7.499,00
Preço atual: R$ 6.999,00
Meta: R$ 7.000,00

Link: https://www.amazon.com.br/dp/B0CXXXXXXXXX

Exemplo 2: Monitor de Vagas de Emprego

Objetivo: Detectar quando novas vagas são publicadas em site de RH

JSON para Importar

{
  "name": "Detector de Novas 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": "A cada hora",
        "parameters": {
          "cron": "0 * * * *"
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Monitor Vagas",
        "parameters": {
          "operation": "monitor",
          "url": "https://careers.company.com/jobs?location=brazil&department=engineering",
          "mode": "manual",
          "selectors": {
            "vagas": ".job-listing",
            "titulos": ".job-title",
            "localizacoes": ".job-location"
          },
          "ignoreSelectors": [".apply-date", ".views-count"],
          "changeThreshold": 0.01,
          "compareMethod": "text"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Novas Vagas?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged}}"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 900, "y": 50 },
      "data": {
        "label": "Analisar Mudanças",
        "parameters": {
          "variableName": "mudancas_detectadas",
          "value": "{{scraper_1.data.changes.textDiff}}"
        }
      }
    },
    {
      "id": "ai_1",
      "type": "ai",
      "position": { "x": 1100, "y": 50 },
      "data": {
        "label": "Extrair Novas Vagas",
        "parameters": {
          "prompt": "Analise as mudanças detectadas e extraia apenas as NOVAS vagas que foram adicionadas. Retorne em formato JSON array com: titulo, localizacao.\n\nMudanças detectadas:\n{{mudancas_detectadas}}",
          "model": "gpt-4",
          "variableName": "novas_vagas"
        }
      }
    },
    {
      "id": "loop_1",
      "type": "loop",
      "position": { "x": 1300, "y": 50 },
      "data": {
        "label": "Cada Nova Vaga",
        "parameters": {
          "items": "{{JSON.parse(novas_vagas)}}",
          "itemVariable": "vaga"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 1500, "y": 50 },
      "data": {
        "label": "Alerta Vaga",
        "parameters": {
          "message": "💼 Nova vaga publicada!\n\nTítulo: {{vaga.titulo}}\nLocalização: {{vaga.localizacao}}\n\nAplicar: https://careers.company.com/jobs"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "schedule_1" },
    { "source": "schedule_1", "target": "scraper_1" },
    { "source": "scraper_1", "target": "condition_1" },
    { "source": "condition_1", "target": "variable_1", "label": "true" },
    { "source": "condition_1", "target": "end_1", "label": "false" },
    { "source": "variable_1", "target": "ai_1" },
    { "source": "ai_1", "target": "loop_1" },
    { "source": "loop_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: 💼 Nova vaga publicada!

Título: Senior Full Stack Developer
Localização: São Paulo, Brazil (Remote)

Aplicar: https://careers.company.com/jobs

Sistema: 💼 Nova vaga publicada!

Título: DevOps Engineer
Localização: Rio de Janeiro, Brazil

Aplicar: https://careers.company.com/jobs

Exemplo 3: Monitor de Disponibilidade de Produto

Objetivo: Alertar quando produto esgotado volta ao estoque

JSON para Importar

{
  "name": "Monitor de Estoque - Alerta de Disponibilidade",
  "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": "A cada 15 minutos",
        "parameters": {
          "cron": "*/15 * * * *"
        }
      }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "URL do Produto",
        "parameters": {
          "message": "Cole a URL do produto que deseja monitorar:",
          "variableName": "product_url",
          "saveToContext": true
        }
      }
    },
    {
      "id": "scraper_1",
      "type": "web_scraper",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Monitor Disponibilidade",
        "parameters": {
          "operation": "monitor",
          "url": "{{product_url}}",
          "mode": "manual",
          "selectors": {
            "titulo": "#productTitle",
            "disponibilidade": "#availability span",
            "preco": ".a-price-whole"
          },
          "monitorInterval": 15,
          "compareMethod": "selector"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Parsear Dados",
        "parameters": {
          "variableName": "produto",
          "value": "{{JSON.parse(scraper_1.data.current.text)}}"
        }
      }
    },
    {
      "id": "condition_1",
      "type": "condition",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Voltou ao Estoque?",
        "parameters": {
          "condition": "{{scraper_1.data.hasChanged && (produto.disponibilidade.includes('Em estoque') || produto.disponibilidade.includes('In stock'))}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 1300, "y": 50 },
      "data": {
        "label": "Alerta Disponível",
        "parameters": {
          "message": "🎉 PRODUTO DISPONÍVEL!\n\nProduto: {{produto.titulo}}\nStatus: {{produto.disponibilidade}}\nPreço: R$ {{produto.preco}}\n\nCOMPRE AGORA: {{product_url}}"
        }
      }
    },
    {
      "id": "whatsapp_1",
      "type": "send_message",
      "position": { "x": 1500, "y": 50 },
      "data": {
        "label": "Enviar WhatsApp",
        "parameters": {
          "to": "+5511999999999",
          "message": "🎉 O produto {{produto.titulo}} VOLTOU AO ESTOQUE! Preço: R$ {{produto.preco}} - Compre: {{product_url}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1700, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "schedule_1" },
    { "source": "schedule_1", "target": "input_1" },
    { "source": "input_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": "whatsapp_1" },
    { "source": "whatsapp_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Cole a URL do produto que deseja monitorar:
Usuário: https://www.amazon.com.br/dp/B08N5WRWNW

[15 minutos depois]

Sistema: 🎉 PRODUTO DISPONÍVEL!

Produto: Echo Dot (4ª Geração)
Status: Em estoque
Preço: R$ 349,00

COMPRE AGORA: https://www.amazon.com.br/dp/B08N5WRWNW

[Enviado via WhatsApp para +5511999999999]

Resposta do Node

{
  "success": true,
  "data": {
    "hasChanged": true,
    "changes": {
      "detected": true,
      "percentageChanged": 0.12,
      "textChanged": true,
      "titleChanged": false,
      "textDiff": "- Preço: R$ 3.499,00\n+ Preço: R$ 3.199,00"
    },
    "previous": {
      "url": "https://example.com/product",
      "title": "Produto XYZ",
      "text": "Preço: R$ 3.499,00",
      "timestamp": "2025-10-13T12:00:00.000Z",
      "hash": "a8b9c0d1e2f3..."
    },
    "current": {
      "url": "https://example.com/product",
      "title": "Produto XYZ",
      "text": "Preço: R$ 3.199,00",
      "timestamp": "2025-10-13T12:30:00.000Z",
      "hash": "f3e2d1c0b9a8..."
    },
    "timestamp": "2025-10-13T12:30:00.000Z"
  },
  "executionTime": 2456
}

Boas Práticas

SIM:

  • Use ignoreSelectors para elementos dinâmicos (timestamps, contadores, ads)
  • Defina changeThreshold adequado para evitar alertas de mudanças irrelevantes
  • Combine com SCHEDULE para verificações periódicas automáticas
  • Salve histórico para análise de tendências (saveHistory: true)
  • Use método "hash" para detectar QUALQUER mudança em páginas legais
  • Configure monitorInterval balanceando detecção rápida x consumo de recursos
  • Use CONDITION para filtrar apenas mudanças relevantes

NÃO:

  • Monitor com intervalo muito curto (< 5 minutos) sem necessidade
  • Ignorar elementos dinâmicos (causa falsos positivos)
  • Esquecer de tratar primeira execução (alertOnFirstRun: false)
  • Monitorar páginas que mudam conteúdo constantemente
  • Não validar se mudança é realmente relevante antes de alertar
  • Monitor sem limite de threshold (alerta para mudanças mínimas)

Dicas

💡 Dica 1: Combine monitor com AI para analisar se a mudança detectada é realmente importante antes de alertar.

💡 Dica 2: Use compareMethod: "hash" para páginas onde QUALQUER mudança importa (termos legais, configurações críticas).

💡 Dica 3: Para monitor de preços, extraia o número e compare numericamente ao invés de comparar texto.

💡 Dica 4: Salve estado anterior em banco de dados ao invés de memória para suportar restarts do sistema.

💡 Dica 5: Use changeThreshold de 0.05-0.10 (5-10%) para páginas normais, evitando alertas para mudanças triviais.

💡 Dica 6: Combine com WEBHOOK ou SEND_MESSAGE para notificações em tempo real quando mudanças ocorrem.

💡 Dica 7: Para produtos esgotados, monitore seletor de disponibilidade específico ao invés de página inteira.

Próximo Node

WEB_SCRAPER (SCRAPE) - Extração simples sem monitoramento → WEB_SCRAPER (SCREENSHOT) - Comparação visual de mudanças → SCHEDULE - Agendar verificações periódicas → CONDITION - Filtrar mudanças relevantes → AI - Analisar mudanças com inteligência artificial