No post anterior, vimos como fazer requisições HTTP utilizando a excelente requests. A maioria dos documentos disponíveis na web estão em formato HTML, o que não é um grande problema quando acessamos via browser, pois ele é quem faz o trabalho sujo de renderizar o documento pra gente. Mas, se quisermos extrair apenas determinados trechos de informações que podem nos ser úteis, precisaremos percorrer o documento HTML em busca dessas informações, o que não é nada fácil, visto que além de pouca gente seguir os padrões para composição de documentos HTML, muitas vezes os criadores dos sites de onde queremos obter as informações não estão nem um pouco preocupados em classificar as informações que ali disponibilizam, deixando os dados soltos em uma barafunda de table, div, p, etc.
Nesse post vamos ver como fazer Web Scraping, que é a extração de dados de páginas web.
Scraping no reddit
Como já mencionei no post anterior, o reddit é um agregador de conteúdo submetido pelos usuários, e tem vários subreddits voltados a assuntos específicos. Por exemplo, o subreddit programming é um dos meus preferidos, pois além de apresentar as notícias mais relevantes, também apresenta artigos técnicos e discussões de altíssima qualidade. Veja a imagem abaixo, que contém a página inicial desse subreddit:
O objetivo desse post é mostrar como fazer um extrator dos principais links submetidos pelos usuários ao reddit programming, usando uma técnica diferente daquela mostrada no final do post anterior.
O resultado esperado é um programinha em linha de comando que gere a seguinte saída:
Descrição do link - URL do link Descrição do link - URL do link Descrição do link - URL do link Descrição do link - URL do link Descrição do link - URL do link
A primeira coisa que temos que fazer quando queremos desenvolver um extrator de informações da web é entender a estrutura dos documentos HTML de onde vamos retirar a informação. Vamos dar uma olhada no código-fonte do reddit:
Na imagem acima, estou usando o developer tools do Chrome para facilitar a visualização das informações do que precisamos no HTML (ctrl+alt+c abre essa ferramenta). Veja que ele já faz o highlighting das informações tanto no código-fonte quanto no documento renderizado. Assim é moleza descobrir o que precisamos extrair do HTML, não? Podemos ver na imagem acima que cada link submetido pelos usuários fica dentro de um elemento p com um atributo class com valor title, como vemos no trecho abaixo (retirado do reddit):
...
<a class="title " href="http://www.ocamlpro.com/blog/2013/03/14/opam-1.0.0.html">
OPAM, a new package manager for OCaml is officially released!
</a>
<span class="domain">
(<a href="http://www.reddit.com/domain/ocamlpro.com/">
ocamlpro.com
</a>)
</span>
...
Dentro do elemento p, temos outro elemento, a, que contém um atributo com a URL em si (href) e que tem como conteúdo o título do link, que também é de nosso interesse. A imagem abaixo mostra em mais detalhes uma parte do conteúdo da página que nos interessa.
Perceba que precisamos extrair da página várias instâncias de informação como essa mostrada acima, pois para cada link submetido teremos uma estrutura como a apresentada acima. Assim, teremos que procurar por todas as ocorrências de links no documento HTML e extrair essas informações de lá.
Agora que já sabemos onde está a informação que precisamos em meio à bagunça do HTML, chegou o desafio principal, que é extrair essa informação usando um programinha. Mas, antes de meter a mão na massa, vamos conhecer a ferramenta que vai nos possibilitar fazer a extração dos dados de uma forma mais prática: a biblioteca BeautifulSoup.
BeautifulSoup
No post anterior, no último trecho de código, mostrei uma forma meio tosca de buscar a informação no HTML. Vou repetir aquele código aqui para você lembrar sobre o que estou falando:
import requests
def cria_lista_links(content):
links = []
for line in content.split('</p>'):
index = line.find('class="title ')
if index != -1:
href_start = line.find('href="', index) + 6
href_end = line.find('"', href_start)
links.append(line[href_start:href_end])
return links
r = requests.get("http://www.reddit.com/r/programming")
print '\n'.join(cria_lista_links(r.content))
É tosca porque não é muito precisa, visto que nossa busca se baseia somente em uma informação textual dentro de um conjunto de linhas com o HTML. Veja que fazemos a busca usando o caractere de " (aspas duplas) ou a string href= como parâmetro. Se o autor da página mudar de aspas duplas para aspas simples, ou de href= para href =, nossa busca já irá parar de funcionar.
A forma mais confiável de fazermos isso é obtida através do parsing (análise sintática) do conteúdo, de forma que ele nos devolva elementos HTML com seus atributos em uma estrutura bem fácil de manipular. Mas, fazer um bom parser para HTML daria bastante trabalho, ainda mais um parser que funcione bem com todas as variações que são usadas em documentos HTML pela web. A boa notícia é que esse parser já existe e está disponível pra gente através da biblioteca BeautifulSoup, que vamos utilizar daqui pra frente.
Analisando código HTML com BeautifulSoup
Considere o HTML apresentado abaixo. Observe com atenção a sua estrutura.
<html>
<head>
<title>Uma página qualquer</title>
</head>
<body>
<div id="content">
<div class="content-title">
Aqui vai um conteúdo
<a class="link" href="http://www.google.com/">qualquer</a>,
um texto sem fundamento algum, escrito sem a mínima
intenção de despertar qualquer interesse por parte
do <a class="link-old"> leitor</a>.
</div>
<div class="content-author">
Escrito por um autor qualquer, que não tinha algo
melhor para escrever.
</div>
<div class="content-data">
16/03/2013
</div>
</div>
<div class="footer">
Nenhum direito reservado.
</div>
</body>
</html>
Para entender melhor o HTML acima, vamos ver a árvore de representação dele:
Agora vamos iniciar um exemplo de análise do HTML acima usando o BeautifulSoup.
import BeautifulSoup
html_data = '''
<html>
<head><title>Uma página qualquer</title></head>
<body>
<div id="content">
<div class="content-title">
Aqui vai um conteúdo
<a class="link" href="http://www.google.com/">qualquer</a>,
um texto sem fundamento algum, escrito sem a mínima
intenção de despertar qualquer interesse por parte
do <a class="link-old"> leitor</a>.
</div>
<div class="content-author">
Escrito por um autor qualquer, que não tinha
algo melhor para escrever.
</div>
<div class="content-data">
16/03/2013
</div>
</div>
<div class="footer">
Nenhum direito reservado.
</div>
</body>
</html>'''
soup = BeautifulSoup.BeautifulSoup(html_data)
*Por praticidade, o conteúdo HTML foi inserido em uma string enorme chamada html_data.
Já podemos extrair informações da página, que foi “parseada” na última linha do trecho acima. Perceba que os atributos existentes em cada elemento possuem os mesmos nomes dos elementos no código HTML (.title por exemplo, se refere ao elemento title da página).
print soup.title #<title>Uma página qualquer</title> print soup.title.text # Uma página qualquer
O atributo text de qualquer elemento sempre retorna o texto que está contido em tal elemento. Podemos também encadear o acesso a elementos. Abaixo, estamos acessando o primeiro div do body do HTML analisado.
print soup.body.div # <div id="content"><div class="content-title">Aqui vai um conteúdo <a class="link" href="http://www.google.com/">qualquer</a>, um texto sem fundamento algum, escrito sem a mínima intenção de despertar qualquer interesse por parte do <a class="link-old"> leitor</a>.</div><div class="content-author">Escrito por um autor qualquer, que não tinha algo melhor para escrever.</div><div class="content-data">16/03/2013</div></div>
Perceba que no trecho de código acima foi impresso somente um dos dois divs que compõem o body (somente o de id content). Olhando para a árvore e para o valor impresso, vemos que foi impressa toda a subárvore do primeiro div do body. Como temos mais de um div dentro do body, quando tentamos acessar o atributo div, nos é retornado somente o primeiro dos divs. Para obter todos os divs do body, podemos fazer assim:
print soup.body.contents # [u'\n', <div id="content"><div class="content-title">Aqui vai um conteúdo <a class="link" href="http://www.google.com/">qualquer</a>, um texto sem fundamento algum, escrito sem a mínima intenção de despertar qualquer interesse por parte do <a class="link-old"> leitor</a>.</div><div class="content-author">Escrito por um autor qualquer, que não tinha algo melhor para escrever.</div><div class="content-data">16/03/2013</div></div>, <div class="footer">Nenhum direito reservado.</div>, u'\n']
O atributo contents de um elemento qualquer, retorna os elementos que são seus filhos na árvore do HTML. Ou então, podemos percorrer os “filhos” de um elemento, usando o generator childGenerator():
for x in soup.body.childGenerator():
print x, '\n'
Como resultado, teremos:
<div id="content"><div class="content-title">Aqui vai um conteúdo <a class="link" href="http://www.google.com/">qualquer</a>, um texto sem fundamento algum, escrito sem a mínima intenção de despertar qualquer interesse por parte do <a class="link-old"> leitor</a>.</div><div class="content-author">Escrito por um autor qualquer, que não tinha algo melhor para escrever.</div><div class="content-data">16/03/2013</div></div <div class="footer">Nenhum direito reservado.</div>
Além de acessar os elementos filhos de uma tag, podemos também acessar o elemento pai através do atributo parent. Aí vai um exemplo:
a = soup.body.div.a print a.parent # <div class="content-title">Aqui vai um conteúdo <a class="link" href="http://www.google.com/">qualquer</a>, um texto sem fundamento algum, escrito sem a mínima intenção de despertar qualquer interesse por parte do <a class="link-old"> leitor</a>.</div>
Como você pode ver, o elemento pai de a é o próprio div que o antecede.
Buscando por elementos específicos
Se quisermos buscar todos os elementos a de uma página HTML, podemos usar o método findAll():
print soup.findAll('a')
# [<a class="link" href="http://www.google.com/">qualquer</a>, <a class="link-old"> leitor</a>]
O método retorna uma lista contendo todas as ocorrências de elementos do tipo procurado. Também podemos obter elementos através de seus atributos id. Em nosso HTML, temos um div com id="content". Podemos obtê-lo com:
print soup.find(id="content") # <div id="content"><div class="content-title">Aqui vai um conteúdo <a class="link" href="http://www.google.com/">qualquer</a>, um texto sem fundamento algum, escrito sem a mínima intenção de despertar qualquer interesse por parte do <a class="link-old"> leitor</a>.</div><div class="content-author">Escrito por um autor qualquer, que não tinha algo melhor para escrever.</div><div class="content-data">16/03/2013</div></div>
Ou então, buscar por elementos que estejam classificados em uma classe específica:
print soup.find(attrs={'class':'footer'})
# <div class="footer">Nenhum direito reservado.</div>
Enfim, dá pra fazer muita coisa usando a BeautifulSoup. Se quiser conhecê-la melhor, veja a documentação: http://www.crummy.com/software/BeautifulSoup/bs4/doc/.
Implementando o scraper do reddit
Como vimos anteriormente, um link no reddit possui a seguinte estrutura:
...
<a class="title " href="http://www.ocamlpro.com/blog/2013/03/14/opam-1.0.0.html">
OPAM, a new package manager for OCaml is officially released!
</a>
<span class="domain">
(<a href="http://www.reddit.com/domain/ocamlpro.com/">
ocamlpro.com
</a>)
</span>
...
Primeiro, precisamos obter o HTML direto do site do reddit:
import requests
import BeautifulSoup
r = requests.get('http://www.reddit.com/r/programming/')
soup = BeautifulSoup.BeautifulSoup(r.content)
Depois, vamos obter cada elemento a que possua o atributo class com valor igual a "title ". Podemos fazer isso usando o método findAll(), percorrendo o resultado e extraindo os atributos que nos interessam (href e text):
for a in soup.findAll('a', attrs={'class': 'title '}):
print "%s - %s" % (a.text, a.get('href'))
Podemos melhorar o código acima, criando uma função que obtenha a lista de links de um subreddit qualquer:
import requests
import BeautifulSoup
def get_links_from(subreddit):
links = []
r = requests.get('http://www.reddit.com/r/%s/' % (subreddit))
if r.status_code != 200:
return None
soup = BeautifulSoup.BeautifulSoup(r.content)
for a in soup.findAll('a', attrs={'class': 'title '}):
links.append((a.text, a.get('href')))
return links
A função acima retorna uma lista contendo tuplas de pares (título do link, URL do link). Assim, podemos percorrer essa lista, imprimindo os valores:
for link_name, link_url in get_links_from('programming'):
print "%s - %s" % (link_name, link_url)
for link_name, link_url in get_links_from('python'):
print "%s - %s" % (link_name, link_url)
Está pronto! Barbada, hein? Que tal agora você começar a estudar aquele site de onde você quer extrair informações e implementar um webscraper?
Boa sorte! 🙂
Leitura adicional
14 comentários sobre “Webscraping em Python”
Deixe um comentário
Você precisa fazer o login para publicar um comentário.



vamos parar de fazer tutoriais que façam os caras sobrecarregarem servidor do reddit?? hah
whoa there, pardner!
you appear to be a search engine spider or are coming to us via
AppEngine. to prevent abuse, we maintain a one request per second hard speed
limit from these sources. please wait a moment and try again.
Pois é, pobre reddit… É alvo de todos tutoriais que escrevo… 😛
Olá, estou tentando utilizar web scraping para pegar o valor do dólar na página do google, no entanto, todas as maneiras que tentei não retornam resultado. Por favor, poderia ajudar?
aqui o código:
#Captura valor do dólar
##############################################################
#biblioteca que manipula html
from requests import *
#biblioteca de parseamento BeautifulSoup 4.4.0
from bs4 import *
#baixa o conteúdo da página descrita pela url para a variável r
r = get(“https://www.google.com.br/#q=dolar”)
#verifica se o retorno de requests.get foi bem sucedido
if (r.status_code != 200):
print (“conexão inválida”)
else:
print(“Prosseguindo…”)
#faz o parse em todo o conteúdo de r
soup = BeautifulSoup(r.content, “html.parser”)
#imprime o conteúdo (valor do dólar) da classe indicada
print (soup.find_all(“div”, attrs = {“class” : “vk_ans vk_bk”}))
print (soup.find_all(“div”, class_=”vk_ans vk_bk”))
print (soup.find_all(string=”reais”))
print (soup.find_all(class_=”vk_ans”))
print (soup.select(“div.vk_ans.vk_bk”))
print (soup.html.find_all(“div”, class_=”vk_ans”))
print (soup.select(“.vk_ans vk_bk”))
Olá, Guilherme.
Aparentemente, o conteúdo da busca retornado pelo Google é carregada via JavaScript. O requests.get() apenas recebe o conteúdo HTML, sem interpretá-lo. Assim, o conteúdo recebido por ele vem repleto de código JS que nunca é executado. Um browser, entretanto, ao encontrar código JavaScript, executa o mesmo. Tal código pode ser o responsável por carregar o div em que a cotação aparece.
Pelo visto, o pessoal usa o PhantomJS para acessar conteúdo carregado via JS: http://kochi-coders.com/2014/05/06/scraping-a-javascript-enabled-web-page-using-beautiful-soup-and-phantomjs/
Obrigado Valdir!
Pesquisei sobre o PhantomJS, mas não compreendi bem, entretanto por meio da sua sugestão descobri a biblioteca selenium, que também interpreta conteúdo JS. Testei e deu certo!
A única coisa ruim do método que utilizei, é que faz necessário o uso de um navegador para carregar o conteúdo da página. Quanto a isso, você tem alguma dica?
meu código ficou assim:
#Captura valor do dólar
##############################################################
#biblioteca que manipula java script
from selenium import webdriver
#biblioteca de parseamento BeautifulSoup 4.4.0
import bs4
#abre o navegador desejado para fazer a busca
browse = webdriver.Firefox()
#Faz a busca pela url no navegador
browse.get(“https://www.google.com.br/#q=dolar”)
#baixa o conteúdo da página descrita pela url para a variável html
html = browse.page_source
#fecha o navegador
browse.quit()
#faz o parse em todo o conteúdo de html
soup = bs4.BeautifulSoup(html, “html.parser”)
#imprime o conteúdo (valor do dólar) da classe indicada
for a in soup.find_all(“div”, attrs = {“class” : “vk_ans vk_bk”}):
print (“%s” %(a.text))
Que tal a lib mechanize [1]?
[1] http://github.com/jjlee/mechanize
Pois é, seria bacana se suportasse python 3, que uso atualmente.
Obrigado novamente, seu trabalho é incrível!
Vou continuar tentando achar uma solução viável enquanto isso.
Guilherme, pelo que vi, o tal do PhantomJS é uma instância “headless” do WebKit (engine de browser). Assim sendo, você pode conectar nele usando o Selenium. Veja se isso ajuda: https://realpython.com/blog/python/headless-selenium-testing-with-python-and-phantomjs/
Olá Valdir! Sua dica de PhantomJs com selenium funcionou bem, obrigado!
Eu estou desenvolvendo um app, portanto precisaria colocá-lo para funcionar em outros computadores e até outras plataformas, como android. Quando eu terminá-lo, como farei para deixá-lo funcionando em outros dispositivos sem precisar configurar tudo manualmente? Digo, há alguma maneira de fazê-lo funcionar em outro computador sem precisar “setar” tudo novamente?
Guilherme, existem várias formas diferentes de fazer o empacotamento do seu software. Sugiro que você faça tal pergunta no http://pt.stackoverflow.com/tags/python para ver o que o pessoal que já teve que fazer empacotamento multiplataforma responde.
Olá,
Achei bem esclarecedor o seu post, mas tenho uma dúvida. E no caso de páginas que você precisa usar um login. Existe alguma forma do Python acessar uma página e fazer o login e após logado extrair outro conteúdo de uma página? Tem um site onde a página quando não estou logado fica assim:
0
0.00
$300.00
E após fazer o login, o código fonte fica assim:
Dishes
0
—
—
$300.00
32 687
200 000
0
0
0.00 %
E o dado que gostaria de extrair justamente aparece quando estou logado no sistema.