podemos usar o xpath com BeautifulSoup?


105

Estou usando o BeautifulSoup para copiar um url e tenho o seguinte código

import urllib
import urllib2
from BeautifulSoup import BeautifulSoup

url =  "http://www.example.com/servlet/av/ResultTemplate=AVResult.html"
req = urllib2.Request(url)
response = urllib2.urlopen(req)
the_page = response.read()
soup = BeautifulSoup(the_page)
soup.findAll('td',attrs={'class':'empformbody'})

Agora, no código acima, podemos usar findAllpara obter tags e informações relacionadas a eles, mas quero usar o xpath. É possível usar o xpath com BeautifulSoup? Se possível, alguém pode me fornecer um código de exemplo para que seja mais útil?

Respostas:


168

Não, o BeautifulSoup, por si só, não oferece suporte a expressões XPath.

Uma biblioteca alternativa, lxml , oferece suporte a XPath 1.0. Ele tem um modo compatível com BeautifulSoup, onde tentará analisar HTML corrompido da mesma forma que o Soup faz. No entanto, o analisador HTML lxml padrão faz um trabalho igualmente bom de analisar HTML quebrado, e acredito que seja mais rápido.

Depois de analisar seu documento em uma árvore lxml, você pode usar o .xpath()método para pesquisar elementos.

try:
    # Python 2
    from urllib2 import urlopen
except ImportError:
    from urllib.request import urlopen
from lxml import etree

url =  "http://www.example.com/servlet/av/ResultTemplate=AVResult.html"
response = urlopen(url)
htmlparser = etree.HTMLParser()
tree = etree.parse(response, htmlparser)
tree.xpath(xpathselector)

Também existe um módulo dedicadolxml.html() com funcionalidade adicional.

Observe que, no exemplo acima, passei o responseobjeto diretamente para lxml, pois fazer com que o analisador leia diretamente do fluxo é mais eficiente do que ler a resposta em uma grande string primeiro. Para fazer o mesmo com a requestsbiblioteca, você deseja definir stream=Truee passar o response.rawobjeto após ativar a descompressão de transporte transparente :

import lxml.html
import requests

url =  "http://www.example.com/servlet/av/ResultTemplate=AVResult.html"
response = requests.get(url, stream=True)
response.raw.decode_content = True
tree = lxml.html.parse(response.raw)

De possível interesse para você é o suporte ao seletor CSS ; a CSSSelectorclasse traduz as instruções CSS em expressões XPath, tornando sua pesquisa td.empformbodymuito mais fácil:

from lxml.cssselect import CSSSelector

td_empformbody = CSSSelector('td.empformbody')
for elem in td_empformbody(tree):
    # Do something with these table cells.

Fechando o círculo: a própria BeautifulSoup não tem muito completo apoio seletor CSS :

for cell in soup.select('table#foobar td.empformbody'):
    # Do something with these table cells.

2
Muito obrigado Pieters, recebi duas informações do seu código, 1. Um esclarecimento de que não podemos usar xpath com BS 2. Um bom exemplo de como usar lxml. Podemos ver em uma documentação particular que "não podemos implementar xpath usando BS na forma escrita", porque deveríamos mostrar alguma prova para alguém que pede um esclarecimento certo?
Shiva Krishna Bavandla

8
É difícil provar uma negativa; a documentação do BeautifulSoup 4 tem uma função de pesquisa e não há resultados para 'xpath'.
Martijn Pieters

122

Posso confirmar que não há suporte para XPath no Beautiful Soup.


76
Nota: Leonard Richardson é o autor de Beautiful Soup, como você verá se clicar em seu perfil de usuário.
senshin

23
Seria muito bom poder usar XPATH dentro da BeautifulSoup
DarthOpto

4
Então, qual é a alternativa?
static_rtti

40

Como já foi dito, BeautifulSoup não tem suporte xpath. Provavelmente, existem várias maneiras de obter algo de um xpath, incluindo usando Selenium. No entanto, aqui está uma solução que funciona no Python 2 ou 3:

from lxml import html
import requests

page = requests.get('http://econpy.pythonanywhere.com/ex/001.html')
tree = html.fromstring(page.content)
#This will create a list of buyers:
buyers = tree.xpath('//div[@title="buyer-name"]/text()')
#This will create a list of prices
prices = tree.xpath('//span[@class="item-price"]/text()')

print('Buyers: ', buyers)
print('Prices: ', prices)

Usei isso como referência.


Um aviso: notei que se houver algo fora da raiz (como um \ n fora das tags <html> externas), referenciar xpaths pela raiz não funcionará, você deve usar xpaths relativos. lxml.de/xpathxslt.html
wordsforthewise

O código de Martijn não funciona mais corretamente (já tem mais de 4 anos ...), a linha etree.parse () é impressa no console e não atribui o valor à variável tree. Isso é uma afirmação e tanto. Certamente não posso reproduzir isso e não faria nenhum sentido . Tem certeza de que está usando Python 2 para testar meu código ou traduziu o urllib2uso da biblioteca para Python 3 urllib.request?
Martijn Pieters

Sim, pode ser o caso que usei Python3 ao escrever isso e não funcionou como esperado. Acabei de testar e o seu funciona com Python2, mas Python3 é preferível, pois o 2 está sendo desativado (não é mais compatível oficialmente) em 2020.
wordsforthewise

concordo totalmente, mas a questão aqui usa Python 2 .
Martijn Pieters

17

BeautifulSoup tem uma função chamada findNext do elemento atual direcionado ao childern, então:

father.findNext('div',{'class':'class_value'}).findNext('div',{'id':'id_value'}).findAll('a') 

O código acima pode imitar o seguinte xpath:

div[class=class_value]/div[id=id_value]

1

Eu pesquisei seus documentos e parece que não há opção xpath. Além disso, como você pode ver aqui em uma questão semelhante no SO, o OP está pedindo uma tradução do xpath para o BeautifulSoup, então minha conclusão seria - não, não há análise do xpath disponível.


sim, na verdade, até agora eu usei o scrapy, que usa o xpath para buscar os dados dentro das tags. É muito prático e fácil de buscar os dados, mas eu preciso fazer o mesmo com o beautifulsoup, então estou ansioso por isso.
Shiva Krishna Bavandla

1

quando você usa lxml, tudo simples:

tree = lxml.html.fromstring(html)
i_need_element = tree.xpath('//a[@class="shared-components"]/@href')

mas ao usar BeautifulSoup BS4 tudo simples também:

  • primeiro remova "//" e "@"
  • segundo - adicione estrela antes de "="

tente esta mágica:

soup = BeautifulSoup(html, "lxml")
i_need_element = soup.select ('a[class*="shared-components"]')

como você vê, isso não suporta sub-tag, então eu removo a parte "/ @ href"


select()é para seletores CSS, não é XPath. como você vê, isso não suporta subtags Embora eu não tenha certeza se isso era verdade na época, certamente não é agora.
AMC

1

Talvez você possa tentar o seguinte sem XPath

from simplified_scrapy.simplified_doc import SimplifiedDoc 
html = '''
<html>
<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
'''
# What XPath can do, so can it
doc = SimplifiedDoc(html)
# The result is the same as doc.getElementByTag('body').getElementByTag('div').getElementByTag('h1').text
print (doc.body.div.h1.text)
print (doc.div.h1.text)
print (doc.h1.text) # Shorter paths will be faster
print (doc.div.getChildren())
print (doc.div.getChildren('p'))

1
from lxml import etree
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('path of your localfile.html'),'html.parser')
dom = etree.HTML(str(soup))
print dom.xpath('//*[@id="BGINP01_S1"]/section/div/font/text()')

Acima usou a combinação do objeto Sopa com lxml e pode-se extrair o valor usando xpath


0

Este é um tópico muito antigo, mas há uma solução alternativa agora, que pode não estar no BeautifulSoup na época.

Aqui está um exemplo do que fiz. Eu uso o módulo "solicitações" para ler um feed RSS e obter seu conteúdo de texto em uma variável chamada "rss_text". Com isso, eu o executo através do BeautifulSoup, procuro o xpath / rss / channel / title e recupero seu conteúdo. Não é exatamente XPath em toda a sua glória (curingas, caminhos múltiplos, etc.), mas se você tiver apenas um caminho básico que deseja localizar, isso funciona.

from bs4 import BeautifulSoup
rss_obj = BeautifulSoup(rss_text, 'xml')
cls.title = rss_obj.rss.channel.title.get_text()

Eu acredito que isso só encontra os elementos filhos. XPath é outra coisa?
raffaem
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.