回答:
これを試して:
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))
isinstance(element, Comment)
、正規表現と一致する代わりにコメントを特定できます。
soup = BeautifulSoup(html)
elif isinstance(element,bs4.element.Comment):
。両親のリストに「メタ」も追加しました。
elif re.match(r"[\s\r\n]+",str(element)): return False
@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()
str(element)
エンコードの問題で失敗し、あなたは試してみてくださいunicode(element)
、あなたは、Python 2を使用している場合、代わりに
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'))
私はレンダリングされたコンテンツを取得するために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
パフォーマンスを重視する場合は、さらに効率的な方法を次に示します。
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
、複数のループを経由せずに親のタグ名を直接確認できるように戻ります。
タイトルは<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')
このケースを処理する最も簡単な方法は、を使用すること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が発生します。
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))
soup.findAll(text=True)
その機能を知らなかった+1