possiamo usare xpath con BeautifulSoup?


106

Sto usando BeautifulSoup per raschiare un URL e avevo il seguente codice

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'})

Ora nel codice precedente possiamo usare findAllper ottenere tag e informazioni ad essi correlati, ma voglio usare xpath. È possibile usare xpath con BeautifulSoup? Se possibile, qualcuno può fornirmi un codice di esempio in modo che sia più utile?

Risposte:


169

No, BeautifulSoup, di per sé, non supporta le espressioni XPath.

Una libreria alternativa, lxml , fa il supporto XPath 1.0. Ha una modalità compatibile con BeautifulSoup in cui proverà ad analizzare l'HTML rotto come fa Soup. Tuttavia, il parser HTML lxml predefinito fa altrettanto bene l'analisi dell'HTML rotto e credo sia più veloce.

Dopo aver analizzato il documento in un albero lxml, puoi utilizzare il .xpath()metodo per cercare gli elementi.

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)

C'è anche un modulo dedicatolxml.html() con funzionalità aggiuntive.

Si noti che nell'esempio sopra ho passato l' responseoggetto direttamente a lxml, poiché avere il parser letto direttamente dal flusso è più efficiente che leggere prima la risposta in una stringa grande. Per fare lo stesso con la requestslibreria, vuoi impostare stream=Truee passare l' response.rawoggetto dopo aver abilitato la decompressione del trasporto trasparente :

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)

Di possibile interesse per te è il supporto del selettore CSS ; la CSSSelectorclasse traduce le istruzioni CSS in espressioni XPath, rendendo la tua ricerca td.empformbodymolto più semplice:

from lxml.cssselect import CSSSelector

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

Venendo al punto di partenza: BeautifulSoup in sé non ha molto completo supporto selettore CSS :

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

2
Grazie mille Pieters, ho ricevuto due informazioni dal tuo codice, 1. Un chiarimento che non possiamo usare xpath con BS 2. Un bell'esempio di come usare lxml. Possiamo vedere su una particolare documentazione che "non possiamo implementare xpath usando BS in forma scritta", perché dovremmo mostrare qualche prova a qualcuno che chiede chiarimenti, giusto?
Shiva Krishna Bavandla

8
È difficile dimostrare un negativo; la documentazione di BeautifulSoup 4 ha una funzione di ricerca e non ci sono risultati per "xpath".
Martijn Pieters

123

Posso confermare che non esiste il supporto XPath in Beautiful Soup.


76
Nota: Leonard Richardson è l'autore di Beautiful Soup, come vedrai se fai clic sul suo profilo utente.
senshin

23
Sarebbe molto bello poter usare XPATH all'interno di BeautifulSoup
DarthOpto

4
Quindi qual è l'alternativa?
static_rtti

40

Come altri hanno già detto, BeautifulSoup non supporta xpath. Probabilmente ci sono diversi modi per ottenere qualcosa da un xpath, incluso l'uso di Selenium. Tuttavia, ecco una soluzione che funziona in 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)

Ho usato questo come riferimento.


Un avvertimento: ho notato che se c'è qualcosa al di fuori della root (come un \ n al di fuori dei tag <html> esterni), quindi il riferimento a xpaths da parte di root non funzionerà, devi usare xpaths relativi. lxml.de/xpathxslt.html
parole per il

Il codice di Martijn non funziona più correttamente (ha più di 4 anni ormai ...), la riga etree.parse () viene stampata sulla console e non assegna il valore alla variabile tree. Questa è una bella affermazione. Di certo non posso riprodurlo e non avrebbe alcun senso . Sei sicuro di utilizzare Python 2 per testare il mio codice o di aver tradotto l' urllib2uso della libreria in Python 3 urllib.request?
Martijn Pieters

Sì, potrebbe essere il caso in cui ho usato Python3 durante la scrittura e non ha funzionato come previsto. È appena stato testato e il tuo funziona con Python2, ma Python3 è di gran lunga preferito in quanto 2 è il tramonto (non più ufficialmente supportato) nel 2020.
parole per il

assolutamente d'accordo, ma la domanda qui usa Python 2 .
Martijn Pieters

17

BeautifulSoup ha una funzione chiamata findNext dall'elemento corrente diretta childern, quindi:

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

Il codice sopra può imitare il seguente xpath:

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

1

Ho cercato nei loro documenti e sembra che non ci sia l'opzione xpath. Inoltre, come puoi vedere qui su una domanda simile su SO, l'OP sta chiedendo una traduzione da xpath a BeautifulSoup, quindi la mia conclusione sarebbe: no, non è disponibile l'analisi di xpath.


sì in realtà fino ad ora ho usato scrapy che usa xpath per recuperare i dati all'interno dei tag. È molto pratico e facile da recuperare i dati, ma ho bisogno di fare lo stesso con beautifulsoup, quindi non vedo l'ora di farlo.
Shiva Krishna Bavandla

1

quando usi lxml tutto semplice:

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

ma quando usi BeautifulSoup BS4 anche tutto semplice:

  • prima rimuovi "//" e "@"
  • secondo - aggiungi una stella prima di "="

prova questa magia:

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

come vedi, questo non supporta il sub-tag, quindi rimuovo la parte "/ @ href"


select()è per i selettori CSS, non è affatto XPath. come vedi, questo non supporta il sub-tag Anche se non sono sicuro che fosse vero al momento, certamente non lo è ora.
AMC

1

Forse puoi provare quanto segue senza 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()')

Sopra è stata utilizzata la combinazione dell'oggetto Soup con lxml e si può estrarre il valore usando xpath


0

Questo è un thread piuttosto vecchio, ma ora esiste una soluzione per aggirare il problema, che potrebbe non essere stata in BeautifulSoup in quel momento.

Ecco un esempio di quello che ho fatto. Uso il modulo "richieste" per leggere un feed RSS e ottenere il suo contenuto di testo in una variabile chiamata "rss_text". Detto questo, lo eseguo tramite BeautifulSoup, cerco xpath / rss / channel / title e ne recupero il contenuto. Non è esattamente XPath in tutta la sua gloria (caratteri jolly, percorsi multipli, ecc.), Ma se hai solo un percorso di base che vuoi individuare, funziona.

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

Credo che questo trovi solo gli elementi figlio. XPath è un'altra cosa?
raffaem
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.