Seletores
Quando você faz scraping de páginas web, o objetivo principal é obter informações úteis dessas páginas. Para isso, precisamos extrair esses dados do código HTML da página.
Em Python existem várias bibliotecas disponíveis para isso, como BeautifulSoup e lxml. No entanto, o Scrapy já possui seu próprio mecanismo para extrair dados, chamado de seletores. Eles recebem esse nome porque "selecionam" partes específicas do documento HTML, com base em expressões XPath ou CSS.
Ao utilizar os seletores CSS ou XPath, o spider irá identificar e extrair os dados desejados de cada página de produto, como SKU, nome, preço, descrição, URL e imagem. Essa personalização permite adaptar o processo de extração às nossas necessidades específicas, coletando apenas as informações relevantes para o projeto.
Consultar respostas das requisições usando XPath e CSS é tão comum que a response inclui mais dois atalhos: response.xpath() e response.css().
Considere o seguinte código HTML de uma página de exemplo:
<!DOCTYPE html>
<html>
<head>
<base href='http://example.com/' />
<title>Example website</title>
</head>
<body>
<div id='images'>
<a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' alt='image1'/></a>
<a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' alt='image2'/></a>
<a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' alt='image3'/></a>
<a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' alt='image4'/></a>
<a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' alt='image5'/></a>
</div>
</body>
</html>
Agora vamos abrir o shell do Scrapy para explorar essa página:
scrapy shell https://docs.scrapy.org/en/latest/_static/selectors-sample1.html
Após o shell ser carregado, você terá a resposta da requisição disponível na variável response. O seletor da página estará acessível através do atributo response.selector e você poderá utilizá-lo com expressões CSS ou XPath para extrair dados.
CSS
Os seletores CSS são baseados nas regras usadas para estilização de elementos em HTML.
title::text — Seleciona o texto dentro da tag <title>
#images a — Seleciona todos os links (tags <a>) dentro da tag <div> que possui o id images
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg" alt="image1"></a>', '<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg" alt="image2"></a>', '<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg" alt="image3"></a>', '<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg" alt="image4"></a>', '<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg" alt="image5"></a>']
#images img::attr(src) — Selecionar o atributo src da imagem
#images a::attr(href) — Selecionar o atributo href
XPath
Os seletores XPath são baseados em uma linguagem de consulta que permite navegar e selecionar partes de documentos XML e HTML. O XPath utiliza uma sintaxe específica para localizar nós no documento, permitindo selecionar elementos com base em suas propriedades, hierarquia, e posição na árvore do documento.
Iremos extrair os mesmo elementos anteriores só que agora usando seletores XPath.
//title/text() — Seleciona o texto dentro da tag <title>
//*[@id="images"]//a — Seleciona todos os links (tags <a>) dentro da tag <div> que possui o id images
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg" alt="image1"></a>', '<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg" alt="image2"></a>', '<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg" alt="image3"></a>', '<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg" alt="image4"></a>', '<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg" alt="image5"></a>']
//*[@id="images"]//img/@src — Selecionar o atributo src da imagem
//*[@id="images"]//a/@href — Selecionar o atributo href
No XPath, a função contains pode ser usada para verificar se um nó contém uma substring específica.
//title[contains(text(), "website")]/text() — Selecionar o texto da tag <title> se contém o texto "website"
No CSS, não existe uma função direta equivalente a contains, mas você pode usar seletores de atributos que verificam se um atributo contém uma substring.
a[href*="image"] — Selecionar as URLs (href) de todas as tags <a> cujo atributo href contém a palavra "image"
É possível usar XPath com expressões regulares.
#images a::attr(href) — Selecionar o atributo href
Agora que aprendemos um pouco sobre seletores do Scrapy, estamos prontos para extrair os dados da página da Shop Ecommerce!
Parseando conteúdo HTML
1. Análise a estrutura da página
Primeiro, vamos abrir a página no navegador. Clique com o botão direito do mouse em uma área de produto e selecione a opção Inspect. A seção DevTools será aberta:
2. Escolha de seletores
O selector precisa ser estratégico para poder encontrar os elementos HTML que contém as informações. Analisando a página da Shop Ecommerce notamos que:
li.product: Contém todos os elementos do produto
Para extrair os dados dos produtos temos:
- URL:
<a> - Imagem do produto:
<img> - Nome do produto:
<h2> - Preço do produto:
<span>.product-price
Testando seletores
Vamos testar esses seletores antes de refatorar nosso método parse():
li.product — Seleciona o texto dentro da tag <title>
[<Selector query="descendant-or-self::li[@class and contains(concat(' ', normalize-space(@class), ' '), ' product ')]" data='<li data-products="item" itemscope it...'>, <Selector query="descendant-or-self::li[@class and contains(concat(' ', normalize-space(@class), ' '), ' product ')]" data='<li data-products="item" itemscope it...'>, <Selector query="descendant-or-self::li[@class and contains(concat(' ', normalize-space(@class), ' '), ' product ')]" data='<li data-products="item" itemscope it...'>]
Vamos verificar se ele contém todos os nós do produto
Agora vamos testar a estratégia de seletor em um único produto, o primeiro da lista de resultados.
Agora é sua vez de brincar com os seletores e descobrir como extrair outros dados do produto como imagem, nome e preço.
Refatorando parse()
- Usa o método css() para selecionar todos os elementos que têm a classe
.product - Itera sobre cada produto encontrado para extrair suas informações específicas.
- Captura todos os textos de qualquer elemento dentro do bloco com a classe
.price - Concatena todos os elementos de texto do preço
- Extrai o valor do atributo href da tag
<a>, que representa o link para o produto - Extrai o valor do atributo src da tag
<img>, que contém a URL da imagem do produto - Extrai o texto do elemento
, que provavelmente é o nome do produto
- A função
parseé assíncrona retorna um gerador de objetos para os itens raspados (dados extraídos)
O método parse primeiro obtém todos os nós de produtos usando um selector css(). Em seguida, a função itera sobre os produtos e recupera as informações desejadas em cada um deles.
A função parse é assíncrona e deve retornar um gerador de objetos scrapy.Request ou scrapy.Item usando a instrução yield do Python. Nesse caso, ela retorna geradores para os itens raspados (dados extraídos).
Código completo da spider:
import scrapy
class ShopecommerceSpider(scrapy.Spider):
name = "shopecommerce"
allowed_domains = ["www.scrapingcourse.com"]
start_urls = ["https://www.scrapingcourse.com/ecommerce/"]
def parse(self, response):
products = response.css(".product")
for product in products:
price_text_elements = product.css(".price *::text").getall()
price = "".join(price_text_elements)
yield {
"url": product.css("a").attrib["href"],
"image": product.css("img").attrib["src"],
"name": product.css("h2::text").get(),
"price": price,
}
Vamos executar a spider que acabamos de criar e analisar a saída no terminal.
Uuhuu! Parabéns! Você acaba de extrair dados dos produtos Shop Ecommerce e temos um novo desafio para você 🚀
Note que a página (https://www.scrapingcourse.com/ecommerce/) exibe diversos produtos e contém uma paginação. Até o momento, nossa spider está capturando apenas os 16 produtos da primeira página 🧐
Vamos aprimorar a lógica de extração para que a spider siga as URLs de paginação e colete os produtos de todas as páginas disponíveis, garantindo uma raspagem completa!
Seguindo as páginas dos produtos
O objeto scrapy.Request é uma parte central do Scrapy, usado para gerar novas requisições HTTP que serão processadas pela spider. Ele permite que a spider continue navegando para outras páginas e colete mais dados de forma eficiente.
next_page = response.css('a.page-numbers.next::attr(href)').get() # (1)!
if next_page: # (2)!
yield scrapy.Request( # (3)!
url=next_page, callback=self.parse
)
- Utiliza um seletor CSS para localizar o link da próxima página de produtos
- Verifica se a URL da próxima página foi encontrada.
- Se o link da próxima página existir, faz uma nova requisição para essa URL e continua a chamada da função parse, permitindo a captura dos produtos da página seguinte.
Código completo da spider:
Vamos executar a spider e analisar o log de execução:
Pronto! Agora estamos coletando os produtos de todas as páginas e pelos logs podemos ver que temos 188 produtos.
Extraindo mais dados sobre os produtos
A coleta de informações básicas sobre os produtos na página inicial é apenas o primeiro passo. Para obter uma compreensão mais completa e útil dos itens disponíveis, é essencial extrair os dados adicionais nas páginas de detalhes dos produtos. Esses dados podem incluir informações como categoria, descrição, tamanho, cor e outros atributos específicos que podem influenciar a decisão de compra.
Vamos criar um método para fazer o parse da página de detalhe dos produtos. Lembre-se de antes testar os seletores no scrapy shell, assim você consegue explorar o HTML da página e validar se está selecionando corretamente o dado.
Para isso, escolha um dos produtos, analise sua página e experimente. Os seletores que você construir para essa página servirá para a dos outros produtos.
Exemplo:
scrapy shell https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket/
Implemente seletores para capturar até os dados básicos do produto que antes eram coletados pela página inicial. Assim iremos garantir que os dados sejam centralizados e capturados em um único lugar, simplificando o processo e evitando redundância.
Portanto, nosso desafio agora é coletar os dados de sku, nome do produto, categoria, imagem, preço, descrição, tamanho e cor na página de detalhes do produto.
Fica a seu critério escolher se vai preferir fazer com seletores CSS ou XPath.
Uma versão do método parse_detail() usando seletores CSS:
Em seguida vamos refatorar o método parse() para a spider seguir os links de cada produto e coletar as informações de suas respectivas páginas. Ao navegar por essas páginas, as informações serão extraídas e organizadas em um dicionário.
Código completo da spider: