чи можемо ми використовувати xpath з BeautifulSoup?


105

Я використовую BeautifulSoup, щоб скребти URL, і у мене був такий код

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

Тепер у наведеному вище коді ми можемо використовувати findAllтеги та інформацію, пов’язану з ними, але я хочу використовувати xpath. Чи можливо використовувати xpath за допомогою BeautifulSoup? Якщо можливо, хтось може надати мені приклад коду, щоб він був кориснішим?

Відповіді:


168

Ні, BeautifulSoup сам по собі не підтримує вирази XPath.

Альтернативна бібліотека, LXML , робить підтримку XPath 1.0. У ньому є режим, сумісний з BeautifulSoup, де він буде намагатися розбирати зламаний HTML, як робить Суп. Однак HTML-аналізатор lxml за замовчуванням робить так само хорошу роботу з розбору зламаного HTML, і я вважаю, що це швидше.

Після того, як ви розібрали документ у дереві lxml, ви можете скористатися .xpath()методом пошуку елементів.

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)

Також є виділений lxml.html()модуль з додатковою функціональністю.

Зауважте, що у наведеному вище прикладі я передав responseоб'єкт безпосередньо lxml, оскільки аналізатор зчитування прямо з потоку є більш ефективним, ніж спочатку зчитування відповіді у великій рядку. Щоб зробити те ж саме з requestsбібліотекою, потрібно встановити stream=Trueта передати response.rawоб’єкт після включення прозорої транспортної декомпресії :

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)

Можлива для вас цікава підтримка CSS Selector ; CSSSelectorклас переводить оператори CSS в виразах XPath, що робить Ваш пошук , td.empformbodyщо набагато простіше:

from lxml.cssselect import CSSSelector

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

Пройшовши повне коло: сам BeautifulSoup має дуже повну підтримку CSS селектор :

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

2
Дуже дякую Пітерс, я отримав дві інформації з ур-коду, 1. Пояснення, що ми не можемо використовувати xpath з BS 2. Приємний приклад того, як використовувати lxml. Чи можемо ми побачити на певній документації, що "ми не можемо реалізувати xpath за допомогою BS у письмовій формі", тому що ми повинні показати певний доказ комусь із тих, хто вимагає роз'яснення?
Шива Крішна Бавандла

8
Важко довести негатив; документація BeautifulSoup 4 має функцію пошуку і немає хітів для «» XPath.
Martijn Pieters

122

Я можу підтвердити, що в програмі Beautiful Soup немає підтримки XPath.


76
Примітка: Леонард Річардсон є автором прекрасного супу, як ви побачите, натиснувши його профіль користувача.
сеншин

23
Було б дуже приємно мати можливість використовувати XPATH в межах BeautifulSoup
DarthOpto

4
То яка альтернатива?
static_rtti

40

Як говорили інші, у BeautifulSoup немає підтримки xpath. Ймовірно, існує кілька способів отримати щось із xpath, включаючи використання Selenium. Однак ось рішення, яке працює в Python 2 або 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)

Я використовував це як орієнтир.


Одне попередження: я помітив, якщо є щось поза коренем (наприклад, \ n за межами зовнішніх <html> тегів), то посилання xpaths на корінь не буде працювати, ви повинні використовувати відносні xpaths. lxml.de/xpathxslt.html
словазвідти

Код Мартійна більше не працює належним чином (на сьогодні йому вже 4 роки ...), рядок etree.parse () друкується на консоль і не присвоює значення змінній дерева. Це цілком претензія. Я, звичайно, не можу це відтворити, і це не мало б сенсу . Ви впевнені, що використовуєте Python 2 для тестування мого коду або перевели використання urllib2бібліотеки на Python 3 urllib.request?
Martijn Pieters

Так, це може бути так, що я використовував Python3 при написанні цього, і він не працював так, як очікувалося. Щойно перевірений, і ваша робота з Python2, але Python3 набагато більше переваг, оскільки 2 триває на заході (більше офіційно не підтримується) у 2020 році.
wordsforthewise

абсолютно згоден, але тут використовується питання Python 2 .
Martijn Pieters

17

BeautifulSoup має функцію з назвою findNext з поточного елемента, спрямованого на дітей, так що:

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

Наведений вище код може імітувати наступний xpath:

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

1

Я шукав їхні документи і, здається, немає можливості xpath. Крім того, як ви бачите тут у подібному питанні щодо SO, OP вимагає перекладу з xpath на BeautifulSoup, тож мій висновок був би - ні, немає доступного розбору xpath.


так, насправді до цих пір я використовував скрапію, яка використовує xpath для отримання даних всередині тегів. Це дуже зручно і легко для отримання даних, але мені випала потреба зробити те ж саме з Beautifulsoup, тому з нетерпінням чекаю цього.
Шива Крішна Бавандла

1

коли ви використовуєте lxml, все просто:

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

але при використанні BeautifulSoup BS4 все також просто:

  • спочатку видаліть "//" та "@"
  • друге - додайте зірочку перед "="

спробуйте цю магію:

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

як бачите, це не підтримує субтег, тому я видаляю "/ @ href" частину


select()призначений для селекторів CSS, це зовсім не XPath. як ви бачите, це не підтримує субтег. Хоча я не впевнений, що це було правдою в той час, зараз, звичайно, це не так.
AMC

1

Можливо, ви можете спробувати наступне без 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()')

Вище використовується комбінація об'єкта "Суп" з lxml і можна отримати значення за допомогою xpath


0

Це досить стара нитка, але зараз існує вирішення проблеми, яке, можливо, ще не було в BeautifulSoup.

Ось приклад того, що я зробив. Я використовую модуль "запити", щоб прочитати RSS-канал і отримати його текстовий вміст у змінній під назвою "rss_text". З цим я запускаю його через BeautifulSoup, шукаю xpath / rss / channel / title та отримую його вміст. Це не зовсім XPath у всій красі (макіяж, кілька шляхів тощо), але якщо у вас просто основний шлях, який ви хочете знайти, це працює.

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

Я вважаю, що це лише знаходить дочірні елементи. XPath - інша річ?
raffaem
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.