pouvons-nous utiliser xpath avec BeautifulSoup?


107

J'utilise BeautifulSoup pour gratter une URL et j'ai eu le code suivant

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

Maintenant, dans le code ci-dessus, nous pouvons utiliser findAllpour obtenir des balises et des informations qui leur sont liées, mais je veux utiliser xpath. Est-il possible d'utiliser xpath avec BeautifulSoup? Si possible, quelqu'un peut-il me fournir un exemple de code afin qu'il soit plus utile?

Réponses:


169

Non, BeautifulSoup, à lui seul, ne prend pas en charge les expressions XPath.

Une bibliothèque alternative, lxml , prend en charge XPath 1.0. Il a un mode compatible BeautifulSoup où il essaiera d'analyser le HTML cassé comme le fait Soup. Cependant, l' analyseur HTML lxml par défaut fait tout aussi bien un travail d'analyse du HTML cassé, et je pense qu'il est plus rapide.

Une fois que vous avez analysé votre document dans une arborescence lxml, vous pouvez utiliser la .xpath()méthode pour rechercher des éléments.

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)

Il existe également un module dédiélxml.html() avec des fonctionnalités supplémentaires.

Notez que dans l'exemple ci-dessus, j'ai passé l' responseobjet directement à lxml, car il est plus efficace de lire l'analyseur directement à partir du flux que de lire d'abord la réponse dans une grande chaîne. Pour faire de même avec la requestsbibliothèque, vous souhaitez définir stream=Trueet transmettre l' response.rawobjet après avoir activé la décompression transparente du transport :

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)

Le support du sélecteur CSS peut vous intéresser ; la CSSSelectorclasse traduit les instructions CSS en expressions XPath, ce td.empformbodyqui facilite grandement votre recherche :

from lxml.cssselect import CSSSelector

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

La boucle est bouclée : BeautifulSoup lui - même prend en charge le sélecteur CSS très complet :

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

2
Merci beaucoup Pieters, j'ai obtenu deux informations de votre code, 1. Une clarification que nous ne pouvons pas utiliser xpath avec BS 2. Un bel exemple sur l'utilisation de lxml. Pouvons-nous voir sur une documentation particulière que "nous ne pouvons pas implémenter xpath en utilisant BS sous forme écrite", parce que nous devrions montrer une preuve à quelqu'un qui demande des éclaircissements, n'est-ce pas?
Shiva Krishna Bavandla

8
Il est difficile de prouver un négatif; la documentation BeautifulSoup 4 a une fonction de recherche et il n'y a pas de résultats pour 'xpath'.
Martijn Pieters

123

Je peux confirmer qu'il n'y a pas de support XPath dans Beautiful Soup.


76
Remarque: Leonard Richardson est l'auteur de Beautiful Soup, comme vous le verrez si vous cliquez sur son profil utilisateur.
senshin

23
Ce serait très bien de pouvoir utiliser XPATH dans BeautifulSoup
DarthOpto

4
Alors, quelle est l'alternative?
static_rtti

41

Comme d'autres l'ont dit, BeautifulSoup ne prend pas en charge xpath. Il existe probablement plusieurs façons d'obtenir quelque chose à partir d'un xpath, notamment en utilisant Selenium. Cependant, voici une solution qui fonctionne en 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)

J'ai utilisé cette référence.


Un avertissement: j'ai remarqué que s'il y a quelque chose en dehors de la racine (comme un \ n en dehors des balises externes <html>), alors référencer xpaths par la racine ne fonctionnera pas, vous devez utiliser des xpaths relatifs. lxml.de/xpathxslt.html
wordsforthewise

Le code de Martijn ne fonctionne plus correctement (il a maintenant 4 ans et plus ...), la ligne etree.parse () s'imprime sur la console et n'assigne pas la valeur à la variable d'arborescence. C'est toute une revendication. Je ne peux certainement pas reproduire cela, et cela n'aurait aucun sens . Êtes-vous sûr d'utiliser Python 2 pour tester mon code avec, ou avez-vous traduit l' urllib2utilisation de la bibliothèque en Python 3 urllib.request?
Martijn Pieters

Ouais, c'est peut-être le cas où j'ai utilisé Python3 lors de l'écriture et cela n'a pas fonctionné comme prévu. Je viens de tester et le vôtre fonctionne avec Python2, mais Python3 est de loin préféré car 2 est en train de se coucher (n'est plus officiellement pris en charge) en 2020.
wordsforthewise

tout à fait d'accord, mais la question ici utilise Python 2 .
Martijn Pieters

17

BeautifulSoup a une fonction nommée findNext à partir de l'élément enfant en cours, donc:

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

Le code ci-dessus peut imiter le xpath suivant:

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

1

J'ai cherché dans leurs documents et il semble qu'il n'y ait pas d'option xpath. De plus, comme vous pouvez le voir ici sur une question similaire sur SO, l'OP demande une traduction de xpath vers BeautifulSoup, donc ma conclusion serait - non, il n'y a pas d'analyse xpath disponible.


oui en fait jusqu'à présent, j'ai utilisé scrapy qui utilise xpath pour récupérer les données à l'intérieur des balises.C'est très pratique et facile à récupérer des données, mais j'ai eu besoin de faire de même avec beautifulsoup alors j'ai hâte d'y être.
Shiva Krishna Bavandla

1

lorsque vous utilisez lxml tout simple:

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

mais lorsque vous utilisez BeautifulSoup BS4 tout simple aussi:

  • supprimez d'abord "//" et "@"
  • deuxième - ajouter une étoile avant "="

essayez cette magie:

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

comme vous le voyez, cela ne prend pas en charge les sous-balises, je supprime donc la partie "/ @ href"


select()est pour les sélecteurs CSS, ce n'est pas du tout XPath. comme vous le voyez, cela ne prend pas en charge les sous-balises. Bien que je ne sois pas sûr que ce soit vrai à l'époque, ce n'est certainement pas le cas maintenant.
AMC

1

Peut-être que vous pouvez essayer ce qui suit sans 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()')

Ci-dessus utilisé la combinaison de l'objet Soup avec lxml et on peut extraire la valeur en utilisant xpath


0

Il s'agit d'un fil assez ancien, mais il existe maintenant une solution de contournement, qui n'était peut-être pas dans BeautifulSoup à l'époque.

Voici un exemple de ce que j'ai fait. J'utilise le module "requests" pour lire un flux RSS et obtenir son contenu texte dans une variable appelée "rss_text". Avec cela, je l'exécute via BeautifulSoup, recherche le xpath / rss / channel / title et récupère son contenu. Ce n'est pas exactement XPath dans toute sa splendeur (jokers, chemins multiples, etc.), mais si vous avez juste un chemin de base que vous souhaitez localiser, cela fonctionne.

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

Je crois que cela ne trouve que les éléments enfants. XPath est une autre chose?
raffaem
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.