BeautifulSoup
Introdução
Um arquivo HTML normalmente possui dezenas de elementos. BeautifulSoup permite montar uma estrutura de dados com esses elementos e fazer a análise de forma rápida e automática. Para montar a estrutura de dados, deve-se usar um analisador. O analisador padrão (vem com o BeautifulSoup) é o “html.parser“.

No exemplo abaixo, é usado o módulo Requests para obter a página oficial do Python; em seguida é usado o “html.parser” para montar a estrutura de dados da página (pode-se também usar outros analisadores como lxml e html5lib); no final, é usado o método prettify() para exibir o código da página de forma estruturada.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") print(pagina.prettify())
São exemplos de métodos do BeautifulSoup:
- findAll() – permite extrair uma lista de objetos tag (é possível especificar o nome da tag e quaisquer atributos da tag);
- find() – retorna a primeira tag encontrada com a especificação dada;
- select() – faz seleção de elementos de acordo com definições CSS.
Exemplos
- Exemplo: usa Requests para obter a página oficial do Python; em seguida, usa o BeautifulSoup com o analisador padrão “html.parser” para criar uma estrutura de dados com os elementos HTML. O método select() é então usado para localizar o título e os três primeiros metadados da página.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") # Título da página titulo = pagina.select('title') print("\nTipo de objeto: " + str(type(titulo))) print("Linha do Título: " + str(titulo)) print("Conteudo do titulo: " + titulo[0].getText()) # Metas da página print('\nMETA 0:' + str(pagina.select('meta')[0])) print('META 1:' + str(pagina.select('meta')[1])) print('META 2:' + str(pagina.select('meta')[2]) + "\n")
A resposta é
Tipo de objeto: <class 'bs4.element.ResultSet'> Linha do Título: [<title>Welcome to Python.org</title>] Conteudo do titulo: Welcome to Python.org META 0:<meta charset="utf-8"/> META 1:<meta content="IE=edge" http-equiv="X-UA-Compatible"/> META 2:<meta content="Python.org" name="application-name"/>
- Exemplo: o método select() encontra múltiplas instâncias para ‘meta’ na página oficial do Python e retorna essas instâncias em uma lista.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") lista = pagina.select('meta') for i in lista: print(i)
A lista de linhas metadados encontradas na página Python é mostrada abaixo.
<meta charset="utf-8"/> <meta content="IE=edge" http-equiv="X-UA-Compatible"/> <meta content="Python.org" name="application-name"/> <meta content="The official home of the Python Programming Language" name="msapplication-tooltip"/> <meta content="Python.org" name="apple-mobile-web-app-title"/> <meta content="yes" name="apple-mobile-web-app-capable"/> <meta content="black" name="apple-mobile-web-app-status-bar-style"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/> <meta content="True" name="HandheldFriendly"/> <meta content="telephone=no" name="format-detection"/> <meta content="on" http-equiv="cleartype"/> <meta content="false" http-equiv="imagetoolbar"/> <meta content="/static/metro-icon-144x144-precomposed.png" name="msapplication-TileImage"/> <meta content="#3673a5" name="msapplication-TileColor"/> <meta content="#3673a5" name="msapplication-navbutton-color"/> <meta content="The official home of the Python Programming Language" name="description"/> <meta content="Python programming language object oriented web free open source software license documentation download community" name="keywords"/> <meta content="website" property="og:type"/> <meta content="Python.org" property="og:site_name"/> <meta content="Welcome to Python.org" property="og:title"/> <meta content="The official home of the Python Programming Language" property="og:description"/> <meta content="https://www.python.org/static/opengraph-icon-200x200.png" property="og:image"/> <meta content="https://www.python.org/static/opengraph-icon-200x200.png" property="og:image:secure_url"/>
- Exemplo: o script obtém informações sobre o título da página Python de duas formas diferentes.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") print("\n*** Primeira forma") titulo = pagina.select('title') print("Tipo de objeto: " + str(type(titulo))) print("Linha do Título: " + str(titulo)) print("Conteudo do título: " + titulo[0].getText()) print("\n*** Segunda forma") print("Nome do elemento HTML: " + str(pagina.title.name)) print("Tag completa: " + str(pagina.title)) print("Conteúdo da tag: " + str(pagina.title.string) + "\n")
A resposta é
*** Primeira forma Tipo de objeto: <class 'bs4.element.ResultSet'> Linha do Título: [<title>Welcome to Python.org</title>] Conteudo do título: Welcome to Python.org *** Segunda forma Nome do elemento HTML: title Tag completa: <title>Welcome to Python.org</title> Conteúdo da tag: Welcome to Python.org
- Exemplo: o programa informa a quantidade de vários elementos HTML na página oficial do Python.
import requests, bs4 r = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(r.text, "html.parser") div = pagina.select('div span') print("\nNúmero de div: " + str(len(div))) img = pagina.select('img') print("\nNúmero de imagens: " + str(len(img))) ul = pagina.select('ul') print("\nNúmero de ul: " + str(len(ul))) li = pagina.select('li') print("\nNúmero de li: " + str(len(li))) span = pagina.select('span') print("\nNúmero de span: " + str(len(span)))
A resposta é mostrada abaixo.
Número de div: 72 Número de imagens: 1 Número de ul: 28 Número de li: 163 Número de span: 72
- Exemplo: o programa usa o método find() para achar o primeiro parágrafo da página oficial Python.
import requests, bs4 r = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(r.text, "html.parser") print('\n*** Primeiro Parágrafo') print(pagina.find('p').text)
O primeiro parágrafo da página é
*** Primeiro Parágrafo Notice: While JavaScript is not essential for this website, your interaction with the content will be limited. Please turn JavaScript on for the full experience.
- Exemplo: o script usa o método find_all() para gerar a lista com todos os parágrafos da página oficial do Python. Depois imprime apenas o primeiro e o terceiro parágrafos.
import requests, bs4 r = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(r.text, "html.parser") paragrafos = pagina.find_all('p') qtde = len(paragrafos) print('\nQuantidade de parágrafos:', qtde) print('\n*** Primeiro Parágrafo') print(paragrafos[0].text) print('\n*** Terceiro Parágrafo') print(paragrafos[2].text)
A resposta é
Quantidade de parágrafos: 23 *** Primeiro Parágrafo Notice: While JavaScript is not essential for this website, your interaction with the content will be limited. Please turn JavaScript on for the full experience. *** Terceiro Parágrafo Lists (known as arrays in other languages) are one of the compound data types that Python understands. Lists can be indexed, sliced and manipulated with other built-in functions. More about lists in Python 3
- Exemplo: o programa verifica a quantidade de links que a página Python possui. Em seguida, lista apenas os links que possuem o valor ‘http://’ no atributo ‘href’ (os links com ‘https’ não entram na seleção).
import requests, re, bs4 r = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(r.text, "html.parser") links = pagina.find_all('a') print('Quantidade de links =', len(links)) links = pagina.findAll('a', attrs={'href': re.compile("^http://")}) print('Quantidade de links com http =', len(links)) print('\n*** Links com http') for link in links: print(link.get('href'))
O método re.compile() verifica se o atributo ‘href’ tem um valor que começa (caractere ^) com a expressão ‘http://’. Abaixo a resposta encontrada.
Quantidade de links = 209 Quantidade de links com http = 32 *** Links com http http://brochure.getpython.info/ http://wiki.python.org/moin/Languages http://python.org/dev/peps/ http://planetpython.org/ http://pyfound.blogspot.com/ http://pycon.blogspot.com/ http://docs.python.org/3/tutorial/introduction.html#using-python-as-a-calculator http://pyfound.blogspot.com/2022/02/we-are-hiring-contract-developers-to.html http://pyfound.blogspot.com/2022/01/announcing-python-software-foundation.html http://pyfound.blogspot.com/2021/12/georgi-ker-awarded-psf-community.html http://www.djangoproject.com/ http://www.pylonsproject.org/ http://bottlepy.org http://tornadoweb.org http://flask.pocoo.org/ http://www.web2py.com/ http://wiki.python.org/moin/TkInter http://www.riverbankcomputing.co.uk/software/pyqt/intro http://www.wxpython.org/ http://www.scipy.org http://pandas.pydata.org/ http://ipython.org http://buildbot.net/ http://trac.edgewall.org/ http://roundup.sourceforge.net/ http://www.ansible.com http://brochure.getpython.info/ http://wiki.python.org/moin/Languages http://python.org/dev/peps/ http://planetpython.org/ http://pyfound.blogspot.com/ http://pycon.blogspot.com/
- Exemplo: o programa localiza inicialmente o primeiro link da página. Depois, o programa exibe o elemento pai do link e o primeiro parágrafo que aparece após o link.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") primeiro_link = pagina.a print("\n*** Primeiro link") print(primeiro_link) print("\n*** Pai do primeiro link") print(primeiro_link.parent) print("\n*** Primeiro parágrafo após link") print(primeiro_link.find_next("p"))
A resposta é
*** Primeiro link <a href="#content" title="Skip to content">Skip to content</a> *** Pai do primeiro link <div class="skip-link screen-reader-text"> <a href="#content" title="Skip to content">Skip to content</a> </div> *** Primeiro parágrafo após link <p>The core of extensible programming is defining functions. Python allows mandatory and optional arguments, keyword arguments, and even arbitrary argument lists. <a href="//docs.python.org/3/tutorial/controlflow.html#defining-functions">More about defining functions in Python 3</a></p>
BeautifulSoup permite navegar entre os diversos elementos de uma página HTML como, por exemplo, elemento pai (.parent), elemento irmão (.next_sibling e .previous_sibling), elemento filho (.children), próximo elemento (.next_element), elemento anterior (.previous_element), etc.
- Exemplo: o script verifica quantas tags na página possuem a classe .header-banner e/ou a classe menu.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") print("\n*** Seleciona classes") classes = pagina.select(".header-banner, .menu") print("Quantidade de elementos selecionados =", len(classes))
Abaixo é apresentada a resposta do programa.
*** Seleciona classes Quantidade de elementos selecionados = 29
- Exemplo: são selecionados todos os parágrafos da página Python. Os parágrafos são então exibidos ao lado da numeração da linha de código.
import requests, bs4 resp = requests.get('https://www.python.org') pagina = bs4.BeautifulSoup(resp.text, "html.parser") for parag in pagina.find_all('p'): print(parag.sourceline, parag.string)
O resultado da execução é
120 None 482 None 496 None 510 None 525 None 539 None 550 None 576 Whether you're new to programming or an experienced developer, it's easy to learn and use Python. 577 Start with our Beginner’s Guide 582 Python source code and installers are available for download for all versions! 583 None 588 Documentation for Python's standard library, along with tutorials and guides, are available online. 589 docs.python.org 594 None 595 jobs.python.org 607 More 642 More 695 More 708 None 722 More 762 None 763 None 1029 None
Note que a linha 576 mostra o conteúdo do parágrafo. O formato da tag na página é
<p>Whether you're new to programming or an experienced developer, it's easy to learn and use Python.</p>
O valor None aparece quando a tag não tem filhos ou tem mais de um filho. Por exemplo, a linha 120 da página Python é mostrada abaixo. Ela possui dois filhos (o primeiro é <strong>…</strong>, o segundo é o texto do parágrafo), por isso o resultado é None.
<p><strong>Notice:</strong> While JavaScript is not essential for this website, your interaction with the content will be limited. Please turn JavaScript on for the full experience. </p>
Quando o parágrafo só tem um filho, o texto do filho é mostrado. É o caso da linha 607 da página.
<p class="give-me-more"><a href="https://blog.python.org" title="More News">More</a></p>
- Exemplo: o programa usa o analisador lxml para identificar os links da página do Python. O método fromstring(), usado no exemplo, fornece um objeto HtmlElement. O método xpath() deste tipo de objeto pode ser usado para localizar e extrair informações do documento HTML.
import requests import lxml.html as lx pagina = requests.get('https://www.python.org/') objHTML = lx.fromstring(pagina.content) links = objHTML.xpath('//a') for link in links: print("***** HREF:", link.get("href")) print(" TEXTO:", link.text_content())
Uma parte da resposta gerada pelo programa é mostrada abaixo.
***** HREF: https://www.facebook.com/pythonlang?fref=ts TEXTO: Facebook ***** HREF: https://twitter.com/ThePSF TEXTO: Twitter ***** HREF: /community/irc/ TEXTO: Chat on IRC
import requests import lxml.html as lx objHTML = lx.fromstring(pagina.content) links = objHTML.xpath('//div[@id="tab_newreleases_content"]/a') i = 0 for link in links: i = i + 1 print("**************") print("Jogo", i) # Título do jogo titulo = link.xpath('.//div[@class="tab_item_name"]/text()')[0] print("Título: ", titulo) # Preço do jogo preco = link.xpath('.//div[@class="discount_final_price"]/text()')[0] print("Preço:", preco) # Informações do jogo informacoes = link.xpath('.//div[@class="tab_item_top_tags"]')[0].text_content() print("Informações:", informacoes) # Plataformas do jogo plataformas=[] lista_plataformas = link.xpath('.//div[@class="tab_item_details"]') for plataforma in lista_plataformas: temp = plataforma.xpath('.//span[contains(@class, "platform_img")]') nome = [t.get('class').split(' ')[-1] for t in temp] plataformas.append(nome) print("Plataformas:", plataformas[0]) print("**************")
Note que inicialmente o programa identifica os links que estão abaixo da div com id=”tab_newreleases_content”. As informações desejadas (título, preço, informações do jogo e plataformas em que o jogo pode ser usado) estão dentro das tags <a>…</a> como mostra a estrutura abaixo (as informações de cada jogo foi substituída por ‘XXXXX’). Portanto, ao recuperar os links, o programa recupera as informações dos jogos. É interessante também notar que os nomes das plataformas fazem parte dos nomes das classes que começam por “platform_img”. Assim se o jogo pode ser usado no Linux, haverá um span com classe “platform_img linux”.
<a href="XXXXX"> <div class="tab_item_cap"><img class="tab_item_cap_img" src="XXXXX" ></div> <div class="discount_block tab_item_discount no_discount" data-price-final=" "> <div class="discount_prices"> <div class="discount_final_price">XXXXX</div> </div> </div> <div class="tab_item_content"> <div class="tab_item_name">XXXXX</div> <div class="tab_item_details"> <span class="platform_img XXXXX"></span> <span class="platform_img XXXXX"></span> <div class="tab_item_top_tags"> <span class="top_tag">XXXXX</span> <span class="top_tag">XXXXX</span> <span class="top_tag">XXXXX</span> </div> </div> </div> <div style="clear: both;"></div> </a>
Abaxo, uma parte da resposta obtida pelo programa.
************** Jogo 25 Título: Aperture Desk Job Preço: Grátis Informações: Grátis para Jogar, Um Só Jogador, Engraçado, Casual Plataformas: ['win', 'linux'] ************** Jogo 26 Título: ELDEN RING Preço: R$ 249,90 Informações: Soulslike, Relaxante, Fantasia Sombria, RPG Plataformas: ['win'] **************
Outra forma de fazer esse exemplo pode ser encontrada em https://timber.io/blog/an-intro-to-web-scraping-with-lxml-and-python/.