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?

Shiva Krishna Bavandla
la source

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.
Martijn Pieters
la source
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.

Leonard Richardson
la source
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.

mots pour
la source
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]
user3820561
la source
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.

Nikola
la source
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"

Oleksandr Panchenko
la source
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'))
dabingsou
la source
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

Deepak Rkm
la source
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()
David A
la source
Je crois que cela ne trouve que les éléments enfants. XPath est une autre chose?
raffaem