2009-12-20 12 views
86

Về cơ bản, tôi muốn sử dụng BeautifulSoup để chụp đúng văn bản hiển thị trên trang web. Ví dụ: this webpage là trường hợp thử nghiệm của tôi. Và tôi chủ yếu chỉ muốn lấy nội dung (bài báo) và thậm chí có thể có một vài cái tên ở đây và ở đó. Tôi đã thử đề xuất trong số SO question trả về nhiều thẻ <script> 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ố tôi cần cho hàm findAll() để chỉ nhận các văn bản hiển thị trên trang web.BeautifulSoup Grab Văn bản trang web có thể nhìn thấy

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

Trả lời

142

Hãy thử điều 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)) 
+1

@jbochi Tôi đã thay thế dòng 3 của hiển thị() bằng re.match ('. * . *', string, re.DOTALL). Của bạn dường như chỉ hoạt động nếu toàn bộ nội dung * của văn bản là nhận xét, nhưng nếu có một không gian ban đầu hoặc dòng mới thì html 'ẩn' sẽ được trả về. Giải pháp của tôi là quá tích cực ở chỗ nó sẽ đánh dấu toàn bộ phần tử là vô hình, nhưng với mục đích của tôi thì tốt. – Trindaz

+25

+1 cho 'soup.findAll (text = True)' không bao giờ biết về tính năng đó –

+6

Đối với BS4 gần đây (ít nhất) bạn có thể nhận dạng các chú thích với 'isinstance (element, Comment)' thay vì kết hợp với regex. – tripleee

1

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

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

Nên hoạt động.

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

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

Tuy nhiên, tôi chắc chắn rằng điều này hoạt động cho trường hợp thử nghiệm này, tìm kiếm câu trả lời 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ẻ nhận xét và thay thế chúng bằng "" nhưng điều đó thậm chí còn gây khó khăn cho lý do tổng hợp .. – user233864

25

Câu trả lời đã được phê duyệt từ @jbochi không làm việc cho tôi. Cuộc gọi hàm str() đặt 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. Dưới đây là một cách gọn gàng hơn để lọc trang web ví dụ 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)' thất bại với các vấn đề mã hóa, bạn nên thử 'unicode (element)' thay vì nếu bạn đang sử dụng Python 2. – mknaf

8

Tôi hoàn toàn tôn trọng sử dụng Súp đẹp để lấy nội dung trả lại nhưng có thể không phải là gói lý tưởng để có được nội dung được hiển thị trên trang.

Tôi gặp sự cố tương tự để hiển thị nội dung hoặc nội dung hiển thị trong trình duyệt thông thường. Đặc biệt, tôi có nhiều trường hợp điển hình có thể làm việc với một ví dụ đơn giản dưới đây. Trong trường hợp này, thẻ không thể 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 đã chọn. Các biến thể khác tồn tại như xác định cài đặt thẻ lớp hiển thị 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 đây 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à không được công việc khá tốt thường nhưng trong html được đăng trên đây nó vẫn giữ được văn bản mà không được trả lại. Sau khi tìm kiếm SO một vài giải pháp đã đưa ra tại đây BeautifulSoup get_text does not strip all tags and JavaScript và tại đây Rendered HTML to plain text using Python

Tôi đã thử cả hai giải pháp sau: html2text và nltk.clean_html và ngạc nhiên bởi kết quả tính thời gian nên họ đã đảm bảo câu trả lời cho hậu thế. Tất nhiên, tốc độ cao phụ thuộc 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 của tất cả 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ề chuỗi có html được hiển thị. 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 
21
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')) 
+3

Các câu trả lời trước không hiệu quả với tôi, nhưng điều này đã làm :) – rjurney

+0

Nếu tôi thử trên url imfuna.com nó chỉ trả về 6 từ (Imfuna Property Inventory and Inspection Apps) mặc dù thực tế có nhiều văn bản/từ trên trang ... bất kỳ ý tưởng nào tại sao câu trả lời này không hoạt động cho url đó? @bumpkin –

1

Trong khi, tôi sẽ hoàn toàn đề nghị sử dụng đẹp-súp nói chung, nếu có ai đang tìm kiếm để hiển thị những phần hữu hình của một html bị thay đổi (ví dụ, nơi bạn có chỉ là một phân khúc hoặc đường dây của một web- trang) cho bất cứ điều gì, lý do, sau đây sẽ loại bỏ nội dung giữa <> tags:

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 cách dễ dàng nhất với mã ít hơn để chỉ nhận đượ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

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