bisakah kita menggunakan xpath dengan BeautifulSoup?

106

Saya menggunakan BeautifulSoup untuk mengikis url dan saya memiliki kode berikut

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

Sekarang dalam kode di atas kita bisa gunakan findAlluntuk mendapatkan tag dan informasi yang terkait dengannya, tetapi saya ingin menggunakan xpath. Apakah mungkin menggunakan xpath dengan BeautifulSoup? Jika memungkinkan, adakah yang bisa memberi saya contoh kode agar lebih membantu?

Shiva Krishna Bavandla
sumber

Jawaban:

169

Tidak, BeautifulSoup, dengan sendirinya, tidak mendukung ekspresi XPath.

Sebuah perpustakaan alternatif, lxml , apakah dukungan XPath 1.0. Ini memiliki mode kompatibel BeautifulSoup di mana ia akan mencoba dan mengurai HTML yang rusak seperti yang dilakukan Soup. Namun, parser HTML lxml default melakukan pekerjaan yang sama baiknya untuk mem -parsing HTML yang rusak, dan saya yakin lebih cepat.

Setelah Anda mengurai dokumen Anda menjadi pohon lxml, Anda dapat menggunakan .xpath()metode untuk mencari elemen.

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)

Ada juga modul khususlxml.html() dengan fungsionalitas tambahan.

Perhatikan bahwa dalam contoh di atas saya meneruskan responseobjek secara langsung lxml, karena memiliki parser yang dibaca langsung dari aliran lebih efisien daripada membaca respons menjadi string besar terlebih dahulu. Untuk melakukan hal yang sama dengan requestslibrary, Anda ingin menyetel stream=Truedan meneruskan response.rawobjek setelah mengaktifkan dekompresi transport transparan :

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)

Yang mungkin menarik bagi Anda adalah dukungan Pemilih CSS ; yang CSSSelectorkelas diterjemahkan pernyataan CSS ke dalam ekspresi XPath, membuat pencarian Anda untuk td.empformbodyyang jauh lebih mudah:

from lxml.cssselect import CSSSelector

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

Lingkaran penuh: BeautifulSoup sendiri memang memiliki dukungan pemilih CSS yang sangat lengkap :

for cell in soup.select('table#foobar td.empformbody'):
    # Do something with these table cells.
Martijn Pieters
sumber
2
Terima kasih banyak Pieters, saya mendapat dua informasi dari kode ur, 1. Klarifikasi bahwa kita tidak dapat menggunakan xpath dengan BS 2. Contoh yang bagus tentang cara menggunakan lxml. Bisakah kita melihatnya pada dokumentasi tertentu bahwa "kita tidak dapat mengimplementasikan xpath menggunakan BS dalam bentuk tertulis", karena kita harus menunjukkan beberapa bukti kepada seseorang yang meminta klarifikasi kan?
Shiva Krishna Bavandla
8
Sulit untuk membuktikan negatif; yang BeautifulSoup 4 dokumentasi memiliki fungsi pencarian dan tidak ada hits untuk 'XPath'.
Martijn Pieters
123

Saya dapat mengonfirmasi bahwa tidak ada dukungan XPath dalam Beautiful Soup.

Leonard Richardson
sumber
76
Catatan: Leonard Richardson adalah pengarang Beautiful Soup, seperti yang akan Anda lihat jika Anda mengklik ke profil penggunanya.
senshin
23
Akan sangat menyenangkan bisa menggunakan XPATH dalam BeautifulSoup
DarthOpto
4
Jadi apa alternatifnya?
static_rtti
40

Seperti yang dikatakan orang lain, BeautifulSoup tidak memiliki dukungan xpath. Mungkin ada beberapa cara untuk mendapatkan sesuatu dari xpath, termasuk menggunakan Selenium. Namun, berikut adalah solusi yang berfungsi baik di Python 2 atau 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)

Saya menggunakan ini sebagai referensi.

kata-kata
sumber
Satu peringatan: Saya perhatikan jika ada sesuatu di luar root (seperti \ n di luar tag <html> luar), maka mereferensikan xpath oleh root tidak akan berfungsi, Anda harus menggunakan xpath relatif. lxml.de/xpathxslt.html
wordsforthewise
Kode Martijn tidak lagi berfungsi dengan baik (sekarang berusia 4+ tahun ...), baris etree.parse () mencetak ke konsol dan tidak menetapkan nilai ke variabel pohon. Itu klaim yang bagus. Saya pasti tidak bisa mereproduksi itu, dan itu tidak masuk akal . Apakah Anda yakin menggunakan Python 2 untuk menguji kode saya, atau telah menerjemahkan urllib2pustaka yang digunakan ke Python 3 urllib.request?
Martijn Pieters
Ya, itu mungkin kasus saya menggunakan Python3 saat menulis itu dan itu tidak berfungsi seperti yang diharapkan. Baru saja diuji dan milik Anda berfungsi dengan Python2, tetapi Python3 lebih disukai karena 2 sedang matahari terbenam (tidak lagi didukung secara resmi) pada tahun 2020.
kata
sangat setuju, tetapi pertanyaannya di sini menggunakan Python 2 .
Martijn Pieters
17

BeautifulSoup memiliki fungsi bernama findNext dari elemen saat ini yang diarahkan ke childern, jadi:

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

Kode di atas dapat meniru xpath berikut:

div[class=class_value]/div[id=id_value]
pengguna3820561
sumber
1

Saya telah mencari melalui dokumen mereka dan tampaknya tidak ada opsi xpath. Juga, seperti yang Anda lihat di sini pada pertanyaan serupa tentang SO, OP meminta terjemahan dari xpath ke BeautifulSoup, jadi kesimpulan saya adalah - tidak, tidak ada penguraian xpath yang tersedia.

Nikola
sumber
ya sebenarnya sampai sekarang saya menggunakan scrapy yang menggunakan xpath untuk mengambil data di dalam tag. Ini sangat berguna dan mudah untuk mengambil data, tetapi saya mendapat kebutuhan untuk melakukan hal yang sama dengan beautifulsoup jadi menantikannya.
Shiva Krishna Bavandla
1

ketika Anda menggunakan lxml semuanya sederhana:

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

tetapi saat menggunakan BeautifulSoup BS4 semuanya juga sederhana:

  • pertama hapus "//" dan "@"
  • kedua - tambahkan bintang sebelum "="

coba sihir ini:

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

seperti yang Anda lihat, ini tidak mendukung sub-tag, jadi saya menghapus bagian "/ @ href"

Oleksandr Panchenko
sumber
select()untuk pemilih CSS, ini sama sekali bukan XPath. seperti yang Anda lihat, ini tidak mendukung sub-tag Meskipun saya tidak yakin apakah itu benar pada saat itu, yang pasti tidak sekarang.
AMC
1

Mungkin Anda dapat mencoba yang berikut ini tanpa 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
sumber
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()')

Di atas digunakan kombinasi objek Soup dengan lxml dan satu dapat mengekstrak nilai menggunakan xpath

Deepak rkm
sumber
0

Ini adalah utas yang cukup lama, tetapi ada solusi penyelesaian sekarang, yang mungkin belum ada di BeautifulSoup pada saat itu.

Inilah contoh dari apa yang saya lakukan. Saya menggunakan modul "request" untuk membaca RSS feed dan mendapatkan konten teksnya dalam variabel yang disebut "rss_text". Dengan itu, saya menjalankannya melalui BeautifulSoup, mencari xpath / rss / channel / title, dan mengambil isinya. Ini bukan XPath dalam semua kemuliaannya (wildcard, banyak jalur, dll.), Tetapi jika Anda hanya memiliki jalur dasar yang ingin Anda temukan, ini berfungsi.

from bs4 import BeautifulSoup
rss_obj = BeautifulSoup(rss_text, 'xml')
cls.title = rss_obj.rss.channel.title.get_text()
David A
sumber
Saya percaya ini hanya menemukan elemen anak. XPath adalah hal lain?
raffaem