BeautifulSoup Ambil Teks Halaman Web yang Terlihat

124

Pada dasarnya, saya ingin menggunakan BeautifulSoup untuk mengambil teks yang terlihat di halaman web secara ketat . Misalnya, halaman web ini adalah kasus uji saya. Dan saya terutama ingin mendapatkan teks isi (artikel) dan bahkan mungkin beberapa nama tab di sana-sini. Saya telah mencoba saran dalam pertanyaan SO ini yang mengembalikan banyak <script>tag dan komentar html yang tidak saya inginkan. Saya tidak dapat menemukan argumen yang saya perlukan untuk fungsi tersebut findAll()agar mendapatkan teks yang terlihat di halaman web.

Jadi, bagaimana cara menemukan semua teks yang terlihat tidak termasuk skrip, komentar, css, dll.?

pengguna233864
sumber

Jawaban:

239

Coba ini:

from bs4 import BeautifulSoup
from bs4.element import Comment
import urllib.request


def tag_visible(element):
    if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
        return False
    if isinstance(element, Comment):
        return False
    return True


def text_from_html(body):
    soup = BeautifulSoup(body, 'html.parser')
    texts = soup.findAll(text=True)
    visible_texts = filter(tag_visible, texts)  
    return u" ".join(t.strip() for t in visible_texts)

html = urllib.request.urlopen('http://www.nytimes.com/2009/12/21/us/21storm.html').read()
print(text_from_html(html))
jbochi
sumber
47
+1 karena soup.findAll(text=True)tidak pernah tahu tentang fitur itu
Hartley Brody
7
Untuk BS4 terbaru (paling tidak) Anda dapat mengidentifikasi komentar isinstance(element, Comment)daripada mencocokkan dengan regex.
tripleee
5
Saya yakin baris 2 seharusnyasoup = BeautifulSoup(html)
jczaplew
11
Dalam fungsi visible, elif untuk menemukan komentar tampaknya tidak berfungsi. saya harus memperbaruinya menjadi elif isinstance(element,bs4.element.Comment):. Saya juga menambahkan 'meta' ke daftar orang tua.
Russ Savage
4
Filter di atas memiliki banyak \ n pada hasil, tambahkan kode berikut untuk menghilangkan spasi dan baris baru: elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫
37

Jawaban yang disetujui dari @jbochi tidak berhasil untuk saya. Pemanggilan fungsi str () memunculkan pengecualian karena tidak bisa mengenkode karakter non-ascii dalam elemen BeautifulSoup. Berikut adalah cara yang lebih ringkas untuk memfilter halaman web contoh menjadi teks yang terlihat.

html = open('21storm.html').read()
soup = BeautifulSoup(html)
[s.extract() for s in soup(['style', 'script', '[document]', 'head', 'title'])]
visible_text = soup.getText()
nmgeek
sumber
1
Jika str(element)gagal dengan masalah encoding, Anda harus mencoba unicode(element)jika Anda menggunakan Python 2.
mknaf
31
import urllib
from bs4 import BeautifulSoup

url = "https://www.yahoo.com"
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)

# kill all script and style elements
for script in soup(["script", "style"]):
    script.extract()    # rip it out

# get text
text = soup.get_text()

# break into lines and remove leading and trailing space on each
lines = (line.strip() for line in text.splitlines())
# break multi-headlines into a line each
chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
# drop blank lines
text = '\n'.join(chunk for chunk in chunks if chunk)

print(text.encode('utf-8'))
anak dusun
sumber
4
Jawaban sebelumnya tidak berhasil untuk saya, tetapi ini berhasil :)
rjurney
Jika saya mencoba ini di url imfuna.com, itu hanya mengembalikan 6 kata (Inventaris Properti Imfuna dan Aplikasi Inspeksi) meskipun faktanya ada lebih banyak teks / kata di halaman ... ada ide mengapa jawaban ini tidak berfungsi untuk itu url? @bumpkin
thetest_1
10

Saya benar-benar menghormati penggunaan Beautiful Soup untuk mendapatkan konten yang dirender, tetapi itu mungkin bukan paket yang ideal untuk memperoleh konten yang dirender di halaman.

Saya mengalami masalah serupa untuk mendapatkan konten yang dirender, atau konten yang terlihat di browser biasa. Secara khusus saya memiliki banyak kasus yang mungkin tidak biasa untuk dikerjakan dengan contoh sederhana di bawah ini. Dalam kasus ini, tag yang tidak dapat ditampilkan berada di dalam tag gaya, dan tidak terlihat di banyak browser yang telah saya periksa. Variasi lain yang ada seperti menentukan pengaturan tag kelas ditampilkan ke tidak ada. Kemudian gunakan kelas ini untuk div.

<html>
  <title>  Title here</title>

  <body>

    lots of text here <p> <br>
    <h1> even headings </h1>

    <style type="text/css"> 
        <div > this will not be visible </div> 
    </style>


  </body>

</html>

Salah satu solusi yang diposting di atas adalah:

html = Utilities.ReadFile('simple.html')
soup = BeautifulSoup.BeautifulSoup(html)
texts = soup.findAll(text=True)
visible_texts = filter(visible, texts)
print(visible_texts)


[u'\n', u'\n', u'\n\n        lots of text here ', u' ', u'\n', u' even headings ', u'\n', u' this will not be visible ', u'\n', u'\n']

Solusi ini tentu saja memiliki aplikasi dalam banyak kasus dan melakukan pekerjaan dengan cukup baik secara umum tetapi dalam html yang diposting di atas tetap mempertahankan teks yang tidak ditampilkan. Setelah mencari SO beberapa solusi muncul di sini BeautifulSoup get_text tidak menghapus semua tag dan JavaScript dan di sini Rendered HTML menjadi teks biasa menggunakan Python

Saya mencoba kedua solusi ini: html2text dan nltk.clean_html dan terkejut dengan hasil pengaturan waktu, jadi saya pikir mereka menjamin jawaban untuk anak cucu. Tentu saja kecepatannya sangat bergantung pada isi datanya ...

Satu jawaban di sini dari @Helge adalah tentang menggunakan nltk untuk semua hal.

import nltk

%timeit nltk.clean_html(html)
was returning 153 us per loop

Ini bekerja sangat baik untuk mengembalikan string dengan html yang diberikan. Modul nltk ini lebih cepat daripada html2text, meskipun mungkin html2text lebih kuat.

betterHTML = html.decode(errors='ignore')
%timeit html2text.html2text(betterHTML)
%3.09 ms per loop
Paul
sumber
3

Jika Anda peduli dengan kinerja, berikut cara lain yang lebih efisien:

import re

INVISIBLE_ELEMS = ('style', 'script', 'head', 'title')
RE_SPACES = re.compile(r'\s{3,}')

def visible_texts(soup):
    """ get visible text from a document """
    text = ' '.join([
        s for s in soup.strings
        if s.parent.name not in INVISIBLE_ELEMS
    ])
    # collapse multiple spaces to two spaces.
    return RE_SPACES.sub('  ', text)

soup.stringsadalah sebuah iterator, dan ini mengembalikan NavigableStringsehingga Anda dapat memeriksa nama tag induk secara langsung, tanpa melalui banyak pengulangan.

Bir Polor
sumber
2

Judul ada di dalam <nyt_headline>tag, yang bertumpuk di dalam <h1>tag dan <div>tag dengan id "artikel".

soup.findAll('nyt_headline', limit=1)

Harus bekerja.

Badan artikel berada di dalam <nyt_text>tag, yang <div>bertumpuk di dalam tag dengan id "articleBody". Di dalam <nyt_text> elemen, teks itu sendiri berada di dalam <p> tag. Gambar tidak ada di dalam <p>tag tersebut. Sulit bagi saya untuk bereksperimen dengan sintaks, tetapi saya mengharapkan potongan yang berfungsi terlihat seperti ini.

text = soup.findAll('nyt_text', limit=1)[0]
text.findAll('p')
Ewan Todd
sumber
Saya yakin ini berfungsi untuk kasus uji ini, namun, mencari jawaban yang lebih umum yang dapat diterapkan ke berbagai situs web lain ... Sejauh ini, saya telah mencoba menggunakan regexps untuk menemukan tag <script> </script> dan < ! -. * -> mengomentari dan menggantinya dengan "" tetapi itu bahkan terbukti agak sulit untuk alasan jumlah ..
user233864
2

Sementara, saya sepenuhnya menyarankan menggunakan beautiful-soup secara umum, jika ada yang ingin menampilkan bagian yang terlihat dari html yang salah format (misalnya di mana Anda hanya memiliki segmen atau baris dari halaman web) untuk alasan apa pun, berikut ini akan menghapus konten di antara tag <dan >:

import re   ## only use with malformed html - this is not efficient
def display_visible_html_using_re(text):             
    return(re.sub("(\<.*?\>)", "",text))
kyrenia
sumber
2

Menggunakan BeautifulSoup cara termudah dengan lebih sedikit kode hanya untuk mendapatkan string, tanpa baris kosong dan omong kosong.

tag = <Parent_Tag_that_contains_the_data>
soup = BeautifulSoup(tag, 'html.parser')

for i in soup.stripped_strings:
    print repr(i)
Diego Suarez
sumber
0

Cara paling sederhana untuk menangani kasus ini adalah dengan menggunakan getattr(). Anda dapat menyesuaikan contoh ini dengan kebutuhan Anda:

from bs4 import BeautifulSoup

source_html = """
<span class="ratingsDisplay">
    <a class="ratingNumber" href="https://www.youtube.com/watch?v=oHg5SJYRHA0" target="_blank" rel="noopener">
        <span class="ratingsContent">3.7</span>
    </a>
</span>
"""

soup = BeautifulSoup(source_html, "lxml")
my_ratings = getattr(soup.find('span', {"class": "ratingsContent"}), "text", None)
print(my_ratings)

Ini akan menemukan elemen teks "3.7", di dalam objek tag <span class="ratingsContent">3.7</span>jika ada, namun default NoneTypejika tidak ada.

getattr(object, name[, default])

Kembalikan nilai dari atribut bernama objek. nama harus berupa string. Jika string adalah nama salah satu atribut objek, hasilnya adalah nilai atribut itu. Misalnya, getattr (x, 'foobar') setara dengan x.foobar. Jika atribut bernama tidak ada, default dikembalikan jika disediakan, jika tidak, AttributeError dimunculkan.

David Yerrington
sumber
0
from bs4 import BeautifulSoup
from bs4.element import Comment
import urllib.request
import re
import ssl

def tag_visible(element):
    if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
        return False
    if isinstance(element, Comment):
        return False
    if re.match(r"[\n]+",str(element)): return False
    return True
def text_from_html(url):
    body = urllib.request.urlopen(url,context=ssl._create_unverified_context()).read()
    soup = BeautifulSoup(body ,"lxml")
    texts = soup.findAll(text=True)
    visible_texts = filter(tag_visible, texts)  
    text = u",".join(t.strip() for t in visible_texts)
    text = text.lstrip().rstrip()
    text = text.split(',')
    clean_text = ''
    for sen in text:
        if sen:
            sen = sen.rstrip().lstrip()
            clean_text += sen+','
    return clean_text
url = 'http://www.nytimes.com/2009/12/21/us/21storm.html'
print(text_from_html(url))
kamran kausar
sumber