можем ли мы использовать xpath с BeautifulSoup?


108

Я использую 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? Если возможно, может ли кто-нибудь предоставить мне пример кода, чтобы он был более полезным?

Ответы:


169

Нет, BeautifulSoup сам по себе не поддерживает выражения XPath.

Альтернативная библиотека, lxml , действительно поддерживает XPath 1.0. У него есть режим, совместимый с BeautifulSoup, в котором он попытается проанализировать сломанный HTML, как это делает Soup. Однако анализатор 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

125

Я могу подтвердить, что в Beautiful Soup нет поддержки XPath.


77
Примечание: Леонард Ричардсон - автор Beautiful Soup, в чем вы убедитесь, если перейдете к его профилю пользователя.
senshin

23
Было бы очень хорошо иметь возможность использовать XPATH в BeautifulSoup
DarthOpto

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

41

Как уже говорили другие, 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 корнем не будет работать, вы должны использовать относительные xpath. lxml.de/xpathxslt.html
wordsforward 06

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

Да, возможно, я использовал Python3, когда писал это, и он работал не так, как ожидалось. Только что протестировал, и ваш работает с Python2, но Python3 более предпочтителен, так как 2 будет заканчиваться (больше не поддерживается официально) в 2020 году.
словам

Абсолютно согласен, но вопрос здесь использует Python 2 .
Мартейн Питерс

17

BeautifulSoup имеет функцию с именем findNext из текущего элемента, направленного на childern, поэтому:

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 недоступен.


да, на самом деле до сих пор я использовал scrapy, который использует 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()')

Выше использовалась комбинация объекта Soup с 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 03
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.