Web Scraping no Cursor: Um Guia Completo

Web scraping é o processo de extrair dados de websites de forma programática. Ele possibilita monitoramento de preços, geração de leads, agregação de notícias, pesquisa de mercado e coleta de dados para aprendizado de máquina. O ecossistema Node.js oferece três ferramentas dominantes: Puppeteer para automação Chrome headless, Playwright para automação cross-browser e Cheerio para parsing rápido de HTML. O Cursor, com seu assistente de IA integrado e servidor de linguagem TypeScript integrado, é um excelente editor para projetos de scraping -- ele fornece IntelliSense para APIs de navegador, auto-completa cadeias de seletores e pode gerar lógica de extração a partir de descrições em linguagem natural de páginas web.
Este guia aborda o fluxo de trabalho completo de web scraping no Cursor: escolhendo a ferramenta certa, configurando Puppeteer e Playwright, fazendo parsing de HTML com Cheerio, implementando padrões de extração, contornando medidas anti-bot e aproveitando a extração com IA.
Escolhendo a Ferramenta Certa
Cada ferramenta de scraping tem pontos fortes distintos. Entender quando usar cada uma é a base de uma estratégia de scraping eficaz.
| Ferramenta | Melhor Para | Navegador | Velocidade | Complexidade |
|---|---|---|---|---|
| Cheerio | HTML estático, parsing rápido | Nenhum | Muito rápida | Baixa |
| Puppeteer | Páginas dinâmicas renderizadas por JS | Chrome/Chromium | Média | Média |
| Playwright | Cross-browser, SPAs modernas | Chrome, Firefox, WebKit | Média | Média-Alta |
| Puppeteer + Cheerio | Abordagem híbrida | Chrome | Rápida | Média |
Peça ao Cursor: "Preciso fazer scraping de um website. Os dados que preciso são renderizados por JavaScript após a página carregar. Qual ferramenta devo usar e por quê?" O Cursor recomendará Puppeteer ou Playwright e explicará por que o Cheerio sozinho não pode capturar conteúdo renderizado dinamicamente.
Configurando um Projeto de Scraping no Cursor
Inicialização do Projeto
mkdir web-scraper && cd web-scraper
npm init -y
npm install typescript tsx @types/node --save-dev
npx tsc --init
Use tsx para executar scripts de scraping em TypeScript diretamente sem uma etapa de build separada. Execute npx tsx scraper.ts no terminal integrado do Cursor (Ctrl+`) para testar scripts imediatamente.
Instalando Puppeteer
npm install puppeteer
Puppeteer baixa um Chromium empacotado por padrão. Verifique a instalação:
// test-puppeteer.ts
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();
Instalando Playwright
npm install playwright
npx playwright install # Baixa binários do navegador
Verifique:
// test-playwright.ts
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();
Instalando Cheerio
npm install cheerio
Cheerio faz parsing de strings HTML sem um navegador, tornando-o a opção mais rápida para páginas estáticas:
// test-cheerio.ts
import * as cheerio from 'cheerio';
const html = '<div class="item"><h2>Title</h2><span class="price">$9.99</span></div>';
const $ = cheerio.load(html);
console.log($('.item h2').text()); // "Title"
console.log($('.item .price').text()); // "$9.99"
Cheerio não pode executar JavaScript. Se a página alvo renderiza conteúdo dinamicamente via JS do lado do cliente (React, Vue, etc.), Cheerio verá apenas o esqueleto HTML inicial. Use Puppeteer ou Playwright para esses sites.
Scraping com Puppeteer
Puppeteer fornece uma API de alto nível para controlar Chrome ou Chromium. É ideal para fazer scraping de páginas renderizadas por JavaScript, lidar com logins e interagir com elementos da página.
Scraping Básico de Página
// scrape-products-puppeteer.ts
import puppeteer from 'puppeteer';
import * as cheerio from 'cheerio';
interface Product {
name: string;
price: string;
rating: string;
url: string;
}
async function scrapeProducts(): Promise<Product[]> {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
try {
const page = await browser.newPage();
// Definir um viewport realista
await page.setViewport({ width: 1920, height: 1080 });
// Definir um user agent realista
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
);
// Navegar e esperar a grade de produtos renderizar
await page.goto('https://example-store.com/products', {
waitUntil: 'networkidle2',
timeout: 30000,
});
// Esperar os cards de produto aparecerem
await page.waitForSelector('.product-card', { timeout: 10000 });
// Rolar para acionar conteúdo de carregamento preguiçoso
await autoScroll(page);
// Extrair dados da página
const products = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product-card')).map((card) => ({
name: card.querySelector('.product-name')?.textContent?.trim() ?? '',
price: card.querySelector('.product-price')?.textContent?.trim() ?? '',
rating: card.querySelector('.product-rating')?.textContent?.trim() ?? '',
url: card.querySelector('a')?.href ?? '',
}))
);
return products;
} finally {
await browser.close();
}
}
async function autoScroll(page: puppeteer.Page) {
await page.evaluate(async () => {
await new Promise<void>((resolve) => {
let totalHeight = 0;
const distance = 100;
const timer = setInterval(() => {
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= document.body.scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
});
});
}
const products = await scrapeProducts();
console.log(`Scraped ${products.length} products`);
console.log(products[0]);
A função autoScroll é essencial para páginas de scroll infinito. Muitos sites carregam produtos preguiçosamente conforme você rola. Sem rolar, o Puppeteer captura apenas os itens inicialmente carregados.
Lidando com Paginação
async function scrapeAllPages(baseUrl: string): Promise<Product[]> {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const allProducts: Product[] = [];
let currentPage = 1;
let hasNextPage = true;
try {
while (hasNextPage) {
await page.goto(`${baseUrl}?page=${currentPage}`, { waitUntil: 'networkidle2' });
await page.waitForSelector('.product-card', { timeout: 10000 });
const products = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product-card')).map((card) => ({
name: card.querySelector('.product-name')?.textContent?.trim() ?? '',
price: card.querySelector('.product-price')?.textContent?.trim() ?? '',
rating: card.querySelector('.product-rating')?.textContent?.trim() ?? '',
url: card.querySelector('a')?.href ?? '',
}))
);
allProducts.push(...products);
hasNextPage = await page.evaluate(
() => document.querySelector('.pagination .next:not(.disabled)') !== null
);
currentPage++;
await new Promise((r) => setTimeout(r, 2000));
}
} finally {
await browser.close();
}
return allProducts;
}
Sempre adicione atrasos entre requisições de página. Fazer scraping rápido demais pode acionar rate limiting, banimentos de IP ou desafios CAPTCHA. Um atraso de 1-3 segundos entre requisições é um bom ponto de partida. Para crawls maiores, use um serviço de rotação de proxy.
Lidando com Páginas Protegidas por Login
async function scrapeLoggedInPage(email: string, password: string) {
const browser = await puppeteer.launch({ headless: false }); // Mostrar navegador para debugging
const page = await browser.newPage();
try {
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });
await page.type('#email', email, { delay: 50 });
await page.type('#password', password, { delay: 50 });
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle2' }),
page.click('#submit'),
]);
// Verificar sucesso do login
const isLoggedIn = await page.evaluate(() => !document.querySelector('.login-error'));
if (!isLoggedIn) throw new Error('Login failed');
// Fazer scraping da página protegida
await page.goto('https://example.com/dashboard', { waitUntil: 'networkidle2' });
return await page.evaluate(() => document.querySelector('.dashboard-data')?.textContent);
} finally {
await browser.close();
}
}
Scraping com Playwright
Playwright é a ferramenta de automação cross-browser da Microsoft. Ela suporta Chromium, Firefox e WebKit e tem uma API mais moderna que o Puppeteer.
Scraping Básico com Playwright
// scrape-playwright.ts
import { chromium } from 'playwright';
interface Article {
title: string;
author: string;
date: string;
summary: string;
url: string;
}
async function scrapeArticles(): Promise<Article[]> {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
});
const page = await context.newPage();
try {
await page.goto('https://example-blog.com', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
// Esperar os artigos carregarem
await page.waitForSelector('.article-card', { timeout: 10000 });
const articles = await page.$$eval('.article-card', (cards) =>
cards.map((card) => ({
title: card.querySelector('.article-title')?.textContent?.trim() ?? '',
author: card.querySelector('.article-author')?.textContent?.trim() ?? '',
date: card.querySelector('.article-date')?.textContent?.trim() ?? '',
summary: card.querySelector('.article-summary')?.textContent?.trim() ?? '',
url: card.querySelector('a')?.href ?? '',
}))
);
return articles;
} finally {
await browser.close();
}
}
Scraping de SPAs com Interseptação de Rede
Playwright pode interceptar requisições de rede para capturar respostas de API diretamente, evitando a necessidade de fazer parsing do HTML renderizado:
async function scrapeViaAPI() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
const apiResponses: any[] = [];
// Interceptar respostas de API
page.on('response', async (response) => {
if (response.url().includes('/api/products') && response.status() === 200) {
const json = await response.json();
apiResponses.push(json);
}
});
await page.goto('https://example-store.com', { waitUntil: 'networkidle' });
await browser.close();
// Agora você tem os dados JSON brutos, sem necessidade de parsing de HTML
return apiResponses;
}
A interseptação de rede é a maneira mais confiável de fazer scraping de SPAs modernas. Em vez de fazer parsing do HTML renderizado, você captura as respostas JSON da API que a página busca. Peça ao Cursor: "Intercepte as requisições XHR nesta página e extraia os dados dos produtos da resposta da API." O Cursor gerará o listener de resposta do Playwright.
Parsing de HTML com Cheerio
Cheerio fornece uma API semelhante ao jQuery para fazer parsing de strings HTML. É 10-100x mais rápido que scraping baseado em navegador porque não renderiza páginas nem executa JavaScript.
Buscando e Fazendo Parsing
// scrape-cheerio.ts
import * as cheerio from 'cheerio';
interface Book {
title: string;
price: number;
availability: string;
rating: number;
}
async function scrapeBooks(): Promise<Book[]> {
const response = await fetch('https://books.toscrape.com/', {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
},
});
const html = await response.text();
const $ = cheerio.load(html);
const books: Book[] = [];
$('.product_pod').each((_, element) => {
const $el = $(element);
const title = $el.find('h3 a').attr('title') ?? '';
const priceText = $el.find('.price_color').text();
const price = parseFloat(priceText.replace(/[^0-9.]/g, ''));
const availability = $el.find('.instock').text().trim();
// Parsear avaliação do nome da classe (ex.: "star-rating Three" -> 3)
const ratingClass = $el.find('.star-rating').attr('class') ?? '';
const ratingMap: Record<string, number> = {
One: 1, Two: 2, Three: 3, Four: 4, Five: 5,
};
const ratingWord = ratingClass.split(' ')[1];
const rating = ratingMap[ratingWord] ?? 0;
books.push({ title, price, availability, rating });
});
return books;
}
Padrões de Seletor Cheerio
Cheerio usa seletores semelhantes ao jQuery: $('.class') para classes, $('#id') para IDs, $('a[href*="/product/"]') para matching de atributos e métodos como .parent(), .siblings(), .children(), .text(), .html() e .attr() para travessia e extração do DOM.
Se você conhece seletores jQuery, você conhece Cheerio. Peça ao Cursor: "Escreva uma cadeia de seletores Cheerio para extrair todos os nomes de produtos, preços e URLs de imagem desta estrutura HTML" e cole o HTML. O Cursor gerará os seletores corretos.
Padrões de Extração de Dados
Extraindo Dados Estruturados de Tabelas
async function scrapeTable(): Promise<Record<string, string>[]> {
const response = await fetch('https://example.com/data-table');
const html = await response.text();
const $ = cheerio.load(html);
// Obter cabeçalhos
const headers: string[] = [];
$('table thead th').each((_, el) => {
headers.push($(el).text().trim().toLowerCase().replace(/\s+/g, '_'));
});
// Extrair linhas
const rows: Record<string, string>[] = [];
$('table tbody tr').each((_, rowEl) => {
const cells: string[] = [];
$(rowEl).find('td').each((_, cellEl) => {
cells.push($(cellEl).text().trim());
});
const row: Record<string, string> = {};
headers.forEach((header, index) => {
row[header] = cells[index] ?? '';
});
rows.push(row);
});
return rows;
}
Extraindo Dados JSON-LD
Muitos websites incorporam dados estruturados no formato JSON-LD. Este é o método de extração mais confiável quando disponível:
import * as cheerio from 'cheerio';
async function scrapeJsonLd(url: string) {
const response = await fetch(url);
const html = await response.text();
const $ = cheerio.load(html);
const jsonLdData: any[] = [];
$('script[type="application/ld+json"]').each((_, el) => {
try {
const data = JSON.parse($(el).contents().text());
if (Array.isArray(data)) {
jsonLdData.push(...data);
} else {
jsonLdData.push(data);
}
} catch (e) {
console.warn('Failed to parse JSON-LD:', e);
}
});
return jsonLdData;
}
// Uso
const structuredData = await scrapeJsonLd('https://example.com/product/123');
// structuredData[0] pode conter: { "@type": "Product", "name": "...", "offers": { "price": "..." } }
JSON-LD é a maneira mais fácil e confiável de extrair dados estruturados. Peça ao Cursor: "Verifique se esta página tem dados estruturados JSON-LD e extraia as informações do produto." Se o site usa marcação schema.org, o Cursor fará o parsing sem seletores CSS frágeis.
Salvando Dados Extraídos
import { writeFileSync } from 'fs';
// Salvar como JSON
function saveToJson(data: any[], filename: string) {
writeFileSync(filename, JSON.stringify(data, null, 2));
console.log(`Saved ${data.length} records to ${filename}`);
}
// Salvar como CSV
function saveToCsv(data: Record<string, any>[], filename: string) {
if (data.length === 0) return;
const headers = Object.keys(data[0]);
const csvLines = [
headers.join(','),
...data.map((row) =>
headers.map((h) => `"${String(row[h] ?? '').replace(/"/g, '""')}"`).join(',')
),
];
writeFileSync(filename, csvLines.join('\n'));
}
Técnicas de Bypass Anti-Bot
Websites modernos implementam proteção anti-bot (Cloudflare, reCAPTCHA, Datadome, PerimeterX) para detectar e bloquear scraping automatizado. Aqui estão estratégias para contornar proteções comuns.
Definindo Cabeçalhos Realistas
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
};
const response = await fetch(url, { headers });
Usando Puppeteer-Stealth
npm install puppeteer-extra puppeteer-extra-plugin-stealth
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// O plugin stealth corrige vetores de detecção comuns:
// - navigator.webdriver = false
// - chrome.runtime object
// - permissions API
// - plugins array
// - languages array
// - WebGL vendor/renderer
await page.goto('https://bot-protected-site.com');
Plugins stealth ajudam, mas não são uma bala de prata. Serviços anti-bot sofisticados como Cloudflare Turnstile usam fingerprinting TLS, análise comportamental e mecanismos de desafio-resposta que vão além de simples verificações de propriedades do navegador. Para scraping de produção de sites fortemente protegidos, considere serviços de proxy comerciais com resolução de CAPTCHA integrada.
Usando Proxies
// Puppeteer com proxy
const browser = await puppeteer.launch({
headless: true,
args: [
`--proxy-server=http://${proxyHost}:${proxyPort}`,
'--no-sandbox',
],
});
await page.authenticate({
username: proxyUsername,
password: proxyPassword,
});
// Playwright com proxy
const browser = await chromium.launch({
headless: true,
proxy: {
server: `http://${proxyHost}:${proxyPort}`,
username: proxyUsername,
password: proxyPassword,
},
});
Rotação de User Agents
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',
];
const randomUA = userAgents[Math.floor(Math.random() * userAgents.length)];
await page.setUserAgent(randomUA);
Atrasos e Comportamento Humano
async function humanLikeScroll(page: puppeteer.Page) {
const totalHeight = await page.evaluate(() => document.body.scrollHeight);
let scrolled = 0;
while (scrolled < totalHeight) {
const scrollAmount = Math.floor(Math.random() * 200) + 100;
await page.evaluate((y) => window.scrollBy(0, y), scrollAmount);
scrolled += scrollAmount;
await new Promise((r) => setTimeout(r, Math.random() * 1500 + 500));
}
}
async function randomMouseMovement(page: puppeteer.Page) {
await page.mouse.move(Math.random() * 800, Math.random() * 600);
await new Promise((r) => setTimeout(r, Math.random() * 500 + 200));
await page.mouse.move(Math.random() * 800, Math.random() * 600);
}
Peça ao Cursor: "Faça meu scraper Puppeteer se comportar mais como um humano. Adicione movimentos de mouse aleatórios, velocidades de rolagem variáveis e atrasos aleatórios entre ações." O Cursor gerará as funções de imitação de comportamento e as integrará ao seu fluxo de scraping.
Extração com IA no Cursor
O assistente de IA do Cursor pode gerar lógica de scraping a partir de descrições de páginas, identificar seletores a partir de trechos de HTML e depurar extrações falhas.
Gerando Seletores a partir de HTML
Abra o Cursor Chat (Ctrl+L) e cole um trecho de HTML:
Here is a product card's HTML structure:
<div class="product-item" data-id="123">
<img src="/img/product.jpg" alt="Product Name" />
<h3 class="product-title">Product Name</h3>
<span class="price-current">$29.99</span>
<span class="price-original">$49.99</span>
<span class="rating" data-rating="4.5">4.5 stars</span>
<span class="stock">In Stock</span>
</div>
Generate a Cheerio extraction function with a typed interface.
Include: productId, name, currentPrice, originalPrice, rating, stockStatus, imageUrl.
O Cursor gerará:
interface ScrapedProduct {
productId: string;
name: string;
currentPrice: number;
originalPrice: number;
rating: number;
stockStatus: string;
imageUrl: string;
}
function extractProduct($: cheerio.CheerioAPI, element: any): ScrapedProduct {
const $el = $(element);
const parsePrice = (sel: string) =>
parseFloat($el.find(sel).text().replace(/[^0-9.]/g, '')) || 0;
return {
productId: $el.attr('data-id') ?? '',
name: $el.find('.product-title').text().trim(),
currentPrice: parsePrice('.price-current'),
originalPrice: parsePrice('.price-original'),
rating: parseFloat($el.find('.rating').attr('data-rating') ?? '0') || 0,
stockStatus: $el.find('.stock').text().trim(),
imageUrl: $el.find('img').attr('src') ?? '',
};
}
Ao pedir ao Cursor para gerar seletores, sempre cole uma amostra de HTML representativa. Isso garante que os seletores correspondam à estrutura DOM real em vez de um palpite. O Cursor também identificará casos extremos como campos opcionais ausentes e adicionará fallbacks.
Depuração com IA
Quando seu scraper retorna resultados vazios, peça ajuda ao Cursor:
My Puppeteer scraper is returning an empty array. The selector
".product-card" should match elements but page.evaluate returns [].
Here is my code. The page loads successfully and I can see the elements
in the browser. What could be wrong?
O Cursor identificará problemas comuns:
- O seletor pode estar dentro de um Shadow DOM.
- O conteúdo pode estar em um iframe.
- A página pode usar um seletor diferente após um redesign recente.
- A opção
waitUntilpode não ser suficiente.
Websites mudam sua estrutura HTML frequentemente. Um scraper que funciona hoje pode quebrar amanhã. Construa seus scrapers defensivamente: use múltiplos seletores de fallback, registre falhas de extração e configure monitoramento para detectar quando seletores param de corresponder.
Melhores Práticas e Ética
Respeitando o robots.txt
async function checkRobotsTxt(url: string): Promise<boolean> {
const robotsUrl = `${new URL(url).origin}/robots.txt`;
try {
const robotsTxt = await (await fetch(robotsUrl)).text();
const path = new URL(url).pathname;
for (const line of robotsTxt.split('\n')) {
if (line.trim().toLowerCase().startsWith('disallow:')) {
const disallowed = line.split(':')[1].trim();
if (disallowed && path.startsWith(disallowed)) return false;
}
}
return true;
} catch {
return true; // Se robots.txt não existe, assume permitido
}
}
Sempre verifique robots.txt antes de fazer scraping. É o protocolo padrão para websites declararem quais páginas podem ser scrapadas. Violar o robots.txt pode levar a banimentos de IP e problemas legais. Use a função checkRobotsTxt acima antes de qualquer operação de scraping.
Rate Limiting
async function rateLimitedDelay() {
const delay = Math.random() * 2000 + 1000; // Atraso de 1-3 segundos
await new Promise((r) => setTimeout(r, delay));
}
// Uso: chame entre cada requisição de página
await rateLimitedDelay();
Conclusão
Web scraping no Cursor combina o poder do Puppeteer, Playwright e Cheerio com geração de código com IA. Cheerio é sua ferramenta para parsing rápido de HTML estático, Puppeteer lida com páginas renderizadas por JavaScript com automação Chrome, e Playwright oferece suporte cross-browser e interseptação de rede para SPAs modernas. Técnicas anti-bot -- plugins stealth, rotação de proxy e comportamento humano -- ajudam você a acessar sites protegidos, enquanto o assistente de IA do Cursor gera seletores a partir de trechos de HTML, depura extrações falhas e escreve pipelines completos de scraping a partir de descrições em linguagem natural. Comece escolhendo a ferramenta certa para seu alvo, construa defensivamente com seletores de fallback e rate limiting, sempre respeite o robots.txt, e deixe o Cursor gerar a lógica de extração para que você possa focar na qualidade dos dados e confiabilidade do pipeline.