Ryan Mitchell

Propaganda
Ryan Mitchell
Novatec
Authorized Portuguese translation of the English edition of titled Web Scraping with Python, ISBN
9781491910290 © 2015 Ryan Mitchell. This translation is published and sold by permission of O'Reilly
Media, Inc., the owner of all rights to publish and sell the same.
Tradução em português autorizada da edição em inglês da obra Web Scraping with Python, ISBN
9781491910290 © 2015 Ryan Mitchell. Esta tradução é publicada e vendida com a permissão da O'Reilly
Media, Inc., detentora de todos os direitos para publicação e venda desta obra.
© Novatec Editora Ltda. 2015.
Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. É proibida a reprodução desta
obra, mesmo parcial, por qualquer processo, sem prévia autorização, por escrito, do autor e da Editora.
Editor: Rubens Prates
Tradução: Aldir José Coelho Corrêa da Silva
Revisão gramatical: Marta Almeida de Sá
Assistente editorial: Priscila A. Yoshimatsu
Editoração eletrônica: Carolina Kuwabata
ISBN: 978-85-7522-447-2 IG20150806
Histórico de impressões:
Agosto/2015
Primeira edição
Novatec Editora Ltda.
Rua Luís Antônio dos Santos 110
02460-000 – São Paulo, SP – Brasil
Tel.: +55 11 2959-6529
Email: [email protected]
Site: www.novatec.com.br
Twitter: twitter.com/novateceditora
Facebook: facebook.com/novatec
LinkedIn: linkedin.com/in/novatec
capítulo 1
Seu primeiro web scraper
Uma vez que você começar a executar o web scraping, passará a apreciar
todas as pequenas coisas que os navegadores fazem para nós. Inicialmente,
sem uma camada de formatação HTML, estilização CSS, execução de
JavaScript e geração de imagens, a Web pode parecer um pouco intimidante, mas neste capítulo, assim como no próximo, abordaremos como
formatar e interpretar dados sem a ajuda de um navegador.
O capítulo começará com os aspectos básicos do envio de uma solicitação
GET para um servidor web em busca de uma página específica, a leitura
da saída HTML dessa página e a alguma extração de dados simples para
isolarmos o conteúdo que procuramos.
Conectando-se
Se você ainda não dedicou tempo suficiente ao uso de redes, ou à segurança
destas, o funcionamento da Internet pode parecer um pouco misterioso.
Não queremos saber o que, exatamente, a rede faz sempre que abrimos
um navegador e acessamos http://google.com, nem precisamos mais saber.
Na verdade, acho fantástico as interfaces de computador terem avançado
a ponto de a maioria das pessoas que usam a Internet não ter a mínima
noção de como ela funciona.
No entanto o web scraping demanda a remoção de parte dessa camada
de interface não só no nível do navegador (como ele interpreta HTML,
CSS e JavaScript), mas ocasionalmente no nível da conexão de rede.
18
Capítulo 1 ■ Seu primeiro web scraper
19
Para você ter uma ideia da infraestrutura necessária no recebimento de
informações em seu navegador, usaremos o exemplo a seguir. Alice tem
um servidor web. Bob usa um computador desktop que está tentando
se conectar ao servidor de Alice. Quando uma máquina quer conversar
com outra, algo como a interação abaixo ocorre:
1. O computador de Bob envia um fluxo de bits 1 e 0, indicados pelas
voltagens alta e baixa em um fio. Esses bits compõem alguma informação contendo um cabeçalho e um corpo. O cabeçalho contém
como destino imediato o endereço MAC do roteador local; o destino
final é o endereço IP de Alice. O corpo contém a solicitação feita
ao aplicativo de servidor de Alice.
2. O roteador local de Bob recebe todos esses uns e zeros e os interpreta como um pacote, proveniente do endereço MAC do próprio
Bob e destinado ao endereço IP de Alice. Ele grava seu endereço IP
no pacote como sendo o do emitente e o envia pela Internet.
3. O pacote de Bob percorre vários servidores intermediários que
o direcionam pelo caminho físico/interconectado correto, até o
servidor de Alice.
4. O servidor de Alice recebe o pacote em seu endereço IP.
5. Ele lê a porta de destino do pacote (quase sempre a porta 80 para
aplicativos web, que pode ser considerada como um “número de
apartamento” para dados de pacotes, onde o endereço IP seria o
“endereço da rua”) no cabeçalho e passa-o para o aplicativo apropriado – o aplicativo de servidor web.
6. O aplicativo de servidor web recebe um fluxo de dados do processador do servidor. Esses dados dizem algo como:
– Essa é uma solicitação GET.
– O arquivo a seguir é solicitado: index.html.
7. O servidor web localiza o arquivo HTML correto, o insere em um novo
pacote para ser enviado para Bob e o envia por meio de seu roteador
local, retornando-o para a máquina de Bob pelo mesmo processo.
Voilà! Isso é a Internet.
20
Web Scraping com Python
Mas em que momento dessa interação o navegador web desempenhou
algum papel? Em absolutamente nenhum. Os navegadores são uma invenção relativamente recente na história da Internet, da época em que o
Nexus foi lançado, em 1990.
Sim, o navegador web é um aplicativo muito útil para criar esses pacotes
de informações, para enviá-los e para interpretar os dados que nos são
retornados como bonitas imagens, sons, vídeos e texto. No entanto ele é
apenas código, e os códigos podem ser desmontados, separados em seus
componentes básicos, reescritos, reutilizados e fazer o que quisermos que
façam. Um navegador web pode solicitar ao processador que envie dados
para o aplicativo que manipula a interface sem (ou com) fio, mas muitas
linguagens têm bibliotecas que fazem isso igualmente bem.
Vejamos como é feito em Python:
from urllib.request import urlopen
html = urlopen("http://pythonscraping.com/pages/page1.html")
print(html.read())
Você pode salvar esse código como scrapetest.py e executá-lo em seu terminal usando o comando:
$python scrapetest.py
É bom ressaltar que, se você também tiver Python 2.x instalado em sua
máquina, talvez tenha de chamar explicitamente Python 3.x executando
o comando desta forma:
$python3 scrapetest.py
Ele exibirá o código HTML completo da página que fica em
http://bit.ly/1QjYgcd. Mais precisamente, exibirá o arquivo HTML page1.html
do diretório <web root>/pages, que fica no servidor localizado no nome
de domínio http://pythonscraping.com.
Qual é a diferença? A maioria das páginas web modernas tem muitos
arquivos de recursos associados a elas. Podem ser arquivos de imagem,
arquivos JavaScript, arquivos CSS ou qualquer outro conteúdo ao qual
a página em que você está interessado esteja vinculada. Quando um
navegador web chega a uma tag como <img src="cuteKitten.jpg">, ele sabe
que precisa fazer outra solicitação ao servidor para obter os dados do
Capítulo 1 ■ Seu primeiro web scraper
21
arquivo cuteKitten.jpg e gerar a página inteira para o usuário. Lembre-se:
nosso script Python (ainda) não tem a lógica que solicita vários arquivos
e só pode ler o arquivo HTML que solicitamos.
Então como ele faz? Já que Python usa o idioma inglês nativo, a linha
from urllib.request import urlopen
significa o que parece: examina o módulo Python request (encontrado
dentro da biblioteca urllib) e importa apenas a função urlopen.
urllib or urllib2?
Se você já usou a biblioteca urllib2 de Python 2.x, deve ter notado
que as coisas mudaram um pouco entre o urllib2 e o urllib. Em
Python 3.x, o urllib2 foi renomeado como urllib e dividido em
vários submódulos: urllib.request, urllib.parse e urllib.error. Embora
os nomes de funções permaneçam quase todos iguais, verifique
quais funções passaram para submódulos ao usar o novo urllib.
O urllib é uma biblioteca Python padrão (o que significa que você não
precisa instalar outro recurso para executar esse exemplo) e contém funções para a solicitação de dados na Web, a manipulação de cookies e até
a alteração de metadados como cabeçalhos e o agente do usuário. Ela será
usada extensamente em todo o livro, logo, recomendamos que você leia a
documentação Python relacionada (http://bit.ly/1FncvYE).
A função urlopen é usada para abrir um objeto remoto por meio de uma
rede e lê-lo. Já que a biblioteca é bem genérica (pode ler arquivos HTML,
arquivos de imagem ou qualquer outro fluxo de arquivo com facilidade),
faremos uso dela com muita frequência no decorrer do livro.
Introdução ao BeautifulSoup
“Linda Sopa, tão rica e verdinha,
Assentada em uma quente terrina!
Quem não se entregaria a tamanha iguaria?
Sopa noturna, sopa tão linda!”
O nome da biblioteca BeautifulSoup vem de um poema de mesmo nome
de Lewis Carroll encontrado em Alice’s Adventures in Wonderland. Na
22
Web Scraping com Python
história, o poema é cantado por um personagem chamado Mock Turtle
(sendo ele também um trocadilho que usa o popular prato vitoriano Mock
Turtle Soup feito não de tartaruga, mas sim de carne de vaca).
Como seu homônimo no País das Maravilhas, o BeautifulSoup tenta dar
sentido ao que não o tem; ele ajuda a formatar e organizar a confusa Web
corrigindo HTML inválido e nos apresentando objetos Python facilmente
examináveis que representam estruturas XML.
Instalando o BeautifulSoup
Já que a biblioteca BeautifulSoup não é uma biblioteca Python padrão,
ela deve ser instalada. Usaremos a biblioteca BeautifulSoup (também
conhecida como BS4) em todo o livro. As instruções completas para a
instalação do BeautifulSoup 4 podem ser encontradas no site Crummy.
com; no entanto o método básico para o Linux é:
$sudo apt-get install python-bs4
e no Mac:
$sudo easy_install pip
Esse comando instala o gerenciador de pacotes Python pip. Na sequência,
execute o comando a seguir:
$pip install beautifulsoup4
para instalar a biblioteca.
Novamente, lembre-se de que, se você tiver as versões de Python 2.x e 3.x
instaladas em sua máquina, pode ser preciso chamar python3 explicitamente:
$python3 myScript.py
Certifique-se também de usar o comando abaixo quando instalar pacotes,
ou eles podem ser instalados em Python 2.x, mas não em Python 3.x:
$sudo python3 setup.py install
Usando o pip, você também pode chamar pip3 para instalar as versões
dos pacotes disponibilizadas por Python 3.x:
$pip3 install beautifulsoup4
Capítulo 1 ■ Seu primeiro web scraper
23
A instalação de pacotes no Windows é quase idêntica ao processo no
Mac e no Linux. Baixe a versão mais recente do BeautifulSoup a partir
do URL de download acima, navegue até o diretório no qual o descompactou e execute:
>python setup.py install
Isso é tudo! Agora o BeautifulSoup será reconhecido como uma biblioteca
Python em sua máquina. Você pode verificar isso abrindo um terminal
Python e importando-a:
$python
> from bs4 import BeautifulSoup
A importação deve ser concluída sem erros.
Também há um instalador .exe para o pip no Windows para que você
possa instalar e gerenciar pacotes facilmente:
>pip install beautifulsoup4
Mantendo as bibliotecas separadas com ambientes virtuais
Se você pretende trabalhar em vários projetos Python, se precisa de
uma maneira de agrupar os projetos facilmente com todas as bibliotecas associadas ou está preocupado com possíveis conflitos entre as
bibliotecas instaladas, pode instalar um ambiente virtual Python para
manter tudo separado e facilitar o gerenciamento.
Quando você instala uma biblioteca Python sem um ambiente virtual, está instalando-a globalmente. Geralmente isso requer que seja
um administrador ou entre como root e que exista uma biblioteca
Python para cada usuário e para cada projeto existentes na máquina.
Felizmente, é fácil criar um ambiente virtual:
$ virtualenv scrapingEnv
Esse comando cria um novo ambiente chamado scrapingEnv, que
você deve ativar para usar:
$ cd scrapingEnv/
$ source bin/activate
24
Web Scraping com Python
Após o ambiente ser ativado, seu nome aparecerá no prompt de linha
de comando, lembrando-o de que atualmente você está trabalhando
com ele. Qualquer biblioteca que você instalar ou script que executar
só poderá ser encontrado nesse ambiente virtual.
Trabalhando no ambiente recém-criado scrapingEnv, posso instalar
e usar o BeautifulSoup, por exemplo:
(scrapingEnv)ryan$ pip install beautifulsoup4
(scrapingEnv)ryan$ python
> from bs4 import BeautifulSoup
>
Posso deixar o ambiente virtual com o comando de desativação;
depois disso, não poderei acessar mais nenhuma biblioteca instalada
dentro dele:
(scrapingEnv)ryan$ deactivate
ryan$ python
> from bs4 import BeautifulSoup
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named 'bs4'
Manter todas as bibliotecas separadas por projeto também ajuda
a fechar a pasta inteira do ambiente e enviá-la para outras pessoas.
Contanto que elas tenham a mesma versão de Python instalada em
suas máquinas, o código funcionará a partir do ambiente virtual sem
demandar que elas instalem bibliotecas por conta própria.
Não vamos instruí-lo a usar um ambiente virtual em todos os exemplos do livro, mas lembre-se de que você pode aplicá-lo quando quiser
tendo apenas que ativá-lo antecipadamente.
Executando o BeautifulSoup
O objeto mais usado da biblioteca BeautifulSoup é, apropriadamente,
o objeto BeautifulSoup. Vamos vê-lo em ação modificando o exemplo
encontrado no começo deste capítulo:
Capítulo 1 ■ Seu primeiro web scraper
25
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
bsObj = BeautifulSoup(html.read());
print(bsObj.h1)
A saída é:
<h1>An Interesting Title</h1>
Como no exemplo anterior, estamos importando a função urlopen e
chamando html.read() para acessar o conteúdo HTML da página. O conteúdo HTML é então transformado em um objeto BeautifulSoup, com a
estrutura a seguir:
• html → <html><head>...</head><body>...</body></html>
—head → <head><title>A Useful Page<title></head>
— title → <title>A Useful Page</title>
—body → <body><h1>An Int...</h1><div>Lorem ip...</div></body>
— h1 → <h1>An Interesting Title</h1>
— div → <div>Lorem Ipsum dolor...</div>
Observe que a tag <h1> que extraímos da página foi aninhada a duas
camadas de profundidade na estrutura de nosso objeto BeautifulSoup
(html → body → h1). No entanto, quando a acessamos no objeto, ela é
chamada diretamente:
bsObj.h1
Na verdade, qualquer uma das chamadas de função a seguir produziria
a mesma saída.
bsObj.html.body.h1
bsObj.body.h1
bsObj.html.h1
Esperamos que essa pequena amostra da função BeautifulSoup tenha lhe
dado uma ideia do poder e da simplicidade dessa biblioteca. Praticamente
qualquer informação pode ser extraída de arquivos HTML (ou XML),
contanto que tenha sido inserida em uma tag identificadora ou esteja
próxima a uma. No capítulo 3, nos aprofundaremos em algumas chamadas
mais complexas da função BeautifulSoup, além de examinar as expressões
26
Web Scraping com Python
regulares e verificar como elas podem ser usadas com o BeautifulSoup
na extração de informações a partir de sites.
Conectando-se de maneira confiável
A Web é uma bagunça. Os dados são mal formatados, os sites ficam inativos e faltam tags de fechamento. Uma das experiências mais frustrantes
no web scraping é ir dormir com um scraper sendo executado, sonhando
com todos os dados que teremos no banco de dados no dia seguinte, e
descobrir que ele encontrou um erro em algum formato de dados inesperado e parou de ser executado no momento em que deixamos de olhar
para a tela. Em situações como esta, você pode ficar tentado a xingar o
desenvolvedor que criou o site (e os dados mal formatados), mas deve
xingar a si próprio por não prever a exceção!
Examinaremos a primeira linha de nosso scraper, após as instruções de
importação, e pensaremos em como manipular qualquer exceção que
ela possa lançar:
html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
Há duas coisas importantes que podem dar errado nessa linha:
• a página não ser encontrada no servidor (ou ocorrer algum erro na
sua recuperação);
• o servidor não ser encontrado.
Na primeira situação, uma mensagem de erro HTTP será retornada.
Essa mensagem pode ser “404 Page Not Found,” “500 Internal Server
Error”, etc. Em todos os casos, a função urlopen lançará a exceção genérica
“HTTPError”. Podemos manipulá-la da seguinte forma:
try:
html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
except HTTPError as e:
print(e)
#retorna null, break ou executa algum outro 'Plano B'
else:
#o programa continua. Nota: se você retornar ou sair na
#captura da exceção, não precisará usar a instrução "else"
Capítulo 1 ■ Seu primeiro web scraper
27
Se um código de erro HTTP for retornado, então o programa exibirá o
erro e não executará o resto do programa que vem após a instrução else.
Se o servidor não for encontrado (se, digamos, http://www.pythonscraping.com
estiver inativo ou o URL for digitado incorretamente), urlopen retornará
um objeto None. Esse objeto é análogo ao valor null de outras linguagens
de programação. Podemos adicionar uma verificação para saber se o
HTML retornado é None:
if html is None:
print("URL is not found")
else:
#o programa continua
É claro que, se a página for recuperada com sucesso no servidor, ainda
haverá a questão de o conteúdo não ser o esperado. Ao acessar uma tag
em um objeto BeautifulSoup, devemos adicionar uma verificação para saber se ela realmente existe. Se você tentar acessar uma tag que não exista,
BeautifulSoup retornará um objeto None. O problema é que tentar acessar
uma tag em um objeto None resultará no lançamento de um AttributeError.
A linha a seguir (em que nonExistentTag é uma tag inventada, e não o nome
de uma função BeautifulSoup real):
print(bsObj.nonExistentTag)
retorna um objeto None. É perfeitamente normal esse objeto ser manipulado e verificado. O problema surge quando não o verificamos e tentamos
chamar nele alguma outra função, como ilustrado na linha abaixo:
print(bsObj.nonExistentTag.someTag)
que retorna a exceção:
AttributeError: 'NoneType' object has no attribute 'someTag'
Mas como podemos nos proteger dessas duas situações? A maneira mais
fácil é abordá-las explicitamente:
try:
badContent = bsObj.nonExistingTag.anotherTag
except AttributeError as e:
print("Tag was not found")
28
Web Scraping com Python
else:
if badContent == None:
print ("Tag was not found")
else:
print(badContent)
Inicialmente parece trabalhosa essa verificação e a manipulação de cada
erro, mas é fácil adicionar alguma reorganização ao código para torná-lo
menos difícil de escrever (e, o mais importante, muito menos difícil de
ler). Por exemplo, o código a seguir é o mesmo scraper escrito de uma
maneira um pouco diferente:
from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
try:
html = urlopen(url)
except HTTPError as e:
return None
try:
bsObj = BeautifulSoup(html.read())
title = bsObj.body.h1
except AttributeError as e:
return None
return title
title = getTitle("http://www.pythonscraping.com/exercises/exercise1.html")
if title == None:
print("Title could not be found")
else:
print(title)
Nesse exemplo, criamos uma função getTitle, que retorna o título da
página ou um objeto None se houver algum problema em sua recuperação.
Dentro de getTitle, estamos procurando um HTTPError, como no exemplo
anterior, e também encapsulamos duas das linhas do BeautifulSoup
dentro de uma única instrução try. Um AttributeError pode ser lançado
Capítulo 1 ■ Seu primeiro web scraper
29
por uma dessas linhas (se o servidor não existir, html será um objeto None,
e html.read() lançará um AttributeError). Na verdade, poderíamos inserir
quantas linhas quiséssemos dentro da mesma instrução try, ou chamar
outra função totalmente nova, que possa lançar um AttributeError a
qualquer momento.
Ao criar scrapers, é importante que você pense no padrão geral de seu
código para que ele manipule exceções e seja ao mesmo tempo legível.
Provavelmente você também vai querer fazer uso pesado da reutilização
de código. A presença de funções genéricas como getSiteHTML e getTitle
(complementadas com uma manipulação de exceções abrangente) facilita
a execução rápida – e confiável – do scraping na Web.
Download