BeautifulSoup4のtextで要素の文字列を得る
- 作成日: 2020-11-24
- 更新日: 2023-12-24
- カテゴリ: BeautifulSoup4
BeautifulSoup4のtextの使い方
Pythonには「BeautifulSoup4(ビューティフル・スープ・フォー)」というHTML/XMLをパースする外部ライブラリがあります。
BeautifulSoup4のbs4.BeautifulSoup
またはbs4.element.Tag
オブジェクトにはtext
という属性があります。
この属性を参照すると、タグの持つテキスト・コンテンツを取得することが可能です。
この記事ではTag
オブジェクトのtext
属性の使い方について解説します。
具体的には↓を見ていきます。
- text属性の正体
- get_text()の構造
- text属性で要素のテキストを得る
- get_text()でセパレータを指定する
- get_text()でストリップする
- get_text()でタイプを指定する
- textとstringの違い
text属性の正体
bs4.BeautifulSoup
クラスはbs4.element.Tag
を継承したクラスです。
from bs4 import BeautifulSoup
from bs4.element import Tag
soup = BeautifulSoup('', 'html.parser')
print(type(soup))
print(isinstance(soup, Tag))
# <class 'bs4.BeautifulSoup'>
# True
この記事で扱うtext
属性はこのbs4.element.Tag
の属性です。
text
は↓のようにして使うことが出来ます。
print(soup.text)
↑のようにタグのtext
属性を参照すると、タグ内のテキストを取得することが出来ます。
BeautifulSoup4ではtext
属性はどのように定義されているのでしょうか?
BeautifulSoup4のソースコードを見てみます。
text
属性はTag
クラスで↓のように定義されています。
text = property(get_text)
property()
関数はプロパティを生成する関数です。
よってtext
属性はプロパティであることがわかります。
property()
の第1引数はゲッターで使う関数です。
つまりtext
属性はテキストを取得するときにTag
クラスのget_text()
メソッドを使っていることになります。
get_text()の構造
text
属性の正体はTag.get_text()
でした。
get_text()
は↓のようなメソッドになっています。
def get_text(self, separator="", strip=False,
types=(NavigableString, CData)):
"""Get all child strings, concatenated using the given separator.
:param separator: Strings will be concatenated using this separator.
:param strip: If True, strings will be stripped before being
concatenated.
:types: A tuple of NavigableString subclasses. Any strings of
a subclass not found in this list will be ignored. By
default, this means only NavigableString and CData objects
will be considered. So no comments, processing instructions,
stylesheets, etc.
:return: A string.
"""
return separator.join([s for s in self._all_strings(
strip, types=types)])
和訳しました↓。
def get_text(self, separator="", strip=False,
types=(NavigableString, CData)):
"""すべての子の文字列を、与えられたセパレーターで結合して取得する。
:param separator: 文字列はこのセパレーターの文字列で結合されます。
:param strip: もしTrueなら、文字列は結合前にストリップされます。
:types: NavigableStringサブクラスのタプル。
このリストにないサブクラスの文字列は無視されます。
デフォルトでは、これはNavigableStringとCDataオブジェクトのみが考慮されることを意味します。
つまり、コメントや処理命令、スタイルシートなどは無視されます。
:return: 文字列
"""
return separator.join([s for s in self._all_strings(
strip, types=types)])
_all_strings()
メソッドは内部でdescendants
をループで回して走査しています。
descendants
はプロパティで、contents
属性内の要素を辿りながら要素をyield
します。
つまりget_text()
はcontents
属性内の要素を順に辿りながらテキストを取得する関数と言うことになります。
これは単一の要素のテキストではなく、子要素も含めて再帰的にテキストを取得するという意味です。
contents
属性はただのリストです。
Tag
クラスはこのcontents
リストにパースした要素を格納しています。
後述のstring
プロパティもtext
と同様にこのcontents
を参照しています。
contents
リストに格納される要素は、代表的なのはNavigableString
です。
これは要素内の文字列を表現するクラスです。
このNavigableString
から派生しているクラスも複数定義されています。
typesに指定できるクラスの一覧
↓はget_text()
のtypes
引数のタプルに格納できるクラスの一覧です。
NavigableString:
最も基本的な文字列を表現するクラスです。
PreformattedString
Comment
クラスやCData
クラスを表現するための抽象クラスです。
CData
CDATAブロックを表すクラスです。
プレフィックスは<![CDATA[
です。
サフィックスは]]>
です。
ProcessingInstruction
SGMLを表すクラスです。
SGMLとはXMLの前身になったフォーマットです。
プレフィックスは<?
です。
サフィックスは>
です。
XMLProcessingInstruction
XMLを表すクラスです。
プレフィックスは<?
です。
サフィックスは?>
です。
Comment
HTML/XMLのコメントを表すクラスです。
プレフィックスは<!--
です。
サフィックスは-->
です。
Declaration
XMLの宣言を表すクラスです。
プレフィックスは<?
です。
サフィックスは?>
です。
Doctype
DOCTYPE
を表すクラスです。
プレフィックスは<!DOCTYPE
です。
サフィックスは>\n
です。
Stylesheet
スタイルシートを表すクラスです。
Script
スクリプトを表すクラスです。
JavaScriptなど。
TemplateString
大きな文書の中に埋め込まれたHTMLテンプレートの中で見られる文字列を表すNavigableString。
文書の本文と区別するために使用します。
text属性で要素のテキストを得る
text
属性で取得した要素のテキストを取得するには↓のようにします。
from bs4 import BeautifulSoup
html = '''
<div>
blue
<p>yellow</p>
<span>green</span>
</div>
'''
soup = BeautifulSoup(html, 'html.parser')
el = soup.find('div')
print(el.text)
↑の出力結果↓。
blue
yellow
green
↑のように子要素のテキストも再帰的に取得してる点に注目してください。
これがtext
の特徴です。
get_text()でセパレータを指定する
get_text()
の引数separator
に文字列を指定すると、要素の文字列をそのセパレーターで囲って取得します。
from bs4 import BeautifulSoup
html = '''
<div>
blue
<p>yellow</p>
<span>green</span>
</div>
'''
soup = BeautifulSoup(html, 'html.parser')
el = soup.find('div')
print(el.get_text(separator='★'))
↑の出力結果↓。
blue
★yellow★
★green★
↑のようにセパレーターは子要素のみに作用します。
親要素、↑の例ではdiv
には作用しません。
get_text()でストリップする
get_text()
で要素のテキストを取得するときにテキストの両端をストリップするにはstrip
引数にTrue
を指定します。
from bs4 import BeautifulSoup
html = '''
<div>
blue
<p> yellow </p>
<span> green </span>
</div>
'''
soup = BeautifulSoup(html, 'html.parser')
el = soup.find('div')
print(soup.get_text(strip=True))
↑の出力結果↓。
blueyellowgreen
get_text()でタイプを指定する
get_text()
はデフォルトでは↓の文字列のみ取得します。
- NavigableString
- CData
このデフォルトの状態ではたとえば<script>
タグの中身は取得できません。
<script>
タグの内容を取得したい場合はbs4.element.Script
をタイプに指定します。
from bs4 import BeautifulSoup
from bs4.element import Script
html = '''
<script>console.log(1)</script>
'''
soup = BeautifulSoup(html, 'html.parser')
print(soup.get_text(types=(Script, )))
textとstringの違い
text
と似た属性にstring
というのもあります。
text
との違いは、text
が複数の要素を再帰的に取得するのに対し、string
は最初の子要素のみに作用する点です。
これはプロパティで、中身は↓のようになっています。
@property
def string(self):
"""Convenience property to get the single string within this
PageElement.
TODO It might make sense to have NavigableString.string return
itself.
:return: If this element has a single string child, return
value is that string. If this element has one child tag,
return value is the 'string' attribute of the child tag,
recursively. If this element is itself a string, has no
children, or has more than one child, return value is None.
"""
if len(self.contents) != 1:
return None
child = self.contents[0]
if isinstance(child, NavigableString):
return child
return child.string
↑の実装を見ると、contents
リストの最初の要素を取得して返していることがわかります。
string
の仕様はちょっと独特です。
まずcontents
が1
じゃない場合にはNone
を返しています。
つまり子要素が複数ある要素の場合は常に得られるのはNone
です。
それからcontents
の最初の要素がNavigableString
でない場合は再帰的にテキストを取得します。
つまり↓のようなHTMLはstring
はNone
を返しますが、
<p>1</p>
<p>2</p>
<p>3</p>
↓のようなHTMLではstring
はテキストを取得できます。
<div><p><span>1</span></p></div>
おわりに
BeautifulSoup4のtext
属性は非常によく使うプロパティです。
使えるようになっておくと色々なシーンで役立ちます。
🦝 < textの正体はget_text()! 謎はすべて解けた!