Entendendo o Web Scraping
Web scraping é a técnica de capturar dados de páginas web de forma automatizada. Existem dois termos importantes nesse contexto: web crawling e web scraping. Web crawling é o processo de navegar pelas páginas web, seguindo links e coletando todo o conteúdo, enquanto web scraping é a coleta específica de dados de uma página.
O web crawling é mais complexo, pois envolve gerenciar a navegação, lidar com bloqueios, erros e mudanças na estrutura das páginas. Já o web scraping é mais simples, focado apenas na extração de dados específicos.
Tradicionalmente, as principais ferramentas utilizadas para web scraping são as bibliotecas Python, como BeautifulSoup e Selenium. No entanto, o JavaScript também tem se destacado nesse cenário, com bibliotecas como Puppeteer, Playwright e Cheerio.
Crawlee e Apify: Soluções em JavaScript
Duas ferramentas que têm transformado a forma como lidamos com web scraping em JavaScript são o Crawlee e o Apify. O Crawlee é um framework que abstrai as complexidades do web crawling, permitindo que você se concentre apenas no que você quer coletar (o “what”) e como você quer coletar (o “where”).
O Crawlee utiliza bibliotecas como Puppeteer, Playwright e Cheerio para realizar a navegação e a extração de dados. Ele também traz funcionalidades avançadas, como gerenciamento de proxies, rotação de IPs e tratamento de erros, que são comuns em projetos de web scraping.
Já o Apify é uma plataforma que permite o deploy e a execução de seus crawlers de forma escalável e gerenciada. Com o Apify, você pode subir seus crawlers em uma infraestrutura serverless, sem precisar se preocupar com o provisionamento e a manutenção de servidores.
Vantagens do Crawlee e Apify
O Crawlee traz diversas vantagens em relação às abordagens tradicionais de web scraping em JavaScript:
- Abstração das complexidades do web crawling, permitindo que você se concentre no que e como coletar os dados.
- Integração com bibliotecas populares, como Puppeteer e Playwright, que oferecem uma API simples e poderosa.
- Funcionalidades avançadas, como gerenciamento de proxies, rotação de IPs e tratamento de erros.
- Possibilidade de criar pipelines de processamento de dados, com a integração com o Apify.
- Processamento de filas de requisição com tratamento automático de erros.
- Capacidade nativa de paralelismo no processamento de requisições enfileiradas.
- Funções de ajuda voltadas para resolver os problemas tanto clássicos quanto avançados do webcrawling, como: infinite scroll, 2FA, auto link enqueuer, etc…
Já o Apify oferece as seguintes vantagens:
- Implantação e execução de crawlers de forma escalável e gerenciada, sem a necessidade de provisionar e manter servidores.
- Integração com o Crawlee, permitindo uma experiência de desenvolvimento e implantação fluida.
- Banco de dados baseado em dataset nativo e integrado com crawlee.
- Funcionalidade nativa de criar uma API para coletar os dados do seu crawler.
- Serviços adicionais, como resolução de captchas e proxy rotativo, que ajudam a lidar com os desafios do web scraping.
- Plano gratuito extenso, com USD 5,00$ gratuitos mensamente em sua conta.
- Possibilidade de monetizar seus crawlers, oferecendo-os como serviço para outras empresas
Criando meu primeiro crawler com Crawlee
A primeira coisa que precisamos fazer é criar o nosso primeiro projeto com crawlee, utilizando o comando:
npx crawlee create amazon-crawler //substitua pelo nome do seu crawler
após isso, ele irá perguntar qual o template estaremos utilizando para fazer o setup do nosso projeto, para esse exemplo, estaremos selecionando o template PlaywrightCrawler [Typescript]
que nos trará uma estrutura com 2 arquivos .ts sendo eles:
// main.ts
// For more information, see https://crawlee.dev/
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
import { router } from './routes.js';
const startUrls = ['https://crawlee.dev'];
const crawler = new PlaywrightCrawler({
// proxyConfiguration: new ProxyConfiguration({ proxyUrls: ['...'] }),
requestHandler: router,
// Comment this option to scrape the full website.
maxRequestsPerCrawl: 20,
});
await crawler.run(startUrls);
Arquivo principal com a execução do crawler, as definições mestras e importação das rotas que executam as requisições
// routes.ts
import { createPlaywrightRouter } from 'crawlee';
export const router = createPlaywrightRouter();
router.addDefaultHandler(async ({ enqueueLinks, log }) => {
log.info(`enqueueing new URLs`);
await enqueueLinks({
globs: ['https://crawlee.dev/**'],
label: 'detail',
});
});
router.addHandler('detail', async ({ request, page, log, pushData }) => {
const title = await page.title();
log.info(`${title}`, { url: request.loadedUrl });
await pushData({
url: request.loadedUrl,
title,
});
});
Arquivo responsável por registrar as rotas, sistema que abstrai o conceito de processadores de coleta.
Ele utiliza esse conceito de roteamento para se assemelhar aos frameworks de criação de APIs rest criando a ideia que cada operação de crawling (“what”) é completamente independente da nossa fila de requisições (“where”), ou seja, independente de quais rotas eu venha a inserir na minha fila, ele irá executar a coleta e caso venha a dar erro na operação, tudo que ele fará é informar a minha fila de requisições que foi impossível efetuar a coleta e automaticamente a requisição que falhou voltará para fila e será processada de forma independente.
O exemplo acima efetua as seguintes operações:
- Acessa a url inicial https://crawlee.dev sem especificar um
handler
especifico, o que leva o crawler a executar odefaultHandler
- Logo em seguida o
defaultHandler
enfileira todos os links da paginas que atendam ao padrão"https://crawlee.dev/**"
para serem executadas pelo handler"detail"
. - O handler
"detail"
coleta os titulos de todas as paginas que foram enfileiradas nele e utilizando a funçãopushData
ele irá utilizar a funcionalidade nativa deDatasets
que funciona como um banco de dados onde serão armazenados os dados do crawler de forma inteligente, impedindo repetição de dados ou inconsistência. - No final da execução de cada requisição, serão salvos os títulos das páginas e as suas respectivas urls em arquivos dentro de
/storage/datasets/default
em arquivos JSON. que podem ser consumidos das mais diversas formas, desde diretamente por meio dos arquivos, ou até mesmo sendo servidos por uma API REST como no exemplo abaixo.
// main.ts
// For more information, see https://crawlee.dev/
import { PlaywrightCrawler } from 'crawlee';
import express from 'express';
import { router } from './routes.js';
const startUrls = ['https://crawlee.dev'];
const crawler = new PlaywrightCrawler({
// proxyConfiguration: new ProxyConfiguration({ proxyUrls: ['...'] }),
requestHandler: router,
// Comment this option to scrape the full website.
maxRequestsPerCrawl: 20,
});
// Create an express server to get the results from the previous crawl
const app = express();
app.get('/datasets/results',async (req,res)=>{
// Gets the results from the previous crawl
const data = await crawler.getData();
return res.json(data);
})
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
await crawler.run(startUrls);
ao acessar a rota obtemos um resultado similar ao representado abaixo.
{
"count": 21,
"desc": false,
"items": [
{
"url": "https://crawlee.dev/docs/examples",
"title": "Examples | Crawlee"
},
{
"url": "https://crawlee.dev/docs/quick-start",
"title": "Quick Start | Crawlee"
},
{
"url": "https://crawlee.dev/api/core",
"title": "@crawlee/core | API | Crawlee"
},
{
"url": "https://crawlee.dev/api/core/changelog",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/blog",
"title": "Crawlee Blog - learn how to build better scrapers | Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.9/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/next/quick-start",
"title": "Quick Start | Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.8/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.6/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.5/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.7/quick-start",
"title": "Quick Start | Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.3/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.2/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.4/quick-start",
"title": "Quick Start | Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.1/quick-start",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/introduction",
"title": "Introduction | Crawlee"
},
{
"url": "https://crawlee.dev/docs/3.0/quick-start",
"title": "Quick Start | Crawlee"
},
{
"url": "https://crawlee.dev/docs/guides/javascript-rendering",
"title": "JavaScript rendering | Crawlee"
},
{
"url": "https://crawlee.dev/docs/guides/avoid-blocking",
"title": "Crawlee"
},
{
"url": "https://crawlee.dev/docs/guides/typescript-project",
"title": "TypeScript Projects | Crawlee"
},
{
"url": "https://crawlee.dev/docs/guides/cheerio-crawler-guide",
"title": "CheerioCrawler guide | Crawlee"
}
],
"limit": 999999999999,
"offset": 0,
"total": 21
}
Exemplo Prático: Coletando Dados da Amazon
Vamos ver um exemplo de como usar o Crawlee e o Apify para criar um crawler que coleta dados de produtos relacionados a “Zelda” na Amazon.
Primeiramente, definimos a URL inicial da busca na Amazon e a palavra-chave “Zelda”:
import {
CheerioCrawler,
Dataset,
createCheerioRouter
} from "crawlee";
// palavra chave para pesquisa no e-commerce
// cria o roteador do cheerio
const router = createCheerioRouter();
// Labels que serão usadas para identificar os handlers e rotas
const labels = {
PRODUCT: "PRODUCT",
OFFERS: "OFFERS",
}
const keyword = "zelda";
const BASE_URL = "https://www.amazon.com";
const initialRequest = {
url: `${BASE_URL}/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords=${keyword}`,
userData: {
keyword,
},
}
Em seguida, criamos o handler responsável por acessar a página de listagem de produtos e extrair os links para as páginas de cada produto:
// cria o handler inicial que irá extrair os produtos da página de pesquisa
router.addDefaultHandler(async ({ $, request, crawler }) => {
const { keyword } = request.userData;
const products = $('div > div [data-asin]:not([data-asin="""])'); for (const product of products) {
const element = $(product);
const titleElement = $(element.find(".a-text-normal [href]"));
const url = `${BASE_URL}${titleElement.attr("href")}`;
await crawler.addRequests([
{
url,
label: labels.PRODUCT,
userData: {
data: {
title: titleElement.first().text().trim(),
asin: element.attr("data-asin"),
itemUrl: url,
keyword,
}
}
}
]);
}
});
Depois, criamos o handler responsável por acessar a página de detalhes do produto e extrair informações como a descrição:
// cria o handler que irá extrair os dados do produto
router.addHandler(labels.PRODUCT, async ({ $, request, crawler }) => {
const { data } = request.userData;
const element = $("div#productDescription");
await crawler.addRequests([
{
url: `${BASE_URL}/gp/aod/ajax/ref=auto_load_aod?asin=${data.asin}&pc=dp`,
label: labels.OFFERS,
userData: {
data: {
...data,
description: element.text().trim(),
}
}
}
]);
})
Em seguida, criamos o handler responsável por obter as ofertas de cada produto relacionando-os
// cria o handler que irá extrair as ofertas do produto
router.addHandler(labels.OFFERS, async ({ $, request }) => {
const { data } = request.userData;
for (const offer of $("div#aod-offer")) {
const element = $(offer);
await Dataset.pushData({
...data,
sellerName: element
.find('div[id*="soldBy"] a[aria-label]')
.text().trim(),
offer: element.find(".a-price .a-offscreen").text().trim(),
});
}
});
Por fim, iniciamos o crawler e adicionamos a primeira requisição:
const crawler = new CheerioCrawler({
requestHandler: router,
});
// executa o crawler
await crawler.run([
initialRequest
]);
Esse exemplo demonstra como o Crawlee simplifica o processo de web scraping, abstraindo a complexidade da navegação e permitindo que você se concentre na extração dos dados.
Integrando com o Apify
Agora, imagine que você queira implantar esse crawler de forma escalável e gerenciada. É aí que entra o Apify. Basta integrar seu código do Crawlee com o Apify SDK e você pode subir seu crawler na plataforma Apify.
const { Actor } = require('apify');
await Actor.init();
const crawler = new CheerioCrawler({
// Mesmo código do exemplo anterior
});
// executa o crawler
await crawler.run([
initialRequest
]);
await Actor.exit();
Com essa integração, você pode desfrutar de diversos benefícios, como:
- Implantação e execução de crawlers de forma escalável e gerenciada
- Acesso a serviços adicionais, como resolução de captchas e proxy rotativo
- Possibilidade de monetizar seus crawlers, oferecendo-os como serviço para outras empresas
- Integrar os Datasets do Crawlee aos Datasets do Apify, possibilitando a criação de APIs automáticas que consomem os dados dos resultados de execução do crawler
O Crawlee e o Apify representam uma mudança significativa na forma como lidamos com web scraping em JavaScript. Eles simplificam o processo, abstraem a complexidade e oferecem uma experiência de desenvolvimento e implantação mais eficiente e escalável.