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.