50% acharam este documento útil (2 votos)
816 visualizações12 páginas

Web Scraping Com Python - Introdução Ao Scrapy

Este documento fornece uma introdução ao uso do framework Scrapy para extrair informações de páginas da Wikipédia. Ele explica como configurar o ambiente Scrapy, define conceitos como Spiders, Items e Pipelines e demonstra funções avançadas do XPath como descendant-or-self, starts-with e expressões regulares para selecionar elementos da página.

Enviado por

coripheu
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato DOCX, PDF, TXT ou leia on-line no Scribd
50% acharam este documento útil (2 votos)
816 visualizações12 páginas

Web Scraping Com Python - Introdução Ao Scrapy

Este documento fornece uma introdução ao uso do framework Scrapy para extrair informações de páginas da Wikipédia. Ele explica como configurar o ambiente Scrapy, define conceitos como Spiders, Items e Pipelines e demonstra funções avançadas do XPath como descendant-or-self, starts-with e expressões regulares para selecionar elementos da página.

Enviado por

coripheu
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato DOCX, PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 12

Web scraping com python — Introdução

ao Scrapy

Web scraping com python — Introdução ao Scrapy

Aprenda a utilizar o scrapy para extrair informações de páginas da wikipédia.

Aqui no Klipbox utilizamos muito uma ferramenta chamada Scrapy, que é um framework para web
crawling. Ele cuida de muitas coisinhas chatas do scraping, facilita outras, além de ser bastante
completo e open source.

E, dessa vez vou ensinar como utilizá-lo para extrair algumas informações da Wikipedia, além de
avançar em alguns conceitos introduzidos no meu primeiro artigo, Web scraping com python —
 Extraindo informações de um ecommerce. Caso não tenha lido ainda e, seja iniciante no assunto,
recomendo muito a leitura antes desse texto.

Após meu primeiro artigo, algumas pessoas vieram me questionar sobre a ética do scraping, quero
deixar claro que o conteúdo aqui tem o objetivo apenas de ensinar, o que fazer com o conhecimento
vai de cada um, desde que esteja dentro da legalidade.

No entanto, nem sempre o scraping é a melhor forma de adquirir a informação. Muitos sites possuem
apis publicas, você pode por exemplo baixar toda Wikipedia como instruido aqui:
https://en.wikipedia.org/wiki/Wikipedia:Database_download, não precisa bombardear todas as
páginas deles hahaha.

O código utilizado aqui se encontra em https://github.com/hcoura/wikipedia_scrapper

As instruções de linha de comando apresentadas aqui são para um ambiente UNIX, lembre-se de
ajustar de acordo com seu sistema operacional.
Esse artigo faz parte de uma série de artigos sobre scraping em python:

1. Web scraping com python — Extraindo Dados de um Ecommerce

2. Web scraping com python — Introdução ao Scrapy

3. Web scraping com python — Selenium e Javascript

Descrição do projeto
O objetivo desse projeto é extrair título, imagem e primeiro parágrafo de todas os artigos relacionados
à um artigo inicial da Wikipedia. Novamente vamos extrair essa informação para um csv, mas queremos
baixar as imagens localmente para uso futuro.

Preparando o ambiente
Caso queira acompanhar o tutorial enquanto escreve seu próprio código, você pode criar um ambiente,
desde que tenha o pipenv instalado, assim:
pipenv install jupyter notebook scrapy lxml requests

Se optar por clonar o repositório e rodar localmente:


git clone git@github.com:hcoura/wikipedia_scrapper.git
pipenv install

E ative o ambiente:
# Ativa o ambiente criado
pipenv shell

Se você não possui o pipenv instalado, veja aqui mais instruções:


https://medium.com/@henriquecoura_87435/webscraping-com-python-extraindo-dados-de-um-
ecommerce-89c16b622f69#5783

Novas funções do XPATH


Nesse projeto vamos utilizar algumas funções mais avançadas do XPATH, caso precise refrescar a
memória veja a minha introdução.

Novamente, vamos trabalhar com um html hipotético:


<html>

<head></head>

<body>
<div class="wrapper">
Texto Wrapper
<div>
Texto div 1
<a href="#" class="link">Link 1</a>
<a href="#">Link 2</a>
<span class="link_span">Span 1</span>
</div>
<div>
Texto div 2
<p>
<a href="#" class="link">Link 3</a>
</p>
</div>
</div>
<div>
<span>Span 2</span>
<a href="#" class="link">Link 4</a>
</div>
<p class="p_link">
Paragraph 1
</p>
</body>

</html>

Descendant::

A função descendant seleciona todos os elementos filhos que atendam os pré-requisitos após os dois
pontos, por exemplo:

//div[@class=”wrapper”]/descendant::a

Retorna no html acima uma lista com os seguintes elementos:


<a href="#" class="link">Link 1</a>
<a href="#">Link 2</a>
<a href="#" class="link">Link 3</a>

Se quiséssemos as urls desses links:

//div[@class=”wrapper”]/descendant::a/@href

Que retornaria [‘#’, ‘#’, ‘#’,]

Descendant-or-self::

Funciona exatamente como descendant, mas adiciona o próprio elemento. Como exemplo, a
expressão:

//div[@class=”wrapper”]/descendant-or-self::div/text()

Retorna
[
'Texto Wrapper',
'Texto div 1',
'Texto div 2',
]

Ou seja, os textos de todos os divs filhos do div de classe wrapper incluindo ele mesmo.

Wildcard e booleanos(*)

Eu não cheguei a comentar sobre o wildcard na introdução ao XPATH, mas basicamente o asterisco
representa qualquer node(elemento).
A expressão //div[@class=”wrapper”]/descendant-or-self::* selecionaria TODOS os elementos filhos
de wrapper inclusive ele mesmo por exemplo.

Mas podemos filtrar o wildcard com operadores booleanos, a expressão


//div[@class=”wrapper”]/descendant::*[not(self::a)]/text(), selecionaria os textos a seguir:
[
'Texto Wrapper',
'Texto div 1',
'Span 1',
'Texto div 2',
'Link 3'
]

Starts-with(@attr, ‘str’)

A função starts-with seleciona apenas os elementos cujo atributo(primeiro parâmetro) começa pela
string(segundo parâmetro) fornecida. Um exemplo nesse html seria selecionar o texto de tudo que
começa com link:

//*[starts-with(@class, “link”)]/text()

Retorna:
[
'Link 1',
'Span 1',
'Link 3',
'Link 4'
]

Expressões regulares(re:test(@attr, ‘regex’))

Com uma estrutura bem parecida à starts-with, podemos testar por expressões regulares com re:test.
Um exemplo similar ao acima, seria selecionar texto de elementos cuja classe contenha o texto link:

//*[re:test(@class, “.*link.*”)]/text()

Retorna:
[
'Link 1',
'Span 1',
'Link 3',
'Link 4',
'Paragraph 1'
]

Na verdade, existe uma função contains que é mais adequada ao exemplo acima, e funciona
exatamente como starts-with, mas como não utilizarei ela no tutorial, exemplifiquei com o regex aqui.

Uma expressão complexa

Juntando tudo isso, podemos escrever uma expressão bem complexa, como essa:

//body/descendant-or-self::*[(self::a or self::p) and re:test(@class, “.*link.*”)]/text()


Traduzindo para o português, retorne o texto de todos os filhos “p” ou “a” do body, inclusive o body
que possuam uma classe que contenha a palavra link. No caso:
[
'Link 1',
'Link 3',
'Link 4',
'Paragraph 1'
]

Utilizando o Scrapy
Como dito anteriormente, o scrapy é um framework completo para web scraping, nesse primeiro artigo
sobre o scrapy introduzirei os spiders, as “aranhas” que de fato executam o scraping; os
Items/ItemLoaders, que são as classes que o scrapy utiliza para lidar com os objetos extraídos; os
ItemPipelines, classes para execução de funções após a extração do Item.

Iniciamos um projeto no scrapy com o seguinte comando:


scrapy startproject wikipedia

E criamos nosso primeiro spider:


scrapy genspider wiki pt.wikipedia.org

Esse é o spider gerado:

 name — É o nome do spider(como iniciamos ele na linha de comando);

 allowed_domains — A lista de domínios permitidos. Caso não seja definida não haverá restrição
de domínios. Útil quando se está procurando novos links para extração mas não quer sair do
domínio atual por exemplo;

 start_urls — Lista de urls iniciais;

 parse — Callback padrão para parsear a resposta.

No momento esse spider não faz absolutamente nada, vamos mudar a página inicial e extrair a url de
resposta.

Para testar o spider, basta rodá-lo na linha de comando com o comando crawl:
scrapy crawl wiki

No logo você verá uma linha assim:


{‘url’: ‘https://pt.wikipedia.org/wiki/Clube_Atl%C3%A9tico_Mineiro'}

Se você quiser extrair para um arquivo, como um csv por exemplo, basta utilizar o parametro -o do
comando scrapy:
scrapy crawl wiki -o wiki.csv

Extraindo as informações
Esse spider ainda não extrai as informações que nós precisamos, vamos cuidar disso agora. O processo
de exploração para encontrar as expressões utilizadas aqui você encontrar no jupyter notebook no
meu repositório. Recomendo que você rode localmente e interaja com ele, descobrindo expressões
melhores e erros que eu possa ter cometido.

Título

Título no inspetor

O título, como pode-se ver na imagem do inspetor do chrome, está em um h1 com id firstHeading:

O objeto TextResponse, que é o objeto que recebemos no método parse, já possui um método xpath,
não sendo necessário utilizar o lxml aqui. Além disso possui algumas funções úteis como
extract_first()(extrai o primeiro match do xpath) e extract()(extrai todos os matches) que serializam e
retornam os elementos encontrados como uma lista, no caso de extract(), de strings unicode.

Primeiro parágrafo
Parágrafo no inspetor

Ao analisar a imagem do inspetor, percebe-se que o texto de interesse só está presente nos seguintes
elementos:

1. Na própria tag p;

2. Em tags b;

3. Em tags a, em que a url não começa com #cite.

O que gera a seguinte expressão:


‘//div[@class=”mw-parser-output”]/p[1]/descendant-or-self::*[self::a[not(starts-with(@href,
“#cite”))] or self::b or self::p]/text()’

Quebrando em partes:

1. //div[@class=”mw-parser-output”]/p[1] — Primeiro parágrafo;

2. descendant-or-self::* — o node encontrado ou seus filhos que respeitem as regras entre


colchetes;
3. self::a[not(starts-with(@href, “#cite”))] — elementos “a” que não começam com #cite;

4. or self::b or self::p — ou uma node b, ou uma node p.

Atualizando nosso spider:

Imagem principal

Imagem principal no inspetor

Ao analisar o inspetor, percebe-se que a imagem principal é a primeira imagem na tabela dentro da
div de classe mw-parser-output. Como isso o spider fica assim:

 Na verdade, essa expressão pega a primeira imagem na tabela de resumo do artigo. Geralmente
essa é a imagem que eu considero principal no artigo, porém em alguns casos essa imagem
não está presente e essa expressão pegaria qualquer outra imagem nessa tabela,
provavelmente não representando o que gostaríamos, mas para esse tutorial é bom o suficiente.
Pronto, vamos testar nosso spider agora:
scrapy crawl wiki

# O item extraído:

{‘url’: ‘https://pt.wikipedia.org/wiki/Clube_Atl%C3%A9tico_Mineiro', ‘title’: ‘Clube Atlético Mineiro’, ‘paragraph’: ‘O Clube


Atlético Mineiro (conhecido apenas por Atlético e cujo acrônimo é CAM) é um clube brasileiro de futebol sediado na cidade
de Belo Horizonte, Minas Gerais. Fundado em 25 de março de 1908 por um grupo de estudantes, tem como suas cores
tradicionais o preto e o branco. Contudo, o clube teve como primeiro nome . Seu símbolo e alcunha mais popular é o Galo,
mascote oficial no final da década de 1930. O Atlético é um dos clubes mais populares do Brasil.’, ‘image_url’:
‘//upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Atletico_mineiro_galo.png/120px-Atletico_mineiro_galo.png’}
Aparentemente tudo está correto, exceto a image_url que está sem scheme, mas isso vamos corrigir
ao falar de item loaders.

Items
Segundo a documentação do scrapy, os Items fornecem uma api parecida com dicionários python
porém com mais “estrutura”. Além disso, e o que importa para gente aqui, os Items do scrapy podem
ser usados com ItemLoaders para “limpar” nossos dados, em um arquivo mais adequado e para usar
ItemPipelines para execução de funções após a extração.

Normalmente, os Items são declarados no arquivo items.py criado automaticamente pelo comando
startproject. e não passam de uma classe python que determina quais são os campos desse item.
O nosso arquivo items.py ficaria assim:

O objeto Field nada mais é do que o um alias para a classe dict do python, ou seja, um bom e velho
dicionário python.

Com isso atualizamos nosso spider para o seguinte:

Se rodarmos o spider, veremos o mesmo resultado que anteriormente:


scrapy crawl wiki

ItemLoader
Os ItemLoaders, quando são utilizados, são os mecanismos com os quais os Items são populados.

São muito úteis para quando o campo extraído pode estar em mais de um lugar, por exemplo utilizando
duas expressões xpath diferentes, e também, como usaremos aqui, para formatar dados.

Podemos criar ItemLoaders específicos para nossos Items, estendendo a classe ItemLoader e
sobrescrevendo(overriding) os métodos correspondentes. Geralmente declaro essas classes no arquivo
items.py. No nosso caso ficaria assim:
Aqui, definimos que:

1. No padrão(default_input_processor, default_output_processor), pegaremos o primeiro


valor(TakeFirst);

2. Na entrada da url da imagem(image_url_in) adicionaremos o schema(“http:”);

3. E na entrada do parágrafo, juntamos(Join(‘’)) a array de textos, não precisando mais dar o join
no spider.

Atualizando nosso spider para utilizar o ItemLoader:

E ao rodar o spider:
scrapy crawl wiki

Temos o resultado esperado:


{‘image_url’: ‘http://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Atletico_mineiro_galo.png/120px-
Atletico_mineiro_galo.png',
‘paragraph’: ‘O Clube Atlético Mineiro (conhecido apenas por Atlético e cujo ‘
‘acrônimo é CAM) é um clube brasileiro de futebol sediado na ‘
‘cidade de Belo Horizonte, Minas Gerais. Fundado em 25 de março ‘
‘de 1908 por um grupo de estudantes, tem como suas cores ‘
‘tradicionais o preto e o branco. Contudo, o clube teve como ‘
‘primeiro nome . Seu símbolo e alcunha mais popular é o Galo, ‘
‘mascote oficial no final da década de 1930. O Atlético é um dos ‘
‘clubes mais populares do Brasil.’,
‘title’: ‘Clube Atlético Mineiro’,
‘url’: ‘https://pt.wikipedia.org/wiki/Clube_Atl%C3%A9tico_Mineiro’}

Note que agora a image_url agora está correta

ItemPipelines
Na extração de informações, só falta uma coisa: baixar as imagens localmente para, caso quisermos
usá-las, não fazermos hotlinking. Para isso vamos utilizar os ItemPipelines.

ItemPipelines nada mais são que classes que definem o método process_item, nos quais serão
passados os Items. Aqui podemos adicionar campos aos Items, descartar duplicados, validar dados,
limpar html, etc.

Primeiro adicionamos um campo no nosso item chamado image:


class Article(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
paragraph = scrapy.Field()
image_url = scrapy.Field()
image = scrapy.Field()

E editamos o arquivo pipelines.py(gerado automaticamente pelo comando startproject) para baixarmos


as imagens:

Se rodarmos nosso spider nesse momento nada acontecerá por que esse pipeline não está configurado
para ser utilizado. Para isso precisamos editar o arquivo settings.py adicionando a configuração dos
ItemPipelines, o novo settings.py fica assim:
# -*- coding: utf-8 -*-
BOT_NAME = 'wikipedia'

SPIDER_MODULES = ['wikipedia.spiders']
NEWSPIDER_MODULE = 'wikipedia.spiders'
ROBOTSTXT_OBEY = True

ITEM_PIPELINES = {
'wikipedia.pipelines.WikipediaPipeline': 300,
}

 O número 300 aqui dita a ordem em que os pipelines irão rodar caso existam mais de um,
quanto menor o número primeiro o item vai passar nele.
 As configurações em settings.py são globais, para todos os spiders, caso necessário, você pode
defini-las na variável custom_settings do spider específico. Essa variável aceita um dict com as
configurações customizadas.
Agora, podemos rodar nosso scrapy que a imagem será baixada para a pasta images, e o novo item
terá o campo image:
scrapy crawl wiki

# O item extraído:

{‘image’: ‘120px-Atletico_mineiro_galo.png’,
‘image_url’: ‘http://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Atletico_mineiro_galo.png/120px-
Atletico_mineiro_galo.png',
‘paragraph’: ‘O Clube Atlético Mineiro (conhecido apenas por Atlético e cujo ‘
‘acrônimo é CAM) é um clube brasileiro de futebol sediado na ‘
‘cidade de Belo Horizonte, Minas Gerais. Fundado em 25 de março ‘
‘de 1908 por um grupo de estudantes, tem como suas cores ‘
‘tradicionais o preto e o branco. Contudo, o clube teve como ‘
‘primeiro nome . Seu símbolo e alcunha mais popular é o Galo, ‘
‘mascote oficial no final da década de 1930. O Atlético é um dos ‘
‘clubes mais populares do Brasil.’,
‘title’: ‘Clube Atlético Mineiro’,
‘url’: ‘https://pt.wikipedia.org/wiki/Clube_Atl%C3%A9tico_Mineiro’}

Só nos resta agora, encontrar os links de artigos relacionados e extrair as informações deles.

Encontrando artigos relacionados


Ao investigar bastante o código fonte da página de um artigo da wikipedia, é possível chegar as
seguintes conclusões em relação aos links de artigos relacionados:

1. As urls são relativas;

2. As urls começam com /wiki/ e depois vem o título do artigo;

3. Todos os links estão contidos em um div com id=bodyContent;

4. Não existe ponto e vírgula como nesse exemplo negativo:


‘/wiki/Ficheiro:Atletico_mineiro_galo.png’.

A seguinte expressão xpath extrai todos os links seguindo as regras acima:

‘//div[@id=”bodyContent”]/descendant::a[re:test(@href, “^/wiki/[^:]*$”)]/@href’

É importante notar, que caso você queira usar a função re:test com a função xpath do lxml você
precisa definir o namespace da função, como no exemplo a seguir. No scrapy não é necessário pois já
é fornecido internamente.
html.xpath(‘//div[@id=”bodyContent”]/descendant::a[re:test(@href, “^/wiki/[^:]*$”)]/@href’, namespaces={“re”:
“http://exslt.org/regular-expressions"})

Com isso atualizamos o nosso spider:

É importante aqui, notar algumas coisas:

1. Para a função parse gerar mais de um item, ela deve, no lugar de retornar um item, gerar um
iterador de Requests e/ou Items ou dicts. Por isso utilizo o yield, gerando um iterador de
Requests para as urls encontradas, dessa vez, passando como callback a função parse_article
e o item gerado por parse_article utilizando a resposta inicial de parse;
2. Apenas extraio informações dos 5 primeiros artigos pois, para o propósito aqui, não há
necessidade de extrair os mais de mil artigos relacionados à página do Atlético-mg;

3. Uso de list(set(articles)) para ter uma lista única após tornar as urls absolutas.

Tudo pronto, podemos rodar nosso crawler e extrair tudo para um csv e baixar as imagens
relacionadas.
scrapy crawl wiki -o items.csv

Conclusão
Mesmo para um exemplo de crawler mais simples como esse, pode-se ver como o scrapy pode nos
fornecer a estrutura para manter nosso código simples, claro e de fácil manutenção. Utilizo-o muito no
meu dia a dia no Klipbox e acredito que pode te ajudar bastante em seus projetos de scraping.

Pretendo escrever artigos mais curtos de agora em diante com uma frequência maior, o próximo será
uma introdução à como extrair informações de páginas com javascript. Como já disse antes, esse é
um projeto novo e estou completamente aberto a sugestões ideias e feedbacks, só comentar abaixo!

E se eu te ajudei de alguma forma não se esqueça de clicar no 💚 abaixo.

 Python

 Scrapy

 Scraping

 Brasil

 Dados

Você também pode gostar