¿Podemos usar xpath con BeautifulSoup?

105

Estoy usando BeautifulSoup para raspar una URL y tenía el siguiente 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'})

Ahora, en el código anterior, podemos usar findAllpara obtener etiquetas e información relacionada con ellas, pero quiero usar xpath. ¿Es posible usar xpath con BeautifulSoup? Si es posible, ¿alguien puede proporcionarme un código de ejemplo para que sea más útil?

Shiva Krishna Bavandla
fuente

Respuestas:

168

No, BeautifulSoup, por sí solo, no admite expresiones XPath.

Una biblioteca alternativa, lxml , hace apoyo XPath 1.0. Tiene un modo compatible con BeautifulSoup en el que intentará analizar HTML roto como lo hace Soup. Sin embargo, el analizador HTML lxml predeterminado hace un buen trabajo al analizar HTML roto, y creo que es más rápido.

Una vez que haya analizado su documento en un árbol lxml, puede usar el .xpath()método para buscar 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)

También hay un módulo dedicadolxml.html() con funcionalidad adicional.

Tenga en cuenta que en el ejemplo anterior le pasé el responseobjeto directamente lxml, ya que hacer que el analizador lea directamente de la secuencia es más eficiente que leer primero la respuesta en una cadena grande. Para hacer lo mismo con la requestsbiblioteca, desea establecer stream=Truey pasar el response.rawobjeto después de habilitar la descompresión 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 posible interés para usted es el soporte de CSS Selector ; la CSSSelectorclase traduce declaraciones CSS en expresiones XPath, lo td.empformbodyque facilita mucho la búsqueda :

from lxml.cssselect import CSSSelector

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

Cerrando el círculo: sí BeautifulSoup no tiene muy completo apoyo selector CSS :

for cell in soup.select('table#foobar td.empformbody'):
    # Do something with these table cells.
Martijn Pieters
fuente
2
Muchas gracias Pieters, obtuve dos informaciones de tu código, 1. Una aclaración de que no podemos usar xpath con BS 2. Un buen ejemplo de cómo usar lxml. ¿Podemos ver en una documentación en particular que "no podemos implementar xpath usando BS en forma escrita", porque deberíamos mostrar alguna prueba a alguien que pida una aclaración, verdad?
Shiva Krishna Bavandla
8
Es difícil probar algo negativo; la documentación de BeautifulSoup 4 tiene una función de búsqueda y no hay resultados para 'xpath'.
Martijn Pieters
122

Puedo confirmar que no hay soporte XPath dentro de Beautiful Soup.

Leonard Richardson
fuente
76
Nota: Leonard Richardson es el autor de Beautiful Soup, como verá si hace clic en su perfil de usuario.
senshin
23
Sería muy bueno poder usar XPATH dentro de BeautifulSoup
DarthOpto
4
entonces cual es la alternativa?
static_rtti
40

Como han dicho otros, BeautifulSoup no tiene soporte para xpath. Probablemente hay varias formas de obtener algo de un xpath, incluido el uso de Selenium. Sin embargo, aquí hay una solución que funciona en Python 2 o 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)

Usé esto como referencia.

palabras por lo demás
fuente
Una advertencia: he notado que si hay algo fuera de la raíz (como un \ n fuera de las etiquetas <html> externas), la referencia a xpaths por la raíz no funcionará, debe usar xpaths relativos. lxml.de/xpathxslt.html
wordsforthewise
El código de Martijn ya no funciona correctamente (ya tiene más de 4 años ...), la línea etree.parse () se imprime en la consola y no asigna el valor a la variable del árbol. Eso es todo un reclamo. Ciertamente no puedo reproducir eso, y no tendría ningún sentido . ¿Está seguro de que está usando Python 2 para probar mi código o ha traducido el urllib2uso de la biblioteca a Python 3 urllib.request?
Martijn Pieters
Sí, ese puede ser el caso de que usé Python3 al escribir eso y no funcionó como se esperaba. Acabo de probarlo y el suyo funciona con Python2, pero Python3 es mucho más preferido, ya que 2 se eliminará (ya no se admite oficialmente) en 2020.
wordsforthewise
absolutamente de acuerdo, pero la pregunta aquí usa Python 2 .
Martijn Pieters
17

BeautifulSoup tiene una función llamada findNext del elemento actual dirigido childern, por lo que:

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

El código anterior puede imitar el siguiente xpath:

div[class=class_value]/div[id=id_value]
usuario3820561
fuente
1

He buscado en sus documentos y parece que no hay una opción xpath. Además, como puede ver aquí en una pregunta similar sobre SO, el OP solicita una traducción de xpath a BeautifulSoup, por lo que mi conclusión sería: no, no hay análisis de xpath disponible.

Nikola
fuente
sí, en realidad, hasta ahora usé scrapy que usa xpath para buscar los datos dentro de las etiquetas. Es muy útil y fácil de obtener datos, pero tengo la necesidad de hacer lo mismo con beautifulsoup, así que estoy ansioso por hacerlo.
Shiva Krishna Bavandla
1

cuando usas lxml todo simple:

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

pero cuando usa BeautifulSoup BS4 también es simple:

  • primero elimine "//" y "@"
  • segundo - agregue una estrella antes de "="

prueba esta magia:

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

como puede ver, esto no es compatible con la subetiqueta, así que elimino la parte "/ @ href"

Oleksandr Panchenko
fuente
select()es para selectores CSS, no es XPath en absoluto. como puede ver, esto no es compatible con la subetiqueta. Aunque no estoy seguro de si eso era cierto en ese momento, ciertamente no lo es ahora.
AMC
1

Tal vez puedas probar lo siguiente sin 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'))
dabingsou
fuente
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()')

Anteriormente se usó la combinación del objeto Soup con lxml y se puede extraer el valor usando xpath

Deepak rkm
fuente
0

Este es un hilo bastante antiguo, pero ahora hay una solución alternativa, que puede que no estuviera en BeautifulSoup en ese momento.

He aquí un ejemplo de lo que hice. Utilizo el módulo "solicitudes" para leer una fuente RSS y obtener su contenido de texto en una variable llamada "rss_text". Con eso, lo ejecuto a través de BeautifulSoup, busco xpath / rss / channel / title y recupero su contenido. No es exactamente XPath en todo su esplendor (comodines, múltiples rutas, etc.), pero si solo tiene una ruta básica que desea ubicar, esto funciona.

from bs4 import BeautifulSoup
rss_obj = BeautifulSoup(rss_text, 'xml')
cls.title = rss_obj.rss.channel.title.get_text()
David A
fuente
Creo que esto solo encuentra los elementos secundarios. XPath es otra cosa?
raffaem