BeautifulSoup Grab Visible Webpage Text


124

基本的に、BeautifulSoupを使用して、Webページに表示されるテキストを厳密に取得します。たとえば、このウェブページは私のテストケースです。そして、私は主に本文テキスト(記事)を取得したいと思っています。このSOの質問<script>、不要なタグやHTMLコメントを多数返す提案を試しました。findAll()Webページに表示されるテキストを取得するためだけに、関数に必要な引数を理解できません。

それで、スクリプト、コメント、CSSなどを除くすべての表示テキストをどのように見つければよいですか?

回答:


239

これを試して:

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 soup.findAll(text=True)その機能を知らなかった+1
Hartley Brody

7
最近のBS4(少なくとも)の場合isinstance(element, Comment)、正規表現と一致する代わりにコメントを特定できます。
tripleee 2013年

5
2行目は次のようになるはずですsoup = BeautifulSoup(html)
jczaplew

11
visible関数では、コメントを見つけるためのelifが機能していなかったようです。に更新する必要がありましたelif isinstance(element,bs4.element.Comment):。両親のリストに「メタ」も追加しました。
Russ Savage

4
上記フィルタは、ホワイトスペースと新しい行を排除するために、次のコードを追加し、その中、n \をたくさん持っている: elif re.match(r"[\s\r\n]+",str(element)): return False
天才小飞猫

37

@jbochiからの承認済みの回答は私にとってはうまくいきません。str()関数呼び出しは、BeautifulSoup要素の非ASCII文字をエンコードできないため、例外を発生させます。以下は、サンプルWebページを表示テキストにフィルターするためのより簡潔な方法です。

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

1
場合はstr(element)エンコードの問題で失敗し、あなたは試してみてくださいunicode(element)、あなたは、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
以前の回答は私にとってはうまく
いき

imfuna.comのURLでこれを試した場合、ページにテキスト/単語がはるかに多いにもかかわらず、6単語(Imfunaプロパティインベントリおよび検査アプリ)しか返されません...この答えがうまくいかない理由URL?@bumpkin
the_t_test_1

10

私はレンダリングされたコンテンツを取得するためにBeautiful Soupを使用することを完全に尊重しますが、ページ上のレンダリングされたコンテンツを取得するための理想的なパッケージではないかもしれません。

レンダリングされたコンテンツ、または一般的なブラウザーで表示されるコンテンツを取得するために同様の問題がありました。特に、以下のような単純な例を処理するための、おそらく非定型的なケースがたくさんありました。この場合、非表示タグはスタイルタグにネストされており、チェックした多くのブラウザーでは表示されません。他にも、クラスタグ設定の表示をnoneに定義するなどのバリエーションがあります。次に、このクラスを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>

上記のソリューションの1つは次のとおりです。

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

このソリューションは確かに多くの場合にアプリケーションを備えており、一般的に非常にうまく機能しますが、上に投稿されたHTMLでは、レンダリングされないテキストを保持します。SOを検索した後、2つのソリューションがここに表示されます。BeautifulSoupget_textはすべてのタグとJavaScript削除しません。ここでは、Pythonを使用してHTMLをプレーンテキストにレンダリングしました

私はhtml2textとnltk.clean_htmlの両方のソリューションを試しましたが、タイミングの結果に驚いたので、後世のための答えが必要だと思いました。もちろん、速度はデータの内容に大きく依存します...

@Helgeからの1つの回答は、すべてのnltkの使用に関するものでした。

import nltk

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

レンダリングされたhtmlを含む文字列を返すことは本当にうまくいきました。このnltkモジュールはhtml2textよりも高速でしたが、おそらくhtml2textの方が堅牢です。

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

3

パフォーマンスを重視する場合は、さらに効率的な方法を次に示します。

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イテレータでありNavigableString、複数のループを経由せずに親のタグ名を直接確認できるように戻ります。


2

タイトルは<nyt_headline>タグ内にあり、<h1>タグと<div>タグ「article」のタグ内にネストされています。

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

うまくいくはずです。

記事の本文は<nyt_text>タグ内にあり<div>、IDが「articleBody」のタグ内にネストされています。<nyt_text> 要素内では、テキスト自体が<p> タグ内に含まれています。画像はこれらの<p>タグ内にありません。構文を試すのは難しいですが、動作するスクレイプはこのようなものになると思います。

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

これはこのテストケースで機能すると確信していますが、他のさまざまなWebサイトに適用できるより一般的な答えを探しています...これまでのところ、正規表現を使用して<script> </ script>タグと< ! - 。* - >コメントや「」に置き換えるが、それも和の理由でちょっと難しい証明しています...
user233864

2

一般的には、beautiful-soupを使用することをお勧めしますが、何らかの理由で不正なHTMLの表示部分(たとえば、Webページのセグメントまたは行のみ)を表示しようとしている場合は、次のようにします。<>タグの間のコンテンツを削除します。

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

2

BeautifulSoupを使用すると、コードを減らして空行やがらくたなしで文字列を取得する最も簡単な方法を使用できます。

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

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

0

このケースを処理する最も簡単な方法は、を使用することgetattr()です。この例をニーズに合わせることができます。

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)

これにより"3.7"、タグオブジェクト内にテキスト要素<span class="ratingsContent">3.7</span>が存在する場合は検索されますが、存在NoneTypeしない場合はデフォルトになります。

getattr(object, name[, default])

オブジェクトの名前付き属性の値を返します。名前は文字列でなければなりません。文字列がオブジェクトの属性の1つの名前である場合、結果はその属性の値になります。たとえば、getattr(x、 'foobar')はx.foobarと同等です。名前付き属性が存在しない場合は、デフォルトが返されます。それ以外の場合は、AttributeErrorが発生します。


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))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.