O que é realmente o Pyppeteer e como pode usá-lo?
Se está a ler isto, é provável que já esteja familiarizado com o que é o web scripting em geral. E provavelmente já ouviu falar do Puppeteer ou do Selenium, dependendo da sua linguagem de programação preferida. Mas o Pyppeteer é, de facto, mais recente no panorama do web scraping. Bem, para resumir, o Pyppeteer é muito mais parecido com o Puppeteer do que com o Selenium.
O Puppeteer é uma biblioteca Node.js que facilita o controlo de uma versão headless do Chrome através do protocolo DevTools. O Pyppeteer é uma versão em Python do Puppeteer. Tal como o Puppeteer original, o Pyppeteer é uma biblioteca, escrita em Python, que basicamente automatiza um navegador. Por outras palavras, o Pyppeteer é uma implementação em Python da API do Puppeteer, que permite utilizar as funcionalidades do Puppeteer num ambiente Python. A principal diferença entre os dois é a linguagem utilizada.
Terminologia do Pyppeteer que deve conhecer
Antes de prosseguirmos, penso que devemos discutir alguns termos comumente usados no contexto do Pyppeteer:
- Headless: Significa iniciar um navegador sem uma interface gráfica de utilizador (GUI). Por outras palavras, está a funcionar «nos bastidores» e não é possível vê-lo no ecrã. É normalmente utilizado para reduzir o consumo de recursos durante o scraping.
- Headful: Por outro lado, um navegador «headful» é aquele que está a ser executado com uma GUI. É o oposto de um navegador headless e é frequentemente utilizado para testes, depuração ou interação manual com páginas web.
- Contexto do navegador: Trata-se de um estado partilhado por todas as páginas num navegador. É normalmente utilizado para definir configurações a nível do navegador, tais como cookies, cabeçalhos HTTP e geolocalização.
- DOM: O Modelo de Objetos de Documento (DOM) é uma interface de programação para documentos HTML e XML. Representa a estrutura de uma página web num formato em árvore, com nós que representam elementos. O Pyppeteer permite-lhe interagir com os elementos de uma página através da manipulação do DOM.
- Elementos: Os blocos de construção de uma página web. São definidos utilizando tags, atributos e valores.
É claro que há mais para aprender e irá aprender mais ao longo do caminho. Mas queria que tivesse uma noção geral para que tenhamos um começo sólido. Estou convencido de que conhecer estes termos o ajudará a compreender melhor a essência deste artigo.
Porquê usar o Pyppeteer no seu projeto de scraping?
Penso que há dois aspetos nesta questão. O primeiro é por que razão o Pyppeteer é uma boa escolha para o web scraping em geral. O segundo é por que razão usar o Pyppeteer em vez do Selenium. De um modo geral, algumas das vantagens do Pyppeteer incluem:
- Avaliação de JavaScript: o Pyppeteer fornece uma função `page.evaluate()`. Permite-lhe executar código JavaScript no contexto da página.
- Controlo de rede: O Pyppeteer fornece um método `page.on()`. Isto permite-lhe monitorizar eventos de rede, tais como pedidos e respostas, que ocorrem numa página.
- Rastreamento e registo: O Pyppeteer permite-lhe rastrear a atividade do navegador e registar mensagens do navegador a partir de uma página. Isto facilita a depuração, o rastreamento e a compreensão do que um site está a fazer.
Em comparação com o Selenium, é bastante semelhante, na medida em que ambos são utilizados para automatizar um navegador web. No entanto, existem algumas diferenças e vantagens fundamentais que o Pyppeteer tem em relação ao Selenium:
- Simplicidade: O Pyppeteer tem uma API mais simples e consistente do que o Selenium, o que o torna mais fácil de usar para principiantes. A API do Pyppeteer é construída com base no protocolo DevTools, que está próximo do navegador e é fácil de aprender e usar.
- Desempenho: O Pyppeteer pode ser mais rápido do que o Selenium porque é construído com base no protocolo DevTools. O protocolo foi concebido para depurar páginas web e é muito mais rápido do que o Selenium WebDriver.
- Melhor controlo da rede: O Pyppeteer permite um maior controlo sobre as definições de rede do navegador, tais como a interceção de pedidos e o bloqueio de pedidos/respostas. Isto facilita o teste e o diagnóstico de problemas relacionados com a rede.
E, claro, há também a questão da escolha. Veja o meu caso, por exemplo. No dia a dia, programo em JavaScript. E estou bastante familiarizado com o Pyppeteer. Mas, por outro lado, a minha linguagem de programação favorita é o Python. Portanto, se tivesse de criar um scraper com uma tecnologia conhecida numa linguagem que prefiro, provavelmente escolheria o Pyppeteer.
Dito isto, penso que já abordámos os aspetos teóricos deste artigo. Está na hora de começarmos a programar a sério.
Como criar um web scraper com o Pyppeteer
Antes de começarmos a programar, deixem-me apresentar-vos a documentação oficial do Pyppeteer. Sou a favor de usar a documentação oficial sempre que alguém se sentir bloqueado. Isso antes de fazer perguntas na comunidade (como no Stackoverflow). Normalmente, acho que a maioria das respostas pode ser encontrada se simplesmente leres a documentação primeiro. Por isso, considera isto um pedido gentil da minha parte. Sempre que estiveres bloqueado, consulta a documentação, depois procura respostas e só faz perguntas como último recurso.
#1: Configurar o ambiente
Primeiro o mais importante: como programador Python, provavelmente já está familiarizado com ambientes virtuais. Portanto, a primeira coisa que precisamos de fazer é criar um ambiente virtual para o nosso projeto. Esta é, geralmente, a sequência de comandos que utilizo:
# Create a new directory and navigate into it
~ » mkdir py_project && cd py_project
# Create the virtual environment
~ » python3 -m venv env
# Activate the virtual environment
~ » source env/bin/activate
No que diz respeito ao ambiente virtual, já está tudo pronto. É hora de avançar e instalar o Pyppeteer. Como já tem o terminal aberto, basta digitar:
# Install the package using pip
~ » python3 -m pip install pyppeteer
# Open the project in your IDE
~ » code .#2: Criar um scraper Pyppeteer simples
O último comando abre o Visual Studio Code ou o seu IDE preferido. Agora que está no «ambiente de desenvolvimento», vamos criar um novo ficheiro `.py` que irá conter o nosso código. Vou chamar ao meu ficheiro `scraper.py`. Note que o Pyppeteer suporta nativamente a execução assíncrona. Vamos, portanto, importar tanto `asyncio` como `pyppeteer` para o nosso ficheiro:
import asyncio
from pyppeteer import launch
Feito isto, podemos avançar para código mais complexo. Em geral, não sou o maior defensor da programação funcional. No entanto, acho que dividir o código em pequenos pedaços permite uma melhor aprendizagem. Vamos, então, colocar o nosso código dentro de uma função:
async def scrape(url):
browser = await launch()
page = await browser.newPage()
await page.goto(url)
content = await page.content()
await browser.close()
return content
Esta função recebe um URL como entrada e inicia um navegador headless utilizando o Pyppeteer. Em seguida, navega até ao URL fornecido, recupera o conteúdo da página e fecha o navegador. O valor que devolve não é mais do que o HTML recolhido da página. Pode utilizar esta função para extrair dados de praticamente qualquer site. Para utilizar a função, deve chamá-la num ciclo de eventos `asyncio`, desta forma:
async def main():
content = await scrape('https://www.example.com')
print(content)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())#3: Adicionar mais funcionalidades
Até este ponto, temos um scraper funcional. Mas isso é praticamente tudo o que temos. Se quiser construir um scraper web mais avançado com o Pyppeteer, terá de adicionar mais funcionalidades ao id. Alerta de spoiler: vamos mergulhar no mundo da programação orientada a objetos. Mas primeiro, vamos definir os nossos objetivos. O que queremos que o nosso scraper seja capaz de fazer?
- Inicializar o navegador com alguns valores personalizados
- Navegar e extrair conteúdo de uma página web
- Escrever texto num campo de entrada
- Extrair o valor de um único elemento
- Extrair o valor de vários elementos
3.1. Opções personalizadas
Vamos então criar uma nova classe `Scraper` por agora e adicionaremos os seus métodos posteriormente:
class Scraper:
def __init__(self, launch_options: dict) -> None:
self.options = launch_options['options']
self.viewPort = launch_options['viewPort'] if 'viewPort' in launch_options else None
pass
O único argumento que estamos a usar para o nosso Scraper é um dicionário `launch_options`. Como pode ver, ele contém duas chaves. Uma chave define as opções do lançador do Pyppeteer. A segunda opção é `None` ou um dicionário que contém a `width` e a `height` do `viewPort`. Esta última é usada para este método.
3.2. Navegar para uma página
Se observar a função que utilizámos anteriormente, verá que abrangemos tanto a navegação como a extração de dados brutos de um URL específico. A única coisa que precisamos de fazer é ajustar e transformar a função num método para o nosso Scraper:
async def goto(self, url: str) -> None:
self.browser = await launch(options=self.options)
self.page = await self.browser.newPage()
await self.page.setViewport(self.viewPort) if self.viewPort != None else print('[i] Using default viewport')
await self.page.goto(url)
Este método é bastante simples. Primeiro, inicia um novo navegador, com as opções personalizadas que definimos anteriormente. Em seguida, cria uma nova página e, se o nosso dicionário `launch_options` tiver `viewPort`, define o viewPort da página. Caso contrário, regista uma mensagem simples. Por último, mas não menos importante, leva-nos ao destino.
3.3. Extrair dados brutos de uma página
Mais uma vez, temos o método na nossa função `scraper` inicial. Vamos apenas aguardar que o `page.content()` carregue e retorne o seu valor:
async def get_full_content(self) -> str:
content = await self.page.content()
return content
3.4. Escrever texto num campo de entrada
Para escrever algo num campo de entrada usando o Pyppeteer, são necessárias duas coisas. Primeiro, localizar o elemento. Segundo, adicionar algum valor a ele. Felizmente, o Pyppeteer tem métodos para ambas as ações:
async def type_value(self, selector: str, value: str) -> None:
element = await self.page.querySelector(selector)
await element.type(value)
3.5. Extrair valor da página
Lembre-se de que queremos poder extrair tanto o valor de um único elemento como valores de vários elementos. Poderíamos usar um único método para ambos. Mas, normalmente, prefiro manter as coisas separadas. Por isso, por agora, vou adicionar mais dois métodos:
async def extract_one(self, selector) -> str:
element = await self.page.querySelector(selector)
text = await element.getProperty("textContent")
return await text.jsonValue()
Aqui, estamos a localizar o elemento utilizando o método `querySelector`. Em seguida, aguardamos o `textContent` e devolvemos o seu `jsonValue()`. Por outro lado, quando quisermos selecionar muitos elementos, utilizaremos `querySelector`:
async def extract_many(self, selector) -> list:
result = []
elements = await self.page.querySelectorAll(selector)
for element in elements:
text = await element.getProperty("textContent")
result.append(await text.jsonValue())
return result
Este método funciona de forma semelhante ao `extract_one`. A única diferença é o seu valor de retorno. Desta vez, estamos a devolver uma lista de todo o texto dentro dos elementos selecionados. E acho que, com esta adição, atingimos todos os nossos objetivos.
#4: Torne-o furtivo
No web scraping, a discrição pode ser descrita como a capacidade de passar despercebido. É claro que construir um scraper totalmente indetetável dá muito trabalho. Por exemplo, o Modo Stealth da Web Scraping API é mantido por uma equipa dedicada. E o esforço investido nisso torna a impressão digital do nosso scraper única em cada pedido.
Mas o meu objetivo geral para este tutorial é colocá-lo no caminho certo. E o caminho certo para um scraper web completo com o Pyppeteer implica adicionar-lhe alguma funcionalidade de discrição. Felizmente, tal como existe o `puppeteer-extra-plugin-stealth` no Node, existe também um pacote para Python. E chama-se, intuitivamente, `pyppeteer-stealth`. Para o adicionar ao seu projeto, primeiro instale-o usando o pip:
~ » python3 -m pip install pyppeteer_stealth
Depois, importe-o para o seu projeto e basta adicionar uma linha extra de código:
async def goto(self, url: str) -> None:
self.browser = await launch(options=self.options)
self.page = await self.browser.newPage()
# Make it stealthy
await stealth(self.page)
await self.page.setViewport(self.viewPort) if self.viewPort != None else print('[i] Using default viewport')
await self.page.goto(url)
E eis como executa o seu scraper. Adicionei alguns comentários ao código para destacar o que cada passo faz:
async def main():
# Define the launch options dictionary
launch_options = {
'options': {
'headless': False,
'autoClose': True
},
'viewPort': {
'width': 1600,
'height': 900
}
}
# Initialize a new scraper
scraper = Scraper(launch_options)
# Navigae to your target
await scraper.goto('https://russmaxdesign.github.io/accessible-forms/accessible-name-input01.html')
# Type `This is me` inside the input field
await scraper.type_value(
'#fish',
'This is me')
# Scrape the entire page
content = await scraper.get_full_content()
print(content)
# Scrape one single element
el = await scraper.extract_one('body > div:nth-child(14) > ul')
print(el)
# Scrape multiple elements
els = await scraper.extract_many('p')
print(els)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())Conclusão
O Pyppeteer é uma ferramenta fantástica para web scraping. Ele porta toda a API do Puppeteer para Python, permitindo que a comunidade Python utilize esta tecnologia sem ter de aprender JavaScript. Além disso, não acho que seja um substituto do Selenium, mas é certamente uma boa alternativa a este.
Espero que o artigo de hoje tenha acrescentado valor ao teu percurso de aprendizagem. E como gosto de desafiar os limites de todos, desafio-te a aprofundares o que aprendeste hoje. O scraper que construímos juntos é um excelente ponto de partida e introduz um elemento-chave na programação: a OOP. Por isso, desafio-te a adicionares mais métodos ao `Scraper` e torná-lo verdadeiramente incrível.




