Văn bản trang web hiển thị của BeautifulSoup Grab


124

Về cơ bản, tôi muốn sử dụng BeautifulSoup để lấy đúng văn bản hiển thị trên trang web. Ví dụ, trang web này là trường hợp thử nghiệm của tôi. Và tôi chủ yếu muốn chỉ lấy phần nội dung (bài báo) và thậm chí có thể là một vài tên tab ở đây và ở đó. Tôi đã thử đề xuất trong câu hỏi SO này trả về rất nhiều <script>thẻ và nhận xét html mà tôi không muốn. Tôi không thể tìm ra các đối số mà tôi cần cho hàm findAll()để chỉ nhận được các văn bản hiển thị trên trang web.

Vì vậy, làm cách nào để tìm tất cả văn bản hiển thị ngoại trừ script, bình luận, css, v.v.?

Câu trả lời:


239

Thử cái này:

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

47
1 cho soup.findAll(text=True)không bao giờ biết về tính năng
Hartley Brody

7
Đối với BS4 gần đây (ít nhất), bạn có thể xác định nhận xét isinstance(element, Comment)thay vì khớp với regex.
tripleee

5
Tôi tin rằng dòng 2 nên làsoup = BeautifulSoup(html)
jczaplew

11
Trong chức năng hiển thị, elif để tìm nhận xét dường như không hoạt động. tôi đã phải cập nhật nó lên elif isinstance(element,bs4.element.Comment):. Tôi cũng đã thêm 'meta' vào danh sách các bậc cha mẹ.
Russ Savage

4
Bộ lọc ở trên có nhiều \ n trong kết quả, hãy thêm mã sau để loại bỏ khoảng trắng và các dòng mới: elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫

37

Câu trả lời được chấp thuận từ @jbochi không phù hợp với tôi. Lệnh gọi hàm str () tạo ra một ngoại lệ vì nó không thể mã hóa các ký tự không phải ascii trong phần tử BeautifulSoup. Đây là một cách ngắn gọn hơn để lọc trang web mẫu thành văn bản hiển thị.

html = open('21storm.html').read()
soup = BeautifulSoup(html)
[s.extract() for s in soup(['style', 'script', '[document]', 'head', 'title'])]
visible_text = soup.getText()

1
Nếu str(element)không thành công với các vấn đề mã hóa, bạn nên cố gắng unicode(element)thay vì nếu bạn đang sử dụng 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'))

4
Các câu trả lời trước đó không hoạt động với tôi, nhưng điều này đã làm được :)
rjurney

Nếu tôi thử điều này trên url imfuna.com, nó chỉ trả về 6 từ (Ứng dụng kiểm kê và kiểm tra tài sản Imfuna) mặc dù thực tế là có nhiều văn bản / từ hơn trên trang ... bất kỳ ý kiến ​​nào tại sao câu trả lời này không hoạt động cho điều đó url? @bumpkin
the_t_test_1

10

Tôi hoàn toàn tôn trọng việc sử dụng Beautiful Soup để tải nội dung, nhưng nó có thể không phải là gói lý tưởng để có được nội dung được kết xuất trên một trang.

Tôi đã gặp sự cố tương tự khi tải nội dung được hiển thị hoặc nội dung hiển thị trong một trình duyệt thông thường. Đặc biệt, tôi có nhiều trường hợp có lẽ không điển hình để làm việc với một ví dụ đơn giản như vậy dưới đây. Trong trường hợp này, thẻ không hiển thị được lồng trong thẻ kiểu và không hiển thị trong nhiều trình duyệt mà tôi đã kiểm tra. Các biến thể khác tồn tại, chẳng hạn như xác định hiển thị cài đặt thẻ lớp thành không. Sau đó, sử dụng lớp này cho 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>

Một giải pháp được đăng ở trên là:

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']

Giải pháp này chắc chắn có ứng dụng trong nhiều trường hợp và nói chung thực hiện công việc khá tốt nhưng trong html được đăng ở trên, nó vẫn giữ lại văn bản không được hiển thị. Sau khi tìm kiếm VẬY, một vài giải pháp đã được đưa ra ở đây BeautifulSoup get_text không loại bỏ tất cả các thẻ và JavaScript và ở đây Hiển thị HTML thành văn bản thuần túy bằng Python

Tôi đã thử cả hai giải pháp này: html2text và nltk.clean_html và rất ngạc nhiên trước kết quả thời gian nên nghĩ rằng chúng đảm bảo một câu trả lời cho hậu thế. Tất nhiên, tốc độ phụ thuộc nhiều vào nội dung của dữ liệu ...

Một câu trả lời ở đây từ @Helge là về việc sử dụng nltk cho mọi thứ.

import nltk

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

Nó hoạt động thực sự tốt để trả về một chuỗi với html được kết xuất. Mô-đun nltk này nhanh hơn cả html2text, mặc dù có lẽ html2text mạnh mẽ hơn.

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

3

Nếu bạn quan tâm đến hiệu suất, đây là một cách khác hiệu quả hơn:

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.stringslà một trình lặp và nó trả về NavigableStringđể bạn có thể kiểm tra trực tiếp tên thẻ của cha mẹ mà không cần trải qua nhiều vòng lặp.


2

Tiêu đề nằm bên trong <nyt_headline>thẻ, được lồng bên trong <h1>thẻ và <div>thẻ có id "article".

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

Nên làm việc.

Nội dung bài viết nằm bên trong <nyt_text>thẻ, được lồng bên trong <div>thẻ có id "articleBody". Bên trong <nyt_text> phần tử, chính văn bản được chứa trong <p> các thẻ. Hình ảnh không nằm trong các <p>thẻ đó. Thật khó cho tôi để thử nghiệm với cú pháp, nhưng tôi mong đợi một đoạn mã hoạt động trông giống như thế này.

text = soup.findAll('nyt_text', limit=1)[0]
text.findAll('p')

Tôi chắc chắn rằng điều này phù hợp với trường hợp thử nghiệm này, tuy nhiên, đang tìm kiếm một câu trả lời chung chung hơn có thể được áp dụng cho nhiều trang web khác ... Cho đến nay, tôi đã thử sử dụng regexps để tìm các thẻ <script> </script> và < ! -. * -> ý kiến và thay thế bằng "" nhưng điều đó thậm chí còn minh kinda khó khăn vì lý do tổng ..
user233864

2

Mặc dù, tôi hoàn toàn khuyên bạn nên sử dụng beautiful-soup nói chung, nếu bất kỳ ai đang tìm cách hiển thị các phần có thể nhìn thấy của html không đúng định dạng (ví dụ: nơi bạn chỉ có một phân đoạn hoặc dòng của trang web) vì bất kỳ lý do gì, như sau sẽ xóa nội dung giữa <>các thẻ:

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

2

Sử dụng BeautifulSoup một cách dễ dàng nhất với ít mã hơn để chỉ lấy các chuỗi, không có dòng trống và tào lao.

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

for i in soup.stripped_strings:
    print repr(i)

0

Cách đơn giản nhất để xử lý trường hợp này là sử dụng getattr(). Bạn có thể điều chỉnh ví dụ này cho phù hợp với nhu cầu của mình:

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)

Điều này sẽ tìm thấy phần tử văn bản "3.7", trong đối tượng thẻ <span class="ratingsContent">3.7</span>khi nó tồn tại, tuy nhiên, mặc định là NoneTypekhi nó không tồn tại .

getattr(object, name[, default])

Trả về giá trị của thuộc tính được đặt tên của đối tượng. tên phải là một chuỗi. Nếu chuỗi là tên của một trong các thuộc tính của đối tượng, thì kết quả là giá trị của thuộc tính đó. Ví dụ: getattr (x, 'foobar') tương đương với x.foobar. Nếu thuộc tính được đặt tên không tồn tại, mặc định sẽ được trả về nếu được cung cấp, nếu không, AttributeError sẽ xuất hiện.


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))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.