czy możemy używać xpath z BeautifulSoup?

107

Używam BeautifulSoup do zeskrobania adresu URL i mam następujący kod

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

Teraz w powyższym kodzie możemy użyć findAlldo pobrania znaczników i informacji z nimi związanych, ale chcę użyć xpath. Czy można używać xpath z BeautifulSoup? Jeśli to możliwe, czy ktoś może mi podać przykładowy kod, aby był bardziej pomocny?

Shiva Krishna Bavandla
źródło

Odpowiedzi:

169

Nie, sama BeautifulSoup nie obsługuje wyrażeń XPath.

Alternatywą biblioteka, lxml , czy wsparcie XPath 1.0. Ma tryb kompatybilny z BeautifulSoup, w którym spróbuje przeanalizować uszkodzony kod HTML tak, jak robi to Soup. Jednak domyślny parser lxml HTML równie dobrze radzi sobie z analizowaniem uszkodzonego kodu HTML i uważam, że jest szybszy.

Po przeanalizowaniu dokumentu w drzewie lxml możesz użyć tej .xpath()metody do wyszukiwania elementów.

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)

Istnieje również dedykowany lxml.html()moduł z dodatkową funkcjonalnością.

Zauważ, że w powyższym przykładzie przekazałem responseobiekt bezpośrednio do lxml, ponieważ odczytanie parsera bezpośrednio ze strumienia jest bardziej wydajne niż wczytanie odpowiedzi najpierw do dużego ciągu. Aby zrobić to samo z requestsbiblioteką, chcesz ustawić stream=Truei przekazać response.rawobiekt po włączeniu przezroczystej dekompresji transportu :

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)

Może Cię zainteresować obsługa selektora CSS ; CSSSelectorklasa przekłada wypowiedzi CSS w wyrażeniach XPath, dzięki czemu wyszukiwanie za td.empformbodyto o wiele łatwiejsze:

from lxml.cssselect import CSSSelector

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

Nadchodzi pełne koło: Sam BeautifulSoup ma mieć bardzo kompletne wsparcie selektora CSS :

for cell in soup.select('table#foobar td.empformbody'):
    # Do something with these table cells.
Martijn Pieters
źródło
2
Bardzo dziękuję Pieters, mam dwie informacje z twojego kodu, 1. Wyjaśnienie, że nie możemy używać xpath z BS 2. Ładny przykład użycia lxml. Czy możemy zobaczyć w konkretnej dokumentacji, że „nie możemy zaimplementować xpath używając BS w formie pisemnej”, ponieważ powinniśmy pokazać jakiś dowód komuś, kto prosi o wyjaśnienie, prawda?
Shiva Krishna Bavandla
8
Trudno jest udowodnić coś negatywnego; dokumentacja BeautifulSoup 4 posiada funkcję wyszukiwania i nie ma żadnych trafień dla „” XPath.
Martijn Pieters
123

Mogę potwierdzić, że w Beautiful Soup nie ma obsługi XPath.

Leonard Richardson
źródło
76
Uwaga: Leonard Richardson jest autorem Beautiful Soup, co zobaczysz, gdy klikniesz na jego profil użytkownika.
senshin
23
Byłoby bardzo miło móc korzystać z XPATH w BeautifulSoup
DarthOpto,
4
Więc jaka jest alternatywa?
static_rtti
41

Jak powiedzieli inni, BeautifulSoup nie obsługuje xpath. Prawdopodobnie istnieje wiele sposobów na uzyskanie czegoś z xpath, w tym użycie selenu. Oto jednak rozwiązanie, które działa w Pythonie 2 lub 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)

Użyłem tego jako odniesienia.

słowa do rozumu
źródło
Jedno ostrzeżenie: zauważyłem, że jeśli jest coś poza katalogiem głównym (np. \ N poza zewnętrznymi znacznikami <html>), to odwoływanie się do xpaths przez root nie zadziała, musisz użyć względnych xpaths. lxml.de/xpathxslt.html
wordsforhewise
Kod Martijna nie działa już poprawnie (ma już ponad 4 lata ...), wiersz etree.parse () drukuje na konsoli i nie przypisuje wartości zmiennej tree. To dość roszczenie. Z pewnością nie mogę tego odtworzyć i nie miałoby to żadnego sensu . Czy na pewno używasz Pythona 2 do testowania mojego kodu lub przetłumaczyłeś urllib2bibliotekę na Python 3 urllib.request?
Martijn Pieters
Tak, może się zdarzyć, że pisząc to użyłem Pythona3 i nie działało to zgodnie z oczekiwaniami. Właśnie przetestowano i twój działa z Pythonem2, ale Python3 jest znacznie preferowany, ponieważ 2 jest zachodem słońca (nie jest już oficjalnie obsługiwany) w 2020 roku.
dohewise
absolutnie się zgadzam, ale pytanie tutaj wykorzystuje Python 2 .
Martijn Pieters
17

BeautifulSoup ma funkcję o nazwie findNext z bieżącego elementu skierowanego podrzędny, więc:

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

Powyższy kod może imitować następującą ścieżkę xpath:

div[class=class_value]/div[id=id_value]
user3820561
źródło
1

Przeszukałem ich dokumenty i wygląda na to, że nie ma opcji xpath. Także, jak widać tutaj na podobne pytanie na SO, OP prosi o przetłumaczenie z XPath do BeautifulSoup, więc mój wniosek będzie - nie ma, nie ma XPath parsowania dostępny.

Nikola
źródło
tak właściwie do tej pory użyłem scrapy, który używa xpath do pobierania danych z tagów. jest bardzo poręczny i łatwy do pobrania danych, ale muszę zrobić to samo z beautifulsoup, więc nie mogę się doczekać.
Shiva Krishna Bavandla
1

kiedy używasz lxml wszystko proste:

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

ale kiedy używasz BeautifulSoup BS4, wszystko jest również proste:

  • najpierw usuń „//” i „@”
  • po drugie - dodaj gwiazdkę przed "="

spróbuj tej magii:

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

jak widzisz, to nie obsługuje tagu podrzędnego, więc usuwam część „/ @ href”

Oleksandr Panchenko
źródło
select()jest dla selektorów CSS, w ogóle nie jest XPath. jak widać, to nie obsługuje tagu podrzędnego. Chociaż nie jestem pewien, czy wtedy było to prawdą, z pewnością nie jest teraz.
AMC
1

Może możesz wypróbować następujące rozwiązania bez 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
źródło
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()')

Powyżej użyto kombinacji obiektu Soup z lxml i można wyodrębnić wartość za pomocą xpath

Deepak rkm
źródło
0

To dość stary wątek, ale obecnie istnieje rozwiązanie obejściowe, którego być może nie było w tym czasie w BeautifulSoup.

Oto przykład tego, co zrobiłem. Używam modułu "request" do czytania kanału RSS i pobierania jego zawartości tekstowej w zmiennej o nazwie "rss_text". Dzięki temu uruchamiam go przez BeautifulSoup, wyszukuję xpath / rss / channel / title i pobieram jego zawartość. To nie jest dokładnie XPath w całej okazałości (symbole wieloznaczne, wiele ścieżek itp.), Ale jeśli masz tylko podstawową ścieżkę, którą chcesz zlokalizować, to działa.

from bs4 import BeautifulSoup
rss_obj = BeautifulSoup(rss_text, 'xml')
cls.title = rss_obj.rss.channel.title.get_text()
David A.
źródło
Uważam, że to tylko znajduje elementy potomne. XPath to inna rzecz?
raffaem