Como você faz web scraping de sites protegidos pelo Cloudflare no n8n?

Estou construindo um fluxo de trabalho de resumo diário que busca conteúdo de blogs da indústria HVAC. Três de minhas fontes (HouseCall Pro, Jobber Academy, HVAC Informed) são protegidas por Cloudflare. Quando meu nó HTTP Request atinge essas URLs, ele recebe uma página de desafio Cloudflare “Só um momento…” 403 em vez do conteúdo real.

Esses sites também não possuem feeds RSS, portanto os nós RSS Feed Read não funcionam.

O que já tentei:

  • Nó HTTP Request (GET) — retorna HTML de desafio Cloudflare

  • Nó RSS Feed Read — retorna erros de análise XML (as páginas não são RSS)

Perguntas:

  1. Existe uma maneira de contornar a proteção de bot do Cloudflare nativamente no n8n?

  2. Devo usar Apify, ScrapingBee ou BrightData para essas fontes?

  3. Existe um nó de comunidade que lida com sites protegidos por Cloudflare?

  4. Adicionar headers semelhantes aos de um navegador (User-Agent, Accept-Language, etc.) ajudaria?

@Asim_Arman fez uma verificação rápida nas suas 3 fontes antes de recomendar qualquer coisa e tem uma boa notícia para uma delas — HouseCall Pro na verdade não está no Cloudflare e seu /feed/ funciona perfeitamente se você apenas adiciona um User-Agent de navegador real no nó HTTP Request. testei com um Mozilla/5.0 UA contra Housecall Pro e recebi um RSS XML válido. seu 403 lá é quase certamente porque o nó HTTP Request do n8n usa um UA genérico (ou nenhum) e o site tem um filtro básico de UA. mude para RSS Feed Read apontando para Housecall Pro com override de User-Agent e pronto para esse, sem necessidade de scraping.

Jobber (getjobber.com) está genuinamente no Cloudflare — header cf-ray presente nas respostas — e não há RSS para a seção academy. HVAC Informed também não expõe RSS. esses dois genuinamente precisam de um serviço de scraping.

sobre a questão de contornar o Cloudflare nativamente, não há jeito real de fazer isso no n8n. o desafio deles requer executar JS para resolver proof-of-work mais fingerprinting comportamental, e o nó HTTP Request é um cliente http puro sem motor de navegador. as libs estilo cloudscraper de 2022-2023 estão todas quebradas contra o Cloudflare atual, então os poucos nós da comunidade que as envolvem não funcionam.

headers tipo navegador corrigem filtros básicos de UA (que é o que o 403 do HouseCall Pro na verdade era, ironicamente) mas não fazem nada contra o verdadeiro desafio Cloudflare — eles fazem fingerprint de TLS, JA3, timing de requisição, e execução JS além apenas headers.

para ScrapingBee vs Apify vs BrightData — para seu caso de uso (digest diário de baixo volume, apenas 2 sites precisando scraping real) ScrapingBee ou Firecrawl é o mais limpo. comparação aproximada:

{
  "scrapingbee": {"per_request": "~$0.001-0.005", "fit": "baixo-médio volume, simples, rápido"},
  "scraperapi": {"per_request": "~$0.001-0.003", "fit": "comparável ao scrapingbee"},
  "firecrawl": {"per_request": "~$0.001", "fit": "scrape-para-markdown, saída pronta para LLM"},
  "brightdata": {"per_request": "~$0.005-0.02", "fit": "alvos mais difíceis, caro mas mais confiável"},
  "apify": {"per_request": "varia", "fit": "fluxos multi-página complexos"}
}

ScrapingBee chamado via HTTP Request fica assim:

{
  "method": "GET",
  "url": "https://app.scrapingbee.com/api/v1/",
  "qs": {
    "api_key": "<sua_chave>",
    "url": "https://www.getjobber.com/academy/",
    "render_js": "true",
    "premium_proxy": "true"
  }
}

o estado final é um RSS Feed Read para HouseCall Pro, duas chamadas HTTP ScrapingBee para Jobber e HVAC Informed, depois parse → digest. muito mais barato do que tratar os 3 como protegidos por Cloudflare quando apenas um na verdade está.

Muito obrigado!, estou testando isso e deixo você saber

De nada! Me avisa como funciona!

Gostaria de adicionar que a Apify tem o Smart Article Extractor que consegue (geralmente) detectar links de artigos em uma página automaticamente e depois fazer scrape deles um por um.

E ele emula um navegador, então consegue contornar o Cloudflare também. É um actor de uso único, então uma chamada uma vez por dia para três blogs provavelmente se encaixa na camada gratuita deles.

Para casos mais gerais (não artigos), você consegue obter conteúdo estruturado usando o Website Content Crawler deles e depois rodá-los através de um pequeno modelo de IA, como haiku ou gpt minis.

Para sites mais complexos como Jobber e HVAC Informed, se você usar uma instância n8n auto-hospedada com Docker e preferir evitar soluções pagas, uma alternativa interessante consiste em executar SeleniumBase no modo Undetected ChromeDriver (UC) dentro de um contêiner Docker separado. O modo UC (Undetected ChromeDriver) do SeleniumBase é atualmente uma das ferramentas open source mais robustas para contornar as proteções do Cloudflare.

Você pode então configurar esse contêiner para executar seus scripts de scraping e acioná-los diretamente do n8n por meio do nó Execute Command (ou usando uma ferramenta personalizada se você estruturou seu fluxo em torno de um Agente IA n8n.

Uma coisa que ninguém mencionou ainda: antes de recorrer a um serviço de scraping, verifique se esses sites têm um sitemap ou feed Atom escondido em um caminho não padrão.

Tente acessar estes antes de pagar por qualquer coisa:

  • /sitemap.xml
  • /sitemap_index.xml
  • /feed/
  • /atom.xml
  • /blog/feed/ ou /blog/rss/

Muitos blogs alimentados por WordPress/HubSpot (que tanto HouseCall Pro quanto Jobber usam) têm feeds ativados, mas não vinculados no . O nó RSS Read funcionará perfeitamente se você acessar a URL correta.

Para os que genuinamente não têm feed e o Cloudflare bloqueia você:

Abordagem mais barata de nível produção: Firecrawl ou ScrapingBee via nó HTTP Request.

HTTP Request → POST https://api.firecrawl.dev/v1/scrape
Headers: Authorization: Bearer
Body: { “url”: “https://target.com/blog”, “formats”: [“markdown”] }

Retorna markdown limpo — sem necessidade de parsing HTML. Execute em um Schedule Trigger uma vez por dia. Com 3 sites × 1 requisição/dia, você ficará bem dentro de qualquer tier gratuito.

Endurecimento para produção (a parte que a maioria dos tutoriais pula):

  1. Adicione uma retry com backoff no nó HTTP Request — APIs de scraping ocasionalmente retornam 429
  2. Armazene o hash da última URL buscada em uma tabela Postgres para que você não reprocesse o mesmo artigo amanhã (idempotência)
  3. Adicione um workflow de Error Trigger — se seu digest falhar silenciosamente na segunda-feira, você quer saber antes de sexta

A parte “apenas faça o scraping” é fácil. Fazer com que funcione de forma confiável todos os dias por 6 meses sem falhas silenciosas é onde a maioria dos workflows de digest diário quebra.

Opa, valeu muito pela explicação detalhada, achamm. Isso foi super útil e acabei seguindo a maioria.

O que segui exatamente:

  • HouseCall Pro - confirmei que não era Cloudflare, adicionei o header User-Agent no nó HTTP Request, apontei para /feed/, passei por um nó XML e depois um nó Code. Tá funcionando perfeitamente agora.

  • Jobber e HVAC Informed - usei Firecrawl em vez de ScrapingBee (mesmo nível de preço que você mencionou, mas preferi o output em markdown para passar pro Claude).

    Abordagem em dois passos: primeiro faço scrape da página de listagem pra extrair as URLs dos artigos, aí depois faço scrape de cada artigo individualmente através de um segundo HTTP Request.

O que adicionamos além das suas recomendações:

  • Um nó Parse Code depois de cada requisição do Firecrawl pra normalizar todas as fontes em campos idênticos (title, content, link, feedSource) antes de fazer o merge

Valeu demais cara, fico feliz de te ter na comunidade

De nada, ficamos felizes em poder ajudá-lo!

Se você tem a possibilidade de auto-hospedar uma ferramenta (por exemplo via Docker no mesmo servidor que seu n8n) e quer evitar os limites de cota das plataformas pagas (como Apify, ScrapingBee ou Firecrawl), você deveria dar uma olhada em FlareSolverr.
É um proxy open-source que lança um navegador headless em background para resolver o desafio do Cloudflare e te devolver o HTML bruto da página. Você só precisa consultá-lo localmente com um simples nó HTTP Request no n8n.
Porém, o FlareSolverr te devolve apenas o código HTML bruto. Você terá que adicionar um nó HTML ou um nó Code logo depois para fazer parse e estruturar você mesmo os dados em JSON.
uum pequeno exemplo:

{
“nodes”: [
{
“parameters”: {
“jsCode”: “const html = $json.solution.response;\n\nfunction getText(html, startMarker, endMarker) {\n const start = html.indexOf(startMarker);\n if (start === -1) return ‘’;\n const end = html.indexOf(endMarker, start);\n if (end === -1) return ‘’;\n return html.substring(start + startMarker.length, end).trim();\n}\n\nfunction getMetaContent(html, name) {\n const regex = new RegExp(<meta name=\"${name}\" content=\"([^\"]+)\", ‘i’);\n const match = html.match(regex);\n return match ? match[1] : ‘’;\n}\n\nlet title = getText(html, ‘<h1 class="story-title">’, ‘’);\n// limpa o título das tags\ntitle = title.replace(/<a[^>]>([^<])<\/a>/, ‘$1’).trim();\n\nconst description = getMetaContent(html, ‘description’);\n\nlet content = getText(html, ‘<div class="articlebody clear cf" id="articlebody">’, ‘<div class="stophere" id="hiddenH1">’);\n// limpa o conteúdo\ncontent = content\n .replace(/<script[^>]>[\s\S]?<\/script>/gi, ‘’)\n .replace(/<style[^>]>[\s\S]?<\/style>/gi, ‘’)\n .replace(/<[^>]+>/g, ’ ')\n .replace(/\s+/g, ’ ')\n .trim();\n\nreturn [{\n title: title,\n description: description,\n content: content\n}];”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-912,
4480
],
“id”: “a6275095-c4ed-427f-a791-620bcd83710b”,
“name”: “Extract from HTML”
},
{
“parameters”: {
“method”: “POST”,
“url”: “=http://flaresolverr:8191/v1”,
“sendHeaders”: true,
“headerParameters”: {
“parameters”: [
{
“name”: “Content-Type”,
“value”: “application/json”
}
]
},
“sendBody”: true,
“bodyParameters”: {
“parameters”: [
{
“name”: “cmd”,
“value”: “request.get”
},
{
“name”: “url”,
“value”: “={{ $json.url }}”
},
{
“name”: “maxTimeout”,
“value”: 90000
}
]
},
“options”: {
“timeout”: 120000
}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.4,
“position”: [
-1120,
4480
],
“id”: “4c884f8a-09c7-4c13-b7e8-ac38098b00f6”,
“name”: “FlareSolverr”,
“retryOnFail”: true
}
],
“connections”: {
“Extract from HTML”: {
“main”: [

]
},
“FlareSolverr”: {
“main”: [
[
{
“node”: “Extract from HTML”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“templateCredsSetupCompleted”: true,
“instanceId”: “03428eda2f2a76af33393f7fa59419dea9e2e74e0d630500b36df8cf5b8e161d”
}
}