From 64753365df3bedd60cde505db792abb20841ada4 Mon Sep 17 00:00:00 2001 From: Leonard Richardson Date: Mon, 11 Nov 2019 11:37:55 -0500 Subject: Moved each translation into its own directory. --- doc.html/index.jp.html | 2822 ++++++++++++++++++++++++++++++++++++++++++++++++ doc.html/index.kr.html | 2476 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 5298 insertions(+) create mode 100644 doc.html/index.jp.html create mode 100644 doc.html/index.kr.html (limited to 'doc.html') diff --git a/doc.html/index.jp.html b/doc.html/index.jp.html new file mode 100644 index 0000000..7f5d8e6 --- /dev/null +++ b/doc.html/index.jp.html @@ -0,0 +1,2822 @@ + + + + + + + + kondou.com - Beautiful Soup 4.2.0 Doc. 日本語訳 (2013-11-19最終更新) + + + + + + + + + + + + + +
+
+
+
+ +
+

Beautiful Soup

+"The Fish-Footman began by producing from under his arm a great letter, nearly as large as himself." +

Beautiful Soup はHTMLやXMLファイルからデータを取得するPythonのライブラリです。あなたの好きなパーサー(構文解析器)を使って、パースツリー(構文木)の探索、検索、修正を行います。 +これはプログラマーの作業時間を大幅に短縮してくれます。

+
+

(訳注)石鹸は食べられない

+

この文書は Beautiful Soup 4.2.0 Documentation の日本語訳です。”Beautiful Soup”を”ビューティフルソープ”と読んでしまう英語が苦手でちょっぴりHな後輩のために翻訳しました。

+

2013年10月29日からこの文書の翻訳をはじめました。11月1日現在まだ全てを訳し終えていませんが、スクレイピングに使う主な部分はとりあえず訳したので、一旦これで公開して、あとは年内を目処にまったりと翻訳をすすめ、あわせて質を高めていこうと思っています。今のところ、パースツリーを修正 以降は、ざっくり訳のためにおかしな表現が多々あることにご注意ください。

+

誤訳やわかりづらいところを見つけたり、なにかご意見があるときには、近藤茂徳()までご連絡ください。こういった翻訳をするのははじめてなので、つっこみ大歓迎です。よろしくお願いします。

+

2013年10月現在、Beautiful Soupについての日本語Webページは、Beautiful Soup 3とBeautiful Soup 4(以下、BS3,BS4)の情報が混在しています。とくに、”Beautiful Soup”で日本語ページを対象にググると、最初に表示される10件中9件がBS3による情報であるために、初心者はそのままBS3を使って混乱しがちです。ご注意ください。

+

混乱しないように初心者が知っておくべきこと

+
    +
  • 2012年5月にBS3の開発が終了し、現在ではBS4の利用が推奨されています
  • +
  • BS3はPython3に対応していません
  • +
  • ただし、BS3のスクリプトのほとんどはimport文を変えるだけでBS4でも動きます
  • +
  • そのため、BS3による情報も問題解決の役に立ちます
  • +
  • 詳しくは Beautiful Soup 3 を読んでください
  • +
  • この文書の クイックスタートfind_all() を読めば、それなりに用は足りると思います
  • +
+
+
+

この文書について

+

この文書は、Beautiful Soup 4 (訳注:以下BS4)の主要機能について、例を挙げて説明します。どのライブラリがよいか、どのように動くか、どのように使うか、どのようにあなたの望むことを達成するか、予想外の動きをしたときは何をすればよいかといったことを示します。

+

この文書で挙げられる例は、Python2.7と3.2のどちらでも同じように動きます。

+

あなたは Beautiful Soup 3 (訳注:以下BS3)の文書 を探しているのかもしれません。もしそうなら、BS3はすでに開発を終えていて、BS4が全てのプロジェクト対して推奨されていることを知っていてください。BS3とBS4の違いを知りたいときは、BS4への移行 を見てください。

+

この文書は、ユーザーにより他の言語にも翻訳されています。

+
    +
  • 이 문서는 한국어 번역도 가능합니다.(외부 링크)
  • +
+
+
+

助けてほしいときは

+

Beautiful Soup について疑問が生じたり、問題に直面したときは、 ディスカッショングループにメールしてください。 もし問題がHTMLのパースのことであれば、そのHTMLについて diagnose() 関数の返す内容 を必ず書くようにしてください。

+
+
+
+

クイックスタート

+

以下のHTMLドキュメントは、このあと何回も例として用いられます。 ふしぎの国のアリス からの引用です。:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+<body>
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+
+

この”three sisters”ドキュメントを Beautiful Soup にかけると、 Beautiful Soup オブジェクトが得られます。これは入れ子データ構造でドキュメントを表現します。:

+
from bs4 import BeautifulSoup
+soup = BeautifulSoup(html_doc)
+
+print(soup.prettify())
+# <html>
+#  <head>
+#   <title>
+#    The Dormouse's story
+#   </title>
+#  </head>
+#  <body>
+#   <p class="title">
+#    <b>
+#     The Dormouse's story
+#    </b>
+#   </p>
+#   <p class="story">
+#    Once upon a time there were three little sisters; and their names were
+#    <a class="sister" href="http://example.com/elsie" id="link1">
+#     Elsie
+#    </a>
+#    ,
+#    <a class="sister" href="http://example.com/lacie" id="link2">
+#     Lacie
+#    </a>
+#    and
+#    <a class="sister" href="http://example.com/tillie" id="link2">
+#     Tillie
+#    </a>
+#    ; and they lived at the bottom of a well.
+#   </p>
+#   <p class="story">
+#    ...
+#   </p>
+#  </body>
+# </html>
+
+
+

以下は、データ構造を探索するいくつかの方法です。:

+
soup.title
+# <title>The Dormouse's story</title>
+
+soup.title.name
+# u'title'
+
+soup.title.string
+# u'The Dormouse's story'
+
+soup.title.parent.name
+# u'head'
+
+soup.p
+# <p class="title"><b>The Dormouse's story</b></p>
+
+soup.p['class']
+# u'title'
+
+soup.a
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+soup.find_all('a')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.find(id="link3")
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+
+
+

よくある処理として、ページの<a>タグ内にあるURLを全て抽出するというものがあります。:

+
for link in soup.find_all('a'):
+    print(link.get('href'))
+# http://example.com/elsie
+# http://example.com/lacie
+# http://example.com/tillie
+
+
+

また、ページからタグを除去して全テキストを抽出するという処理もあります。:

+
print(soup.get_text())
+# The Dormouse's story
+#
+# The Dormouse's story
+#
+# Once upon a time there were three little sisters; and their names were
+# Elsie,
+# Lacie and
+# Tillie;
+# and they lived at the bottom of a well.
+#
+# ...
+
+
+

必要な情報は得られましたか?つづきをどうぞ。

+
+
+

インストール

+

DebianかUbuntuの最近のバージョンを使っていれば、Beautiful Soupはシステムのパッケージマネージャでインストールできます。:

+

$ apt-get install python-bs4

+

Beautiful Soup 4 は PyPiを通して公開されています。そのため、もしシステムパッケージで Beautiful Soup をインストールできないときは、easy_installpip でインストールできます。

+

$ easy_install beautifulsoup4

+

$ pip install beautifulsoup4

+

( BeautifulSoup パッケージはおそらくあなたが探しているものでは ありません 。これは、一つ前のメジャーリリース Beautiful Soup 3 です。多くのソフトウェアがBS3を使っていて、今でもBS3は利用できます。しかし、新しくコードを書く場合は、 beautifulsoup4 をインストールすべきです。)

+

もし、 easy_installpip をインストールしてないときは、download the Beautiful Soup 4 source tarball でソースをダウンロードし setup.py を用いてインストールできます。

+

$ python setup.py install

+

もしどの方法も失敗するのなら、あなたのアプリケーションにライブラリをそのままパッケージングするという手もあります。Beautiful Soupのライセンスはそれを認めています。.tar.gz形式でダウンロードし、アプリケーションのソースコード内に bs4 ディレクトリをコピーしてください。そうすれば、Beautiful Soupをインストールすることなしに使うことができます。

+

私は、Python 2.7とPython 3.2でBeautiful Soupを開発しましたが、他の最近のバージョンでも動くはずです。

+
+

インストール後の問題

+

Beautiful SoupはPython 2のコードとしてパッケージされています。 +Beautiful SoupをPython 3環境で使おうとしてインストールすると、それは自動的にPython 3のコードとして変換されます。 +もし、Beautiful Soupパッケージをインストールしないと、コードは変換されません。 +Windowsでは、間違ったバージョンが入っていると、それが報告されます。

+

ImportError “No module named HTMLParser” というエラーが表示されたら、それはPython 3環境でPython 2で書かれたコードを実行しようとしたためです。

+

ImportError “No module named html.parser” というエラーが表示されたら、それはPython 2環境でPython 3ので書かれたコードを実行しようとしたためです。

+

どちらの場合もとるべき対応は、Beautiful Soupを(tarballを解凍したときディレクトリを含め) +完全にアンインストールして、再インストールをすることです。

+

ROOT_TAG_NAME = u'[document]' 行で SyntaxError “Invalid syntax” のエラーが表示されたら、 +Python 2で書かれたBeautiful SoupのコードをPython 3に変換しなければいけません。

+

そのためには、次のようにパッケージをインストールするか、:

+

$ python3 setup.py install

+

もしくは、手動で 2to3 変換スクリプトを bs4 ディレクトリで実行すればできます。:

+

$ 2to3-3.2 -w bs4

+
+
+

パーサーのインストール

+

Beautiful SoupはPythonの標準ライブラリに入っているHTMLパーサーをサポートすると同時に、多くのサードパーティーのPythonパーサーもサポートしています。一つには、 lxml parser. があります。環境に依りますが、以下のコマンドのどれかでlxmlをインストールできるでしょう。:

+

$ apt-get install python-lxml

+

$ easy_install lxml

+

$ pip install lxml

+

別の選択肢として、Python純正の html5lib parser が挙げられます。これは HTMLをwebブラウザがするようにパースします。これも環境に依りますが、以下のコマンドのどれかでhtml5libをインストールできるでしょう。:

+

$ apt-get install python-html5lib

+

$ easy_install html5lib

+

$ pip install html5lib

+

以下の表は、各パーサーのライブラリの強みと弱みをまとめてあります。

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
パーサー使用例強み弱み
Python’s html.parserBeautifulSoup(markup, "html.parser")
    +
  • 標準ライブラリ
  • +
  • まずまずのスピード
  • +
  • Python2.7.3/3.2.2以降に対応
  • +
+
    +
  • Python2.7.3/3.2.2未満は非対応
  • +
+
lxml’s HTML parserBeautifulSoup(markup, "lxml")
    +
  • 爆速
  • +
  • 対応(?)
  • +
+
    +
  • 外部Cライブラリに依存
  • +
+
lxml’s XML parserBeautifulSoup(markup, ["lxml", "xml"]) +BeautifulSoup(markup, "xml")
    +
  • 爆速
  • +
  • 唯一の対応XMLパーサー
  • +
+
    +
  • 外部Cライブラリに依存
  • +
+
html5libBeautifulSoup(markup, "html5lib")
    +
  • 対応度高
  • +
  • WEBブラウザと同じようにパース
  • +
  • 正しいHTML5を生成
  • +
+
    +
  • とても遅い
  • +
  • 外部Pythonライブラリに依存
  • +
+
+

できれば、速度のためにlxmlをインストールして使うことをお薦めします。 +とくに、あなたがPython2.7.3のPython2系か、Python3.2.2より前のPython3系を使っているばあいは、lxmlかhtml5libをインストールすることは とても大事です 。 +なぜなら、Pythonにはじめから組み込まれているHTMLパーサーは、古いバージョンのPythonではそこまで良く動かないからです。

+

構文が不正確なドキュメントのときは、パーサーが違うと生成されるパースツリーが異なってくることに注意してください。 +詳しくは、 パーサーの違い を参照のこと。

+
+
+
+

スープの作成

+

ドキュメントをパース(構文解析)するには、 +そのドキュメントを Beautiful Soup コンストラクタに渡します。 +文字列でも開いたファイルハンドルでも渡せます。:

+
from bs4 import BeautifulSoup
+
+soup = BeautifulSoup(open("index.html"))
+
+soup = BeautifulSoup("<html>data</html>")
+
+
+

最初に、ドキュメントはUnicodeに変換され、HTMLエンティティはUnicode文字列に変換されます。:

+
BeautifulSoup("Sacr&eacute; bleu!")
+<html><head></head><body>Sacré bleu!</body></html>
+
+

Beautiful Soupは、ドキュメントをもっとも適したパーサー(構文解析器)を使ってパースします。 +XMLパーサーを使うように指定しなければ、HTMLパーサーが用いられます。( XMLのパース を参照)

+
+
+

4種類のオブジェクト

+

Beautiful Soup は複雑なHTMLドキュメントを、Pythonオブジェクトの複雑なツリー構造に変換します。 +しかし、あなたは Tag, NavigableString, BeautifulSoup, Comment +の 4種類のオブジェクト だけを扱えばよいです。

+
+

Tag obj.

+

Tag オブジェクトは、元のドキュメント内のXMLやHTMLのタグに対応しています。:

+
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
+tag = soup.b
+type(tag)
+# <class 'bs4.element.Tag'>
+
+
+

Tag オブジェクトは、多くの属性とメソッドを持っています。それらのほとんどは、 パースツリーを探索パースツリーを検索 で説明します。この節では Tag オブジェクトの重要な機能である、名前と属性について説明します。

+
+

名前

+

タグはそれぞれ名前を持っていますが、 .name でアクセスできます。:

+
tag.name
+# u'b'
+
+
+

タグの名前を変えると、その変更はBeautiful Soupが生成する全てのマークアップに反映されます。:

+
tag.name = "blockquote"
+tag
+# <blockquote class="boldest">Extremely bold</blockquote>
+
+
+
+
+

属性

+

タグは多くの属性を持ちます。 +<b class=”boldest”>は、”boldest”という値の’class’属性を持ちます。 +Tag オブジェクトを辞書のように扱うことで、そのタグの属性にアクセスできます。:

+
tag['class']
+# u'boldest'
+
+
+

.attrs で辞書に直接アクセスできます。:

+
tag.attrs
+# {u'class': u'boldest'}
+
+
+

繰り返しになりますが、辞書のように Tag オブジェクトを扱うことにより、タグの属性に対して追加, 削除, 修正も行うことができます。:

+
tag['class'] = 'verybold'
+tag['id'] = 1
+tag
+# <blockquote class="verybold" id="1">Extremely bold</blockquote>
+
+del tag['class']
+del tag['id']
+tag
+# <blockquote>Extremely bold</blockquote>
+
+tag['class']
+# KeyError: 'class'
+print(tag.get('class'))
+# None
+
+
+
+

値が複数のとき

+

HTML4は、値を複数もてる2,3の属性を定義しています。 +HTML5で、それらはなくなりましたが、別の同様の属性が定義されました。 +もっとも一般的な値を複数もつ属性は class です。(たとえば、HTMLタグは複数のCSSクラスを持つことができます) +また他の複数の値を持つ属性としては、 rel, rev, accept-charset, headers, accesskey があります。 +Beautiful Soupは、これらの属性がもつ複数の値をリストとして示します。:

+
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
+css_soup.p['class']
+# ["body", "strikeout"]
+
+css_soup = BeautifulSoup('<p class="body"></p>')
+css_soup.p['class']
+# ["body"]
+
+
+

ある属性が複数の値をもっているようでも、HTML標準の定義から外れている場合、Beautiful Soupはその属性をひとまとまりの値として扱います。:

+
id_soup = BeautifulSoup('<p id="my id"></p>')
+id_soup.p['id']
+# 'my id'
+
+
+

タグを文字列に変換したときは、これらの属性の複数の値は一つにまとめられます。:

+
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
+rel_soup.a['rel']
+# ['index']
+rel_soup.a['rel'] = ['index', 'contents']
+print(rel_soup.p)
+# <p>Back to the <a rel="index contents">homepage</a></p>
+
+
+

ドキュメントをXMLとしてパースすると、値を複数もつ属性はなくなります。:

+
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
+xml_soup.p['class']
+# u'body strikeout'
+
+
+
+
+
+ +
+

BeautifulSoup obj.

+

Beautiful Soup オブジェクトは、それ自身で元のドキュメント全体を表しています。 +たいていの場合、Tag obj. を扱うことで、用は足りるでしょう。 +これは、Tag obj.パースツリーを探索パースツリーを検索. で述べられているメソッドの多くをサポートしているということです。

+

BeautifulSoup オブジェクトは、実際のHTMLやXMLタグに対応していないので、名前や属性を持たない。 +しかし、 .name をみるような便利なものはいくつかある。そして、それらは特別な .name “[document]”を得られる(?訳がおかしい。けど次回まわし?):

+
soup.name
+# u'[document]'
+
+
+
+
+

Comments obj. 他

+

Tag, NavigableString, BeautifulSoup はHTMLやXMLファイルのほぼ全てをカバーします。しかし、少しだけ残ったものがあります。それはコメントについてです。:

+
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
+soup = BeautifulSoup(markup)
+comment = soup.b.string
+type(comment)
+# <class 'bs4.element.Comment'>
+
+
+

Comment オブジェクトは、 NavigableString オブジェクトの特別なタイプです。:

+
comment
+# u'Hey, buddy. Want to buy a used parser'
+
+
+

コメントはHTMLの中にあらわれますが、 Comment は特別な書式で表示されます。:

+
print(soup.b.prettify())
+# <b>
+#  <!--Hey, buddy. Want to buy a used parser?-->
+# </b>
+
+
+

Beautiful Soupは、XMLドキュメントのなかの他の全ての要素をクラス定義しています。 +CData, ProcessingInstruction, Declaration, Doctype. +Comment クラスのように、これらは文字に何かを加えた NavigableString のサブクラスです。 +ここでは、コメントをCDDATAブロックに置換した例を示します。:

+
from bs4 import CData
+cdata = CData("A CDATA block")
+comment.replace_with(cdata)
+
+print(soup.b.prettify())
+# <b>
+#  <![CDATA[A CDATA block]]>
+# </b>
+
+
+
+
+
+

パースツリーを探索

+

ここで再び “Three sisters” のHTMLドキュメントです。:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+from bs4 import BeautifulSoup
+soup = BeautifulSoup(html_doc)
+
+
+

ドキュメントのある部分から他の部分へどのように移動するかを示すために、このドキュメントを例に使っていきます。

+
+

子要素へ下移動

+

タグはその間に(ドキュメント本文のテキスト)文字列や他のタグを挟んでいます。これらの要素は、 タグの 子要素 です。Beautiful Soupは、タグの子要素を探索し扱うための多くの属性を提供します。

+

Beautiful Soupの文字列は、これらの属性をサポートしません。なぜなら、文字列は子要素をもたないからです。

+
+

タグ名で探索

+

パースツリーを探索する一番簡単な方法は、あなたが取得したいタグの名前を使うことです。 +もし、<head> タグを取得したければ、 soup.head と入力すればよいです。:

+
soup.head
+# <head><title>The Dormouse's story</title></head>
+
+soup.title
+# <title>The Dormouse's story</title>
+
+
+

また、パースツリーのある部分から出発して、何度もズームインを繰り返す方法もあります。 +このコードは、<body>タグ以下の最初の<b>タグを取得します。:

+
soup.body.b
+# <b>The Dormouse's story</b>
+
+
+

属性としてタグ名を使うと、その名前のタグのうち 最初 にあるものを取得できます。:

+
soup.a
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+
+

全ての <a>タグを取得したいときや、ある名前のタグのうち2番目以降のものをしたいときは、 パースツリーを検索 で述べられている find_all() のようなメソッドを使う必要があります。:

+
soup.find_all('a')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+
+
+

.contents / .children

+

タグの子要素は、 .contents で呼び出すと、リストで取得できます。:

+
head_tag = soup.head
+head_tag
+# <head><title>The Dormouse's story</title></head>
+
+head_tag.contents
+[<title>The Dormouse's story</title>]
+
+title_tag = head_tag.contents[0]
+title_tag
+# <title>The Dormouse's story</title>
+title_tag.contents
+# [u'The Dormouse's story']
+
+

Beautiful Soup オブジェクトは、それ自身が子要素を持ちます。この場合、<html>タグが Beautiful Soup オブジェクトの子要素になります。:

+
len(soup.contents)
+# 1
+soup.contents[0].name
+# u'html'
+
+
+

文字列は .contents を持ちません。なぜなら、文字列は何も挟まないからです。:

+
text = title_tag.contents[0]
+text.contents
+# AttributeError: 'NavigableString' object has no attribute 'contents'
+
+
+

タグの子要素を、リストの代わりに、 .children ジェネレーターを用いてイテレーターで扱うこともできます。:

+
for child in title_tag.children:
+    print(child)
+# The Dormouse's story
+
+
+
+
+

.descendants

+

.contents.children 属性は、あるタグの 直下の 子要素のみを表します。 +例えば、<head>タグは、ただ一つの直下の子要素である<title>タグを持ちます。:

+
head_tag.contents
+# [<title>The Dormouse's story</title>]
+
+
+

しかし、この<title>タグ自身も、子要素に”The Dormouse’s story”文字列を持ちます。 +この文字列もまた、<head>タグの子要素であるという意味になります。 +そこで、 .descendants (子孫) 属性を用いると、 あるタグの 全ての 子要素を再帰的に取り出すことができます。 +再帰的というのは、直下の子要素、そのまた子要素、そしてさらにといったふうに繰り返してということです。

+
for child in head_tag.descendants:
+    print(child)
+# <title>The Dormouse's story</title>
+# The Dormouse's story
+
+
+

このドキュメントの<head>タグはただ1つの子要素しか持ちませんが、 +<title>タグと<title>タグの子要素という2つの子孫要素を持ちます。 +また、このドキュメントの BeautifulSoup オブジェクトには、 +直下の子要素は<html>タグ1つしかありませんが、子孫要素はたくさんあります。:

+
len(list(soup.children))
+# 1
+len(list(soup.descendants))
+# 25
+
+
+
+
+

.string

+

ある Tag オブジェクトが1つだけ子要素をもっていて、その子要素が NavigableString オブジェクトならば、 .string 属性で利用できます。:

+
title_tag.string
+# u'The Dormouse's story'
+
+
+

ある Tag オブジェクトのただ1つの子要素が、別の Tag オブジェクトであって .string 属性を持つならば、元の Tag オブジェクトも同じ .string 属性を持つと考えられます。:

+
head_tag.contents
+# [<title>The Dormouse's story</title>]
+
+head_tag.string
+# u'The Dormouse's story'
+
+
+

ある tag オブジェクトが複数の子要素を持ち、 .string 属性がどの子要素を参照しているかわからないとき、 .string 属性は None と定義されます。:

+
print(soup.html.string)
+# None
+
+
+
+
+

.strings / .stripped_strings

+

あるタグの中にあるドキュメント本文が要素が複数であっても、それらの文字列をみることができます。 +その場合は、 .strings ジェネレーターを使用します。:

+
for string in soup.strings:
+    print(repr(string))
+# u"The Dormouse's story"
+# u'\n\n'
+# u"The Dormouse's story"
+# u'\n\n'
+# u'Once upon a time there were three little sisters; and their names were\n'
+# u'Elsie'
+# u',\n'
+# u'Lacie'
+# u' and\n'
+# u'Tillie'
+# u';\nand they lived at the bottom of a well.'
+# u'\n\n'
+# u'...'
+# u'\n'
+
+
+

これらの文字列は、大量の余計な空白が入りがちである。 +そこで、 .stripped_strings ジェネレーターを代わりに用いることで、それら空白を除くことができる。:

+
for string in soup.stripped_strings:
+    print(repr(string))
+# u"The Dormouse's story"
+# u"The Dormouse's story"
+# u'Once upon a time there were three little sisters; and their names were'
+# u'Elsie'
+# u','
+# u'Lacie'
+# u'and'
+# u'Tillie'
+# u';\nand they lived at the bottom of a well.'
+# u'...'
+
+
+

ここでは、文字列中に入る空白はそのままで、文字列の最初や最後に付く空白は削除されます。

+
+
+
+

親要素へ上移動

+

“家族ツリー”に例えると、全てのタグや文字列はそれぞれが一つの親要素を持ちます。

+
+

.parent

+

.parent 属性で親要素にアクセスできます。 +たとえば、”three sisters”ドキュメントでは、<head>タグは<title>タグの親要素です。:

+
title_tag = soup.title
+title_tag
+# <title>The Dormouse's story</title>
+title_tag.parent
+# <head><title>The Dormouse's story</title></head>
+
+
+

タイトル文字列はそれ自身が親要素を持ち、<title>タグはタイトル文字列を子要素に持ちます。:

+
title_tag.string.parent
+# <title>The Dormouse's story</title>
+
+
+

<html>タグの様なトップレベルのタグは、 BeautifulSoup オブジェクトそれ自身になります。:

+
html_tag = soup.html
+type(html_tag.parent)
+# <class 'bs4.BeautifulSoup'>
+
+
+

そして、BeautifulSoup オブジェクトの .parent 属性は、Noneになります。:

+
print(soup.parent)
+# None
+
+
+
+
+

.parents

+

あるタグに対する祖先要素全てを .parents で取得することができます。 +以下は、HTMLドキュメントの深いところにある<a>タグからスタートして、最上層まで辿っています。:

+
link = soup.a
+link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+for parent in link.parents:
+    if parent is None:
+        print(parent)
+    else:
+        print(parent.name)
+# p
+# body
+# html
+# [document]
+# None
+
+
+
+
+
+

兄弟要素へ横移動

+

以下のようなシンプルなHTMLドキュメントを考えてみましょう。:

+
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
+print(sibling_soup.prettify())
+# <html>
+#  <body>
+#   <a>
+#    <b>
+#     text1
+#    </b>
+#    <c>
+#     text2
+#    </c>
+#   </a>
+#  </body>
+# </html>
+
+
+

<b>タグは<c>タグと同じレベルにあります。つまり、2つはともに同じタグの直下の子要素ということです。 +こういった関係にあるタグを siblings (兄弟)といいます。 +HTMLドキュメントをきれいに出力(?)したとき、siblingsは同じインデントレベルになります。 +こういったタグの関係をコードで利用することができます。

+
+

.next_sibling / .previous_sibling

+

.next_sibling.previous_sibling を用いて、パースツリーの同じレベルの要素間を辿ることができます。:

+
sibling_soup.b.next_sibling
+# <c>text2</c>
+
+sibling_soup.c.previous_sibling
+# <b>text1</b>
+
+
+

この<b>タグは .next_sibling は持ちますが、 .previous_sibling は持ちません。 +なぜなら、<b>タグの前にはパースツリーで同レベルの要素がないからです。 +同様に、<c>タグは .previous_sibling を持ちますが、.next_sibling は持ちません。:

+
print(sibling_soup.b.previous_sibling)
+# None
+print(sibling_soup.c.next_sibling)
+# None
+
+
+

“text1”と”text”は兄弟ではありません。なぜなら、2つは同じ親をもたないからです。:

+
sibling_soup.b.string
+# u'text1'
+
+print(sibling_soup.b.string.next_sibling)
+# None
+
+
+

実際のHTMLドキュメントをパースすると、 .next_sibling.previous_sibling は前後に空白を持ちます。 +“three sisters”ドキュメントで見てみましょう。:

+
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
+
+

すなおに考えれば、最初の<a>タグの .next_sibling は2番目の<a>タグとなるはずですが、実際は違います。 +それは、最初の<a>タグと2番目を分ける”コンマと改行コード”という文字列になります。:

+
link = soup.a
+link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+link.next_sibling
+# u',\n'
+
+
+

2番目の<a>タグは、そのコンマと改行コードの .next_sibling になります。:

+
link.next_sibling.next_sibling
+# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
+
+
+
+
+

.next_siblings / .previous_siblings

+

複数の兄弟要素を .next_siblings.previous_siblings をイテレーターとして使って、まとめて扱えます。:

+
for sibling in soup.a.next_siblings:
+    print(repr(sibling))
+# u',\n'
+# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
+# u' and\n'
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+# u'; and they lived at the bottom of a well.'
+# None
+
+for sibling in soup.find(id="link3").previous_siblings:
+    print(repr(sibling))
+# ' and\n'
+# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
+# u',\n'
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+# u'Once upon a time there were three little sisters; and their names were\n'
+# None
+
+
+
+
+
+

前後の要素へ移動

+

“three sisters”ドキュメントのはじめの部分を見てみましょう。:

+
<html><head><title>The Dormouse's story</title></head>
+<p class="title"><b>The Dormouse's story</b></p>
+
+

HTMLパーサーは、この文字列を読み込み、イベントの連なりとして理解します。”open an <html> tag”, “open a <head> tag”, “open a <title> tag”, “add a string”, “close the <title> tag”, “open a <p>”... といったかんじです。Beautiful Soupはこのイベントの連なりを、さらに再構成して扱います。

+
+

.next_element / .previous_element

+

文字列やHTMLタグの .next_element 属性は、それの直後の要素を指し示します。 +.next_string と同じようですが、決定的に違います。

+

“three sisters”ドキュメントの最後の<a>タグについて考えてみましょう。 +それの .next_string はその<a>タグによって分割された文の後ろの部分の文字列です。(?):

+
last_a_tag = soup.find("a", id="link3")
+last_a_tag
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+
+last_a_tag.next_sibling
+# '; and they lived at the bottom of a well.'
+
+
+

一方、 .next_element は、<a>タグのすぐ後ろの要素である”Tillie”という単語を指し示します。文の残りの部分ではありません。:

+
last_a_tag.next_element
+# u'Tillie'
+
+
+

これは元の文章で”Tillie”という単語がセミコロンの前に現れるからです。 +パーサーは<a>タグに出会い、次に”Tillie”という単語、そして</a>という閉じるタグがきます。 +そのあとは、セミコロンがあって、文の残りの部分です。 +セミコロンは<a>タグと同じレベルにありますが、”Tillie”という単語が最初に出会います。

+

.previous_element 属性は、 .next_element とは逆です。 +その要素の一つ前の要素を指し示します。:

+
last_a_tag.previous_element
+# u' and\n'
+last_a_tag.previous_element.next_element
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+
+
+
+
+

.next_elements / .previous_elements

+

パースされたドキュメントの要素を、前後方向に取得していくイテレーターを使うこともできます。:

+
for element in last_a_tag.next_elements:
+    print(repr(element))
+# u'Tillie'
+# u';\nand they lived at the bottom of a well.'
+# u'\n\n'
+# <p class="story">...</p>
+# u'...'
+# u'\n'
+# None
+
+
+
+
+
+
+

パースツリーを検索

+

Beautiful Soupはパースパースツリーを検索する多くのメソッドを定義しています。 +しかし、それらはどれもとても似通っています。 +この章では、find()find_all() という2つの人気のメソッドの説明に、多くのスペースを費やします。 +それ以外のメソッドは、ほとんど同じ引数を持つので、簡単な説明にとどめることにします。

+

ここでは再び、”three sisters”ドキュメントを例に使っていきます。:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+from bs4 import BeautifulSoup
+soup = BeautifulSoup(html_doc)
+
+
+

find_all() のようなフィルターを通すことにより、 +興味のあるドキュメントのある一部分にズームすることができます。

+
+

フィルターの種類

+

find_all() 等のメソッドの詳細を説明するまえに、これらのメソッドに渡すフィルターの例を示します。 +検索APIの使い方をマスターする上で、フィルターは何度もでてきます。 +これにより、タグ名, タグの属性, ドキュメントの文字列やそれを組み合わせた条件を指定して、フィルターをかけます

+
+

文字列

+

もっともシンプルなフィルターは文字列です。 +検索メソッドに文字列を渡すと、Beautiful Soupは厳格に文字列を一致させます。 +以下のコードは、ドキュメント内の<b>タグを全て見つけます。:

+
soup.find_all('b')
+# [<b>The Dormouse's story</b>]
+
+
+

バイト文字列を渡すと、Beautiful SoupはそれをUTF-8にエンコードされた文字列として扱います。 +これを避けるには、代わりにUnicode文字列を渡します。

+
+
+

正規表現

+

正規表現オブジェクトを渡すと、Beautiful Soupはそれの match() メソッドを用いて、その正規表現に一致するものをマッチさせます。 +以下のコードは、全ての”b”ではじまるつづりの名前のタグを見つけます。 +“three sisters”ドキュメントでは、<body>タグと<b>タグにマッチします。:

+
import re
+for tag in soup.find_all(re.compile("^b")):
+    print(tag.name)
+# body
+# b
+
+
+

以下のコードでは、タグ名に”t”のつづりを含むもの全てを見つけます。:

+
for tag in soup.find_all(re.compile("t")):
+    print(tag.name)
+# html
+# title
+
+
+
+
+

リスト

+

フィルターにリストで引数をわたすと、Beautiful Soupはそのリストの内のいずれかにマッチした要素を返します。 +以下のコードは、全ての<a>タグと<b>タグを見つけます。:

+
soup.find_all(["a", "b"])
+# [<b>The Dormouse's story</b>,
+#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+
+
+

True値

+

True値 は全ての要素にマッチします。 +以下のコードは、ドキュメント内の 全て のタグをみつけます。 +ただし、ドキュメント本文のテキスト文字列はマッチされません。:

+
for tag in soup.find_all(True):
+    print(tag.name)
+# html
+# head
+# title
+# body
+# p
+# b
+# p
+# a
+# a
+# a
+# p
+
+
+
+
+

関数

+

以上のフィルターで機能が足りないときは、自分で引数に要素をとる関数を定義することもできます。 +その関数は、引数がマッチしたときは True を、そうでないときは False を返します。

+

以下の関数では、HTMLタグが “class” 属性を持ち、”id”属性を持たない場合に True を返します。:

+
def has_class_but_no_id(tag):
+    return tag.has_attr('class') and not tag.has_attr('id')
+
+
+

この関数を find_all() に渡すと、”three sisters”ドキュメントから全ての<p>タグを取得できます。:

+
soup.find_all(has_class_but_no_id)
+# [<p class="title"><b>The Dormouse's story</b></p>,
+#  <p class="story">Once upon a time there were...</p>,
+#  <p class="story">...</p>]
+
+
+

この関数は<p>タグだけを抽出します。 +<a>タグは”class”と”id”の両方の属性を定義しているので抽出できません。 +<html>や<title>のようなタグは、”class”を定義してないので、同様に抽出できません。

+

以下の関数は、HTMLタグがstringオブジェクトに囲まれているときは、 True を返します。(?):

+
from bs4 import NavigableString
+def surrounded_by_strings(tag):
+    return (isinstance(tag.next_element, NavigableString)
+            and isinstance(tag.previous_element, NavigableString))
+
+for tag in soup.find_all(surrounded_by_strings):
+    print tag.name
+# p
+# a
+# a
+# a
+# p
+
+
+

これで検索メソッドの詳細をみていくことの準備ができました。

+
+
+
+

find_all()

+

使い方: find_all(name, attrs, recursive, text, limit, **kwargs)

+

find_all() メソッドは、Tag オブジェクトが持つ子孫要素のうち、引数に一致する 全ての 要素を見つけます。 +フィルターの種類 でいくつかの例を挙げましたが、ここでもう少し説明します。:

+
soup.find_all("title")
+# [<title>The Dormouse's story</title>]
+
+soup.find_all("p", "title")
+# [<p class="title"><b>The Dormouse's story</b></p>]
+
+soup.find_all("a")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.find_all(id="link2")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+import re
+soup.find(text=re.compile("sisters"))
+# u'Once upon a time there were three little sisters; and their names were\n'
+
+
+

これらの使い方は、すでに説明してるものもあれば、初出のものもあります。 +textid に値を渡すのはどういう意味でしょうか? +なぜ、find_all("p", "title") は、CSSの”title”タグをもつ<p>タグを発見したのでしょうか? +find_all() の引数をみていきましょう。

+
+

name引数

+

find_all()name 引数に値を渡すと、タグの名前だけを対象に検索が行われます。 +名前がマッチしないタグと同じように、テキスト文字列は無視されます。

+

以下の例は、もっともシンプルな使い方です。:

+
soup.find_all("title")
+# [<title>The Dormouse's story</title>]
+
+
+

フィルターの種類 で述べたように、 name 引数は文字列, 正規表現, リスト, 関数, True値をとることができます。

+
+
+

キーワード引数

+

どのような理解できない引数でも、タグの属性の一つとして解釈されます。 +キーワード引数 id に値を渡すと、Beautiful Soupはタグの’id’属性に対してフィルタリングを行います。:

+
soup.find_all(id='link2')
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+

キーワード引数 href に値を渡すと、Beautiful SoupはHTMLタグの’href’属性に対してフィルタリングを行います。:

+
soup.find_all(href=re.compile("elsie"))
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+
+

キーワード引数の値もまた、 文字列, 正規表現, リスト, 関数, True値 をとることができます。

+

次のコードは、id 属性に値が入っている全てのタグを見つけます。このとき、値は何でもあっても構いません。:

+
soup.find_all(id=True)
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

複数のキーワード引数を一度に渡すことによって、複数の属性についてフィルタリングできます。:

+
soup.find_all(href=re.compile("elsie"), id='link1')
+# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
+
+
+

HTML5の ‘data-*’ 属性など、いくつかの属性についてはキーワード引数として用いることができません。:

+
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
+data_soup.find_all(data-foo="value")
+# SyntaxError: keyword can't be an expression
+
+
+

しかし、これらの属性を辞書にして、キーワード引数 attrs として値を渡せばフィルタリングすることができます。:

+
data_soup.find_all(attrs={"data-foo": "value"})
+# [<div data-foo="value">foo!</div>]
+
+
+
+
+

CSSのクラスで検索

+

HTMLタグが持つCSSのクラスで検索をかけるのはとても便利です。 +しかし”class”はPythonの予約語のため、class をキーワード引数として用いると文法エラーになります。 +そこで、Beautiful Soup 4.1.2からは、 class_ というキーワード引数でCSSのクラスを検索できるようになりました。:

+
soup.find_all("a", class_="sister")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

他のキーワード引数と同様、 class_ には文字列, 正規表現, 関数, True値を渡せます。:

+
soup.find_all(class_=re.compile("itl"))
+# [<p class="title"><b>The Dormouse's story</b></p>]
+
+def has_six_characters(css_class):
+    return css_class is not None and len(css_class) == 6
+
+soup.find_all(class_=has_six_characters)
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

Tag オブジェクトの属性の 値が複数のとき を思い出してください。 +それと同様に、あるCSSクラスを検索するときは、複数のCSSクラスに対してマッチさせられます。:

+
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
+css_soup.find_all("p", class_="strikeout")
+# [<p class="body strikeout"></p>]
+
+css_soup.find_all("p", class_="body")
+# [<p class="body strikeout"></p>]
+
+
+

class 属性の値は、文字列としても検索できます。:

+
css_soup.find_all("p", class_="body strikeout")
+# [<p class="body strikeout"></p>]
+
+
+

しかし、文字列の値としての変数を検索することはできません。:

+
css_soup.find_all("p", class_="strikeout body")
+# []
+
+
+

もしあなたが2つ以上のクラスをまっちさせたいなら、CSSセレクトを使ってください。:

+
css_soup.select("p.strikeout.body")
+# [<p class="body strikeout"></p>]
+
+
+

Beautiful Soupの古いバージョンでは、 class_ 引数は使えません。 +そこで、以下に述べる attrs トリックを使うことができます。 +これは”class”をkeyに持つ辞書を attrs 引数に渡して、検索することができます。 +この辞書のvalueには、文字列, 正規表現などが使えます。:

+
soup.find_all("a", attrs={"class": "sister"})
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+
+
+

text引数

+

text 引数で、タグに挟まれている文字列を対象に検索することができます。 +name 引数やキーワード引数のように、 文字列 , 正規表現 , リスト , 関数 , True値 が使えます。 +以下の例をごらんください。:

+
soup.find_all(text="Elsie")
+# [u'Elsie']
+
+soup.find_all(text=["Tillie", "Elsie", "Lacie"])
+# [u'Elsie', u'Lacie', u'Tillie']
+
+soup.find_all(text=re.compile("Dormouse"))
+[u"The Dormouse's story", u"The Dormouse's story"]
+
+def is_the_only_string_within_a_tag(s):
+    """Return True if this string is the only child of its parent tag."""
+    return (s == s.parent.string)
+
+soup.find_all(text=is_the_only_string_within_a_tag)
+# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
+
+
+

text 引数はテキスト文字列の検索ですが、これにタグの検索を組みわせることもできます。 +Beautiful Soupは、text 引数で指定した文字列を .string にもつタグ全てを見つけます。 +次のコードは、.string に “Elsie”を持つ<a>タグを見つけます。:

+
soup.find_all("a", text="Elsie")
+# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
+
+
+
+
+

limit引数

+

find_all() メソッドは、指定したフィルターにマッチした全てのタグと文字列を返します。 +これはドキュメントが大きいときは時間がかかります。 +もし、 全ての 結果を必要としなければ、limit 引数で取得する数を指定することができます。

+

“three siters”ドキュメントには3つのリンクがある、しかし以下のコードははじめの2つしか見つけない。:

+
soup.find_all("a", limit=2)
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+
+
+

recursive引数

+

mytag.find_all() を実行すると、Beautiful Soupは、 mytag の全ての子孫要素を調べます。 +(子要素、子要素の子要素、そのまた子要素というかんじで、、) +もし、直下の子要素しか調べたくなければ、recursive=False という引数を渡せばよいです。 +以下で違いをみてみましょう。:

+
soup.html.find_all("title")
+# [<title>The Dormouse's story</title>]
+
+soup.html.find_all("title", recursive=False)
+# []
+
+
+

これはドキュメントの一部です。:

+
<html>
+ <head>
+  <title>
+   The Dormouse's story
+  </title>
+ </head>
+...
+
+

このドキュメントにおいて、<title>タグは<html>の下にはあるが、直下 にあるわけではありません。 +Beautiful Soupが<title>タグを見つけることができるのは、<html>タグ以下の全ての子孫要素を探してよいときだけです。 +もし、find_all() の引数に recurive=False という<html>タグの直下のみを検索するという制限がかかっていたら、<title>タグを見つけることはできません。

+

Beautiful Soupは、多くのパースツリーを検索するメソッドを提供しています。 +それら多くは共通する引数を持ちます。 +find_all()name, attrs, text, limit, キーワード引数は、他の多くのメソッドにも対応しています。 +しかし、 recursive 引数は、 find_all(), find() の2つのメソッドしか対応していません。 +find_parents() のようなメソッドに、引数 recursive=False を渡しても意味がありません。

+
+
+

ショートカット

+

find_all() はBeautiful Soupの検索APIの中で、一番使われるものなので、ショートカットがあります。 +Beautiful Soup オブジェクトや Tag オブジェクトを関数のように扱って、 find_all() メソッドを呼び出すことができます。 +以下の2行は等価です。:

+
soup.find_all("a")
+soup("a")
+
+
+

以下の2行もまた等価です。:

+
soup.title.find_all(text=True)
+soup.title(text=True)
+
+
+
+
+
+

find()

+

使い方: find(name, attrs, recursive, text, **kwargs)

+

find_all() メソッドは、検索結果を得るためにHTMLドキュメント全部をスキャンします。 +しかし、1つだけの検索結果が必要なときがあります。 +もし、HTMLドキュメントに<body>タグが1つだけなら、HTMLドキュメント全体をスキャンするのは時間の無駄です。 +その場合は find_all() メソッドに limit=1 という引数を渡さずに、 find() メソッドを使うことができます。 +以下の2行は、ほぼ等価です。:

+
soup.find_all('title', limit=1)
+# [<title>The Dormouse's story</title>]
+
+soup.find('title')
+# <title>The Dormouse's story</title>
+
+
+

ただ1つ違う点は、find_all() は要素1のリストを返し、find() は要素をそのまま返すことです。

+

find_all() が何もみつけられないときは空リストを返します。 +find() が何もみつけられないときは、 None を返します。:

+
print(soup.find("nosuchtag"))
+# None
+
+
+

タグ名で探索 で出てきた soup.head.title で探索する方法を覚えていますか? おkれは、find() についても適用できます。:

+
soup.head.title
+# <title>The Dormouse's story</title>
+
+soup.find("head").find("title")
+# <title>The Dormouse's story</title>
+
+
+
+
+

find_parents() / find_parent()

+

使い方: find_parents(name, attrs, text, limit, **kwargs)

+

使い方: find_parent(name, attrs, text, **kwargs)

+

ここまで find_all()find() について述べてきました。 +Beautiful Soup APIにはパースツリーを検索するためのメソッドが、あと10あります。 +しかし、おそれる必要はありません。 +そのうち5つは、find_all() と基本的に同じです。 +そして、のこりの5つは find() と基本的に同じです。 +違いは、ツリーのどの部分を検索対象にするのかという点のみです。

+

最初に、 find_parents()find_parent() を見てみましょう。 +find_all()find() がタグの子孫を見て、ツリーを下りていったことを思い出してください。 +find_parents()find_parent() は逆です。 +これらはタグや文字列の親をみて、ツリーを’上に’検索していきます。 +以下の”three daughters”ドキュメントの例で、深いレベルにある文字列から検索していく様子を見てください。:

+
a_string = soup.find(text="Lacie")
+a_string
+# u'Lacie'
+
+a_string.find_parents("a")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+a_string.find_parent("p")
+# <p class="story">Once upon a time there were three little sisters; and their names were
+#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
+#  and they lived at the bottom of a well.</p>
+
+a_string.find_parents("p", class="title")
+# []
+
+

3つの<a>タグのうちの1つは、検索の起点になる文字列の直接の親要素なので、それが返されました。 +3つの<p>タグのうちの1つは、起点の文字列の直接の親ではありませんが、やはりそれも返されました。 +CSSクラス”title”をもつ<p>タグは、”three daughers”ドキュメント中にはあるのですが、起点の文字列の親要素ではないので、 find_parents() では見つけることができませんでした。

+

find_parent()find_parents() のつながりはわかったでしょうか。 +.parent.parents 属性については、以前に述べてあります。 +そのつながりはとても強いです。 +これらの検索メソッドは実際には .parents で、全ての親要素の連なりをイテレートして扱います。 +そして、要素それぞれについてフィルターにマッチするかどうかをチェックします。

+
+
+

find_next_siblings() / find_next_sibling()

+

使い方: find_next_siblings(name, attrs, text, limit, **kwargs)

+

使い方: find_next_sibling(name, attrs, text, **kwargs)

+

これらのメソッドは、後方にある兄弟要素を扱うのに、 .next_siblings を使います。 +find_next_siblings() メソッドはマッチする兄弟要素を全て返し、 find_next_sibling() は最初の一つを返します。:

+
first_link = soup.a
+first_link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+first_link.find_next_siblings("a")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+first_story_paragraph = soup.find("p", "story")
+first_story_paragraph.find_next_sibling("p")
+# <p class="story">...</p>
+
+
+
+
+

find_previous_siblings() / find_previous_sibling()

+

使い方: find_previous_siblings(name, attrs, text, limit, **kwargs)

+

使い方: find_previous_sibling(name, attrs, text, **kwargs)

+

これらのメソッドは、HTMLドキュメントの前方にあった兄弟要素を扱うのに .previous_siblings を使います。 +find_previous_siblings() メソッドはマッチする兄弟要素を全て返し、 find_previous_sibling() は最初の一つを返します。:

+
last_link = soup.find("a", id="link3")
+last_link
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+
+last_link.find_previous_siblings("a")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+first_story_paragraph = soup.find("p", "story")
+first_story_paragraph.find_previous_sibling("p")
+# <p class="title"><b>The Dormouse's story</b></p>
+
+
+
+
+

find_all_next() / find_next()

+

使い方: find_all_next(name, attrs, text, limit, **kwargs)

+

使い方: find_next(name, attrs, text, **kwargs)

+

これらのメソッドは、HTMLドキュメントのその後にあらわれるタグと文字列の要素全てイテレートして扱うために、 .next_elements メソッドを使います。 +find_all_next() メソッドはマッチするもの全てを返し、 find_next() は最初にマッチしたものを返します。(!要改善):

+
first_link = soup.a
+first_link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+first_link.find_all_next(text=True)
+# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
+#  u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']
+
+first_link.find_next("p")
+# <p class="story">...</p>
+
+
+

最初の例では、起点となった<a>タグに挟まれている、文字列”Elsie”が返されています。 +2番めの例では、起点となった<a>タグと同じパートじゃないにも関わらず、最後の<p>タグが示されています。 +これらのメソッドでは、問題はフィルターにマッチするか否かと、スタートした要素よりも後にでてきたかということが問われます。(!要改善)

+
+
+

find_all_previous() / find_previous()

+

使い方: find_all_previous(name, attrs, text, limit, **kwargs)

+

使い方: find_previous(name, attrs, text, **kwargs)

+

これらのメソッドは、ドキュメントの起点のタグの前にあらわれるタグと文字列の要素全てをイテレートして扱うために、 .previous_elements メソッドを使います。(!要改善):

+
first_link = soup.a
+first_link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+first_link.find_all_previous("p")
+# [<p class="story">Once upon a time there were three little sisters; ...</p>,
+#  <p class="title"><b>The Dormouse's story</b></p>]
+
+first_link.find_previous("title")
+# <title>The Dormouse's story</title>
+
+
+

find_all_previous("p") は”three sisters”ドキュメントの最初の段落を見つけます。(class=”title”のやつです) +しかし、第2段落でも見つけます。<p>タグは内に起点にした<a>タグを含んでいます。 +驚きすぎないでください。 +我々は、起点のタグより前方に現れた全てのタグを見ているのです。<a>タグを挟んでいる<p>タグは、<a>タグよりも前に示されねばなりません。(!要改善)

+
+
+

CSSセレクタ

+

Beautiful Soupは、よく使われるCSSセレクタをほとんどサポートしています。 +Tag オブジェクトや BeautifulSoup オブジェクトに .select() メソッドで文字列を渡すだけで使えます。

+

タグを見つけるには次のようにします。:

+
soup.select("title")
+# [<title>The Dormouse's story</title>]
+
+soup.select("p nth-of-type(3)")
+# [<p class="story">...</p>]
+
+
+

あるタグより後ろの指定されたタグを見つけます。:

+
soup.select("body a")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select("html head title")
+# [<title>The Dormouse's story</title>]
+
+
+

あるタグの直後の指定されたタグを見つけます。:

+
soup.select("head > title")
+# [<title>The Dormouse's story</title>]
+
+soup.select("p > a")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select("p > a:nth-of-type(2)")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+soup.select("p > #link1")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+soup.select("body > a")
+# []
+
+
+

タグの兄弟要素を見つけます。:

+
soup.select("#link1 ~ .sister")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie"  id="link3">Tillie</a>]
+
+soup.select("#link1 + .sister")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+

CSSクラスによってタグを見つけます。:

+
soup.select(".sister")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select("[class~=sister]")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

CSSのIDによってタグを見つけます。:

+
soup.select("#link1")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+soup.select("a#link2")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+

指定の属性の有無でタグを見つけます。:

+
soup.select('a[href]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

属性が持つ値によってタグを見つけます。:

+
soup.select('a[href="http://example.com/elsie"]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+soup.select('a[href^="http://example.com/"]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select('a[href$="tillie"]')
+# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select('a[href*=".com/el"]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+
+

languageコードで、マッチさせます。:

+
multilingual_markup = """
+ <p lang="en">Hello</p>
+ <p lang="en-us">Howdy, y'all</p>
+ <p lang="en-gb">Pip-pip, old fruit</p>
+ <p lang="fr">Bonjour mes amis</p>
+"""
+multilingual_soup = BeautifulSoup(multilingual_markup)
+multilingual_soup.select('p[lang|=en]')
+# [<p lang="en">Hello</p>,
+#  <p lang="en-us">Howdy, y'all</p>,
+#  <p lang="en-gb">Pip-pip, old fruit</p>]
+
+
+

このやり方は、CSSセレクタの文法を知っているユーザにとっては、とても便利です。 +これでBeautiful Soup APIの全てのキモを使えるようになりました。 +もしCSSセレクタを使いこなしたいなら、lxmlを使ってみるのもよいでしょう。 +lxmlは処理がとても速く、さらに多くのCSSセレクタをサポートしています。 +しかし、ここではBeautiful Soup APIを使って、シンプルなCSSセレクタの組み合わせるによる方法を説明しました。

+
+
+
+

パースツリーを修正

+

Beautiful Soupの主な強みは、パースツリーの検索するところにあります。 +しかしまた、Beautiful Soupは、ツリーを修正したり、変更したツリーを新しいHTMLやXMLのドキュメントに出力することもできます。

+
+

名前や属性の変更

+

属性 の節でも述べましたが、タグの名前変更、属性値の変更、追加、削除ができます。:

+
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
+tag = soup.b
+
+tag.name = "blockquote"
+tag['class'] = 'verybold'
+tag['id'] = 1
+tag
+# <blockquote class="verybold" id="1">Extremely bold</blockquote>
+
+del tag['class']
+del tag['id']
+tag
+# <blockquote>Extremely bold</blockquote>
+
+
+
+
+

.string の修正

+

Tag オブジェクトの .string を変更すると、そのタグが挟む文字列がその値に変更されます。:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+
+tag = soup.a
+tag.string = "New link text."
+tag
+# <a href="http://example.com/">New link text.</a>
+
+
+

注意点: 変更したタグが他のタグを挟んでいると、それらのタグ全てが破壊されます。

+
+
+

append()

+

Tag.append() により、タグが挟んでいる文字列に追加をすることができます。 +まるでPythonのリストの .append() のように作用します。:

+
soup = BeautifulSoup("<a>Foo</a>")
+soup.a.append("Bar")
+
+soup
+# <html><head></head><body><a>FooBar</a></body></html>
+soup.a.contents
+# [u'Foo', u'Bar']
+
+
+
+
+

BeautifulSoup.new_string() / .new_tag()

+

ドキュメントに文字列を加えたいときは、Pythonの文字列を append() に渡してください。 +もしくは、 factory methodBeautifulSoup.new_string() を呼出してください。:

+
soup = BeautifulSoup("<b></b>")
+tag = soup.b
+tag.append("Hello")
+new_string = soup.new_string(" there")
+tag.append(new_string)
+tag
+# <b>Hello there.</b>
+tag.contents
+# [u'Hello', u' there']
+
+
+

新しいコメントや 他の NavigableString のサブクラスを生成したいときは、 new_string() の第2引数にそのクラスを渡してください。:

+
from bs4 import Comment
+new_comment = soup.new_string("Nice to see you.", Comment)
+tag.append(new_comment)
+tag
+# <b>Hello there<!--Nice to see you.--></b>
+tag.contents
+# [u'Hello', u' there', u'Nice to see you.']
+
+
+

(これはBeautiful Soup 4.2.1 の新機能です)

+

完全に新しいタグを生成したいときは、factory methodの BeautifulSoup.new_tag() を呼び出してください。:

+
soup = BeautifulSoup("<b></b>")
+original_tag = soup.b
+
+new_tag = soup.new_tag("a", href="http://www.example.com")
+original_tag.append(new_tag)
+original_tag
+# <b><a href="http://www.example.com"></a></b>
+
+new_tag.string = "Link text."
+original_tag
+# <b><a href="http://www.example.com">Link text.</a></b>
+
+
+

第1引数のタグ名だけは必須です。

+
+
+

insert()

+

Tag.insert()Tag.append() に似ています。 +違うのは、タグの .contents の最後以外にも、要素を挿入できるという点です。:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+tag = soup.a
+
+tag.insert(1, "but did not endorse ")
+tag
+# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
+tag.contents
+# [u'I linked to ', u'but did not endorse', <i>example.com</i>]
+
+
+
+
+

insert_before() / insert_after()

+

insert_before() メソッドは、あるタグの直前に、別のタグや文字列を挿入します。:

+
soup = BeautifulSoup("<b>stop</b>")
+tag = soup.new_tag("i")
+tag.string = "Don't"
+soup.b.string.insert_before(tag)
+soup.b
+# <b><i>Don't</i>stop</b>
+
+
+

insert_after() メソッドは、あるタグの直後に、別のタグや文字列を挿入します。:

+
soup.b.i.insert_after(soup.new_string(" ever "))
+soup.b
+# <b><i>Don't</i> ever stop</b>
+soup.b.contents
+# [<i>Don't</i>, u' ever ', u'stop']
+
+
+
+
+

clear()

+

Tag.clear() は、タグが挟んでいるcontentsを削除します。(訳注:要チェック?):

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+tag = soup.a
+
+tag.clear()
+tag
+# <a href="http://example.com/"></a>
+
+
+
+
+

extract()

+

PageElement.extract() はツリーからタグや文字列を除去します。 +返値は、その抽出されたタグや文字列です。:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+i_tag = soup.i.extract()
+
+a_tag
+# <a href="http://example.com/">I linked to</a>
+
+i_tag
+# <i>example.com</i>
+
+print(i_tag.parent)
+None
+
+
+

このとき、2つのパースツリーがあります。1つは BeautifulSoup オブジェクトを根ノードとしたあなたがパースしたドキュメントです。もう1つは、抽出したタグを根ノードとするものです。抽出した要素の子要素を extract でコールできます。:

+
my_string = i_tag.string.extract()
+my_string
+# u'example.com'
+
+print(my_string.parent)
+# None
+i_tag
+# <i></i>
+
+
+
+
+

decompose()

+

Tag.decompose() はパースツリーからタグを除去します。 +そのタグと挟んでいるcontentsを完全に削除します

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+soup.i.decompose()
+
+a_tag
+# <a href="http://example.com/">I linked to</a>
+
+
+
+
+

replace_with()

+

PageElement.replace_with() はツリーからタグと文字列を除去し、 +引数に与えたタグや文字をその代わりに置き換えます。:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+new_tag = soup.new_tag("b")
+new_tag.string = "example.net"
+a_tag.i.replace_with(new_tag)
+
+a_tag
+# <a href="http://example.com/">I linked to <b>example.net</b></a>
+
+
+

replace_with() は置き換えられたタグや文字列を返します。 +それを、調査したり、ツリーの他の部分に加えることができます。

+
+
+

wrap()

+

PageElement.wrap() は、その要素を引数で指定したタグを挟みます。 +新しく挟まれたものを返します。

+
soup = BeautifulSoup("<p>I wish I was bold.</p>")
+soup.p.string.wrap(soup.new_tag("b"))
+# <b>I wish I was bold.</b>
+
+soup.p.wrap(soup.new_tag("div")
+# <div><p><b>I wish I was bold.</b></p></div>
+
+

このメソッドは、Beautiful Soup 4.0.5 からの新機能です。

+
+
+

unwrap()

+

Tag.unwrap()wrap() の反対です。 +それは、タグの中身がなんであれ、それとタグを置き換えます。 +マークアップをはずすのに便利です。(訳注:やりなおし??):

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+a_tag.i.unwrap()
+a_tag
+# <a href="http://example.com/">I linked to example.com</a>
+
+
+

replace_with() のように、 unwrap() は置き換えられたタグを返します。

+
+
+
+

出力

+
+

きれいに出力

+

prettify() メソッドは、BeautifulSoupパースツリーを、1行に1タグのきれいにフォーマットされたUnicode文字列に変換します。:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+soup.prettify()
+# '<html>\n <head>\n </head>\n <body>\n  <a href="http://example.com/">\n...'
+
+print(soup.prettify())
+# <html>
+#  <head>
+#  </head>
+#  <body>
+#   <a href="http://example.com/">
+#    I linked to
+#    <i>
+#     example.com
+#    </i>
+#   </a>
+#  </body>
+# </html>
+
+
+

prettify() メソッドは、 トップレベルの BeautifulSoup オブジェクトでも、それ以外の Tag オブジェクトでも呼び出すことができます。:

+
print(soup.a.prettify())
+# <a href="http://example.com/">
+#  I linked to
+#  <i>
+#   example.com
+#  </i>
+# </a>
+
+
+
+
+

一行に出力

+

フォーマットされたテキストではなく単なる文字列がほしければ、 BeautifulSoupTag オブジェクトの unicode()str() を呼び出せます。:

+
str(soup)
+# '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'
+
+unicode(soup.a)
+# u'<a href="http://example.com/">I linked to <i>example.com</i></a>'
+
+
+

str() 関数は、UTF-8にエンコードされた文字列を返します。 +他のオプションを知りたければ、 エンコード をみてください。

+

バイト文字列を得るのに、 encode() を用いることもできます。 +decode() を用いると、Unicodeを得ることができます。

+
+
+

フォーマットを指定

+

Beautiful Soupに、”&lquot;”のようなHTMLエンティティを含んだドキュメントを渡すと、それらはUnicodeキャラクタに変換されます。:

+
soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.")
+unicode(soup)
+# u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
+
+
+

そのドキュメントを文字列に変換すると、Unicode文字列はUTF-8キャラクタとしてエンコードされます。 +それを、HTMLエンティティに戻すことはできません。:

+
str(soup)
+# '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'
+
+
+

デフォルトでは、出力するときエスケープされるのは、裸の&と角かっこのみです。 +これらは、”&amp;”,”&lt;”,”&gt”に変換されます。 +そのためBeautifulSoupはうっかり不正確なHTMLやXMLを生成することはありません。:

+
soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>")
+soup.p
+# <p>The law firm of Dewey, Cheatem, &amp; Howe</p>
+
+soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
+soup.a
+# <a href="http://example.com/?foo=val1&amp;bar=val2">A link</a>
+
+
+

prettify(), encode(), decode()formatter 属性に値を与えると、出力を変更することができます。 +formatter は、4種類の値をとり得ます。

+

デフォルトでは、 formatter="minimal" です。 +文字列は、Beautiful Soupが正しいHTML/XMLを生成することを十分に保証するように、加工されるだけです。:

+
french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
+soup = BeautifulSoup(french)
+print(soup.prettify(formatter="minimal"))
+# <html>
+#  <body>
+#   <p>
+#    Il a dit &lt;&lt;Sacrテゥ bleu!&gt;&gt;
+#   </p>
+#  </body>
+# </html>
+
+
+

もし、 formatter="html" を渡せば、BSは 可能なときはいつでも、Unicode文字列を HTMLエンティティに変換します。:

+
print(soup.prettify(formatter="html"))
+# <html>
+#  <body>
+#   <p>
+#    Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;
+#   </p>
+#  </body>
+# </html>
+
+
+

もし、 formatter=None を渡せば、BSは出力においてまったく文字列を修正しません。 +これは、最速のオプションですが、BSが正しくないHTML/XMLを生成することになります。 +次の例をご覧ください。:

+
print(soup.prettify(formatter=None))
+# <html>
+#  <body>
+#   <p>
+#    Il a dit <<Sacrテゥ bleu!>>
+#   </p>
+#  </body>
+# </html>
+
+link_soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
+print(link_soup.a.encode(formatter=None))
+# <a href="http://example.com/?foo=val1&bar=val2">A link</a>
+
+
+

formatter に関数を渡すと、ドキュメントの文字列や属性値のたびに、BSはその関数をコールします。 +関数内で望むことはなんであれできます。 +以下では、formatterは文字列を大文字にコンバートし、他には何もしません。:

+
def uppercase(str):
+    return str.upper()
+
+print(soup.prettify(formatter=uppercase))
+# <html>
+#  <body>
+#   <p>
+#    IL A DIT <<SACRテ BLEU!>>
+#   </p>
+#  </body>
+# </html>
+
+print(link_soup.a.prettify(formatter=uppercase))
+# <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
+#  A LINK
+# </a>
+
+
+

もしあなたがあなたの関数を書いたなら、あなたは bs4.dammitEntitySubstitution クラスについて知るべきです。 +このクラスは、BSの標準的なformatter をクラスメソッドとして内包します。 +“html” formatterは EntitySubstitution.substitute_html , +“minimal” formatterは EntitySubstitution.substitute_xml です。 +あなたは、これらの関数を、 formatter==htmlformatter==minimal をシュミレーションします。 +しかし、それに加えて他のこともします。

+

これは例です。UnicodeキャラクタをHTMLエンティティに置換します。可能なときはいつでも。 +しかし、 また 全ての文字列を大文字に変換します。:

+
from bs4.dammit import EntitySubstitution
+def uppercase_and_substitute_html_entities(str):
+    return EntitySubstitution.substitute_html(str.upper())
+
+print(soup.prettify(formatter=uppercase_and_substitute_html_entities))
+# <html>
+#  <body>
+#   <p>
+#    IL A DIT &lt;&lt;SACR&Eacute; BLEU!&gt;&gt;
+#   </p>
+#  </body>
+# </html>
+
+
+

最後に一点(最終通告?): もし CData オブジェクトを生成したときは、そのオブジェクト内のテキストは 正確にあるがまま、フォーマットされることなく いつも表されます。 +BSは formatterメソッドを呼出します。あなたがカスタムメソッドを書いた場合にのみ。どういうカスタムメソッド化というと、全てのドキュメント内の文字列やなにかをcountする。しかし、それは返り値を無視します。:

+
from bs4.element import CData
+soup = BeautifulSoup("<a></a>")
+soup.a.string = CData("one < three")
+print(soup.a.prettify(formatter="xml"))
+# <a>
+#  <![CDATA[one < three]]>
+# </a>
+
+
+
+
+

get_text()

+

ドキュメントやタグのテキスト部分だけが取得したいときは、 get_text() メソッドを使います。 +それは、全ドキュメントや下層のタグを、ユニコードの単一文字列として返します。:

+
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
+soup = BeautifulSoup(markup)
+
+soup.get_text()
+u'\nI linked to example.com\n'
+soup.i.get_text()
+u'example.com'
+
+
+

テキストをまとめる際の区切り文字を指定することができます。:

+
# soup.get_text("|")
+u'\nI linked to |example.com|\n'
+
+
+

各文字列パーツの最初と最後の空白を除去することもできます。:

+
# soup.get_text("|", strip=True)
+u'I linked to|example.com'
+
+
+

空白を除去するのに、 stripped_strings ジェネレーターを使って処理することもできます。:

+
[text for text in soup.stripped_strings]
+# [u'I linked to', u'example.com']
+
+
+
+
+
+

パーサーの指定

+

もし貴方がいくつかのhtmlをパースしたいなら、あなたは、 Beautiful Soup コンストラクタに、マークアップをダンプできる。 +それはたぶんうまくいきます。 +Beautiful Soupはパーサーを選んで、データをパースします。 +しかし、どのパーサーが使われるか変更するために、コンストラクタに渡すいくつかの引数があります

+

1つ目の BeautifulSoup コンストラクタの引数は、 あなたがパースしたいマークアップの、文字列または開いているファイルハンドルです。 +2つ目の引数は、どのように マークアップをぱーすするかについてです。

+

もし何も指定しなかった場合は、インストールされているなかで最高のHTMLパーサーを使います。 +Beautiful Soupは、lxmlのパーサーを最高のものとしています。そして、html5libとPythonの組み込みパーサー。 +あなたは次のうちの一つを指定することで、これを上書きできます。

+
    +
  • パースしたいマークアップの種類: サポートしているのは、”html”, “xml”, “html5”です。
  • +
+
    +
  • パーサーライブラリの名前: オプションとしてサポートしているのは、”lxml”, “html5lib”, (Pythonの組み込みHTMLパーサーである) “html.parser”。
  • +
+

この パーサーのインストール の章は、サポートしているパーサーを比較します。

+

もし適切なパーサーをインストールしていないときは、Beautiful Soupはあなたのリクエストを無視し、違うパーサーを選びます。 +現在、ただ一つサポートされているXMLパーサーは、lxmlです。 +もし、lxmlをインストールしてないとき、XMLの要求はあなたに何も与えませんし、”lxml”へのリクエストも動きません。(要改善!)

+
+

パーサーの違い

+

Beautiful Soupは多くの異なるパーサーに同じインターフェースを提供しています。 +しかし、パーサーはそれぞれは異なります。 +パーサーが異なると、同じドキュメントでも、生成されるパースツリーは異なってきます。 +HTMLパーサーとXMLパーサーには大きな違いがあります。 +以下は、短いドキュメントをHTMLとしてパースしたものです。:

+
BeautifulSoup("<a><b /></a>")
+# <html><head></head><body><a><b></b></a></body></html>
+
+
+

空の<b />タグは、正式なHTMLではないため、パーサーはそれを<b></b>のタグの組に変換します。

+

以下は、同じドキュメントをXMLとしてパースしたものです。 +(これを実行するにはlxmlをインストールしておく必要があります) +<b />タグはそのまま残っており、ドキュメントはXML宣言が<html>タグの代わりに加えられたことに気づいてください。:

+
BeautifulSoup("<a><b /></a>", "xml")
+# <?xml version="1.0" encoding="utf-8"?>
+# <a><b/></a>
+
+
+

HTMLパーサー同士でも、違いはあります。 +完全な形のHTMLドキュメントをBeautiful Soupに与えたときは、その違いは問題になりません。 +あるパーサーは、他のパーサーよりも速いでしょう。 +しかし、それらは全て元のHTMLドキュメントを正確に反映したデータ構造を与えるでしょう。

+

しかし、不完全な形のHTMLドキュメントのときは、異なるパーサーは異なる結果を出力します。 +以下は、lxmlのHTMLパーサーによってパースされた短く不正なドキュメントです。 +ぶらさがっている</p>タグは、単に無視されていることに気づいてください。:

+
BeautifulSoup("<a></p>", "lxml")
+# <html><body><a></a></body></html>
+
+
+

以下は、html5libによってパースされた同じドキュメントです。:

+
BeautifulSoup("<a></p>", "html5lib")
+# <html><head></head><body><a><p></p></a></body></html>
+
+
+

ぶらさがっている</p>タグを無視する代わりに、html5libは、それを開始の<p>タグと組にしました。 +このパーサーはまた、ドキュメントに空の<head>タグも加えました。

+

以下は、Python組み込みのHTMLパーサーで同じドキュメントをパースしたものです。:

+
BeautifulSoup("<a></p>", "html.parser")
+# <a></a>
+
+
+

html5libのように、このパーサーは終わりの</p>タグを無視します。 +html5libとは違い、このパーサーは<body>タグを加えて正しい書式のHTMLドキュメントを作成しようとはしません。 +lxmlとは違い、なんとかして<html>タグを加えようとはしません。

+

“<a></p>”というドキュメントは不正なので、これについての”正しい”処理方法はありません。 +html5libパーサーはhtml5標準のいち部分のテクニックを使います。 +それは、ただしい主張を正しい方法についてします。しかし、これらの3つの方法全て、道理に合っています。(?あとで再チェック)

+

パーサー間の違いは、あなたのスクリプトにも影響するでしょう。 +もし、スクリプトを他の人に配布したり、複数の計算機で実行しようとするならば、 Beautiful Soup コンストラクタについてパーサーを指定するべきです。 +そうすることによって、あなたがパースした方法と違うやりかたでドキュメントをパースする可能性を減らすでしょう。

+
+
+
+

エンコード

+

HTMLやXMLドキュメントは全て、ASCIIやUTF-8のような特定の文字コードで書かれています。 +しかし、BeautifulSoupにドキュメントをロードすると、それらはUnicode型に変換されます。:

+
markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
+soup = BeautifulSoup(markup)
+soup.h1
+# <h1>Sacré bleu!</h1>
+soup.h1.string
+# u'Sacr\xe9 bleu!'
+
+
+

これは魔法ではありません。Beautiful Soupは Unicode, Dammit を内部でライブラリとして呼び出し、文字コードを判別してUnicodeに変換するのに使っています。 +自動判別された文字コードは、 BeautifulSoup オブジェクトの .original_encoding 属性で参照することができます。:

+
soup.original_encoding
+'utf-8'
+
+
+

Unicode, Dammit はほとんどの場合正しく判別しますが、たまに失敗します。 +たいてい適切に判別しますが、バイト毎の検索の場合は、とてもながい時間がかかります。 +もし、ドキュメントの文字コードが分かっているのなら、失敗や遅延を避けるために、 BeautifulSoup コンストラクタに from_encoding として渡すとよいです。

+

次の例は、ISO-8859-8(訳注:ラテン文字等の文字コード)で書かれたドキュメントです。 +このドキュメントは短いために、Unicode, Dammitはそれが何か判別できず、それをISO-8859-7(訳注:ギリシア文字等の文字コード)と誤認します。:

+
markup = b"<h1>\xed\xe5\xec\xf9</h1>"
+soup = BeautifulSoup(markup)
+soup.h1
+<h1>νεμω</h1>
+soup.original_encoding
+'ISO-8859-7'
+
+

正しい from_encoding を渡すことで、これを正すことができます。:

+
soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
+soup.h1
+<h1>ם ו ל ש</h1>
+soup.original_encoding
+'iso8859-8'
+
+

(通常、UTF-8のドキュメントは複数の文字コードを含むことができますが、) ごくまれに、変換できない文字をユニコードの特殊文字”REPLACEMENT CHARACTER” (U+FFFD,�) に置き換えることがあります。 +Unicode, Dammit がこれを行うときは、同時に、 UnicodeDammitBeautifulSoup オブジェクトの .contains_replacement_characters 属性にTrueをセットします。 +これにより、変換後のユニコードの文字列は、元の文字コードの文字列を正確に表現しておらず、いくつかのデータが損なわれているということがわかります。 +もし、 .contains_replacement_charactersFalse のときは、ドキュメント内に特殊文字�があっても、それは(この段落の�のように)もともとあり、データは損なわれていないということです。

+
+

出力のエンコード

+

Beautiful Soupでドキュメントを出力すると、元のドキュメントがUTF-8でなくても、UTF-8で出力されます。 +次の例は、Latin-1で書かれたドキュメントについてです。:

+
markup = b'''
+ <html>
+  <head>
+   <meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
+  </head>
+  <body>
+   <p>Sacr\xe9 bleu!</p>
+  </body>
+ </html>
+'''
+
+soup = BeautifulSoup(markup)
+print(soup.prettify())
+# <html>
+#  <head>
+#   <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
+#  </head>
+#  <body>
+#   <p>
+#    Sacrテゥ bleu!
+#   </p>
+#  </body>
+# </html>
+
+
+

<meta>タグは書き換えられ、ドキュメントが現在UTF-8であることを示しています。:

+
.. If you don't want UTF-8, you can pass an encoding into ``prettify()``::
+
+

UTF-8以外で出力したいときは、 prettify() にその文字コードを渡してください。:

+
print(soup.prettify("latin-1"))
+# <html>
+#  <head>
+#   <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
+# ...
+
+
+

Pythonのstr型であるかのように、BeautifulSoup オブジェクトや、その要素のencode()をコールすることができます。:

+
soup.p.encode("latin-1")
+# '<p>Sacr\xe9 bleu!</p>'
+
+soup.p.encode("utf-8")
+# '<p>Sacr\xc3\xa9 bleu!</p>'
+
+
+

あなたが選んだ文字コードでは表せない文字は、XMLエンティティリファレンスの数字に変換されます。 +次の例は、スノーマンのユニコード文字を含んだドキュメントです。:

+
markup = u"<b>\N{SNOWMAN}</b>"
+snowman_soup = BeautifulSoup(markup)
+tag = snowman_soup.b
+
+
+

スノーマンの文字はUTF-8のドキュメントに組み込めます。(それは☃と表示されます。しかし、ISO-Latin-1やASCIIにはその文字がありません。そこで、これらの文字コードでは”&#9731”に変換されます。):

+
print(tag.encode("utf-8"))
+# <b>☃</b>
+
+print tag.encode("latin-1")
+# <b>&#9731;</b>
+
+print tag.encode("ascii")
+# <b>&#9731;</b>
+
+
+
+
+

Unicode, Dammit

+

Beautiful Soup 抜きで、Unicode, Dammitを使えます。 +文字コードがわからないデータを持つときや、Unicodeにそのデータを変換したいときは、それは便利です。:

+
from bs4 import UnicodeDammit
+dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!")
+print(dammit.unicode_markup)
+# Sacré bleu!
+dammit.original_encoding
+# 'utf-8'
+
+
+

Pythonライブラリ chardetcchardet をインストールしていれば、Unicode, Dammitはさらに正確に文字コードを推測できます。:

+
dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
+print(dammit.unicode_markup)
+# Sacré bleu!
+dammit.original_encoding
+# 'latin-1'
+
+
+

Unicode, Dammitには、Beautiful Soupが使わない2つの機能があります。

+
+

スマート引用符

+

(訳注: スマート引用符とは、引用符’で左右の向き(open/close)が区別されているもののことです。 +ASCIIコードやシフトJISの引用符は区別されていません。 +[ 参考リンク ])

+

Unicode, Dammitは Microsoftスマート引用符を、HTMLやXMLのエンティティに変換します。:

+
markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"
+
+UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
+# u'<p>I just &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'
+
+UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
+# u'<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'
+
+
+

Microsoftスマート引用符をASCII引用符に変換できます。:

+
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
+# u'<p>I just "love" Microsoft Word\'s smart quotes</p>'
+
+
+

できればこの機能を便利に使ってほしいですが、Beautiful Soupはそれを使いません。 +Beautiful Soupは、他の文字と同じように、Microsoftスマート引用符をUnicodeキャラクタに変換するという、デフォルトの振るまいを選びます。:

+
UnicodeDammit(markup, ["windows-1252"]).unicode_markup
+# u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'
+
+
+
+
+

複数の文字コード

+

ときどき、ほぼUTF-8で書かれているが、一部Microsoftスマート引用符のような文字コードがWindows-1252の文字を含むドキュメントがあります。:

+
snowmen = (u"\N{SNOWMAN}" * 3)
+quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
+doc = snowmen.encode("utf8") + quote.encode("windows_1252")
+
+
+

このドキュメントは扱いに困ります。 +スノーマンはUTF-8ですが、スマート引用符はWindows-1252です。 +スノーマンか引用符のどちらかしか表示できません。:

+
print(doc)
+# ☃☃☃�I like snowmen!�
+
+print(doc.decode("windows-1252"))
+# ☃☃☃“I like snowmen!”
+
+
+

ドキュメントをUTF-8としてデコードすると、 UnicodeDecodeError が発生し、Windows-1252でデコードすると意味不明(gibberish?)なことになります。 +幸いなことに、 UnicodeDammit.detwingle() はその文字をpure UTF-8に変換し、それをUnicodeにデコードし、スノーマンと引用符を並べて表示することを許可します。:

+
new_doc = UnicodeDammit.detwingle(doc)
+print(new_doc.decode("utf8"))
+# ☃☃☃“I like snowmen!”
+
+
+

UnicodeDammit.detwingle() UTF-8に埋め込まれたWindows-1252の文字を扱う方法(とその逆)のみを知っています。しかしこれは、よくあるケースではありません。

+

データを BeautifulSoupUnicodeDammit コンストラクタに渡す前に、 UnicodeDammit.detwingle() をコールしなければならないことに注意してください。 +Beautiful Soupは 何らかの単一の文字コードでドキュメントが記されていると想定しています。 +もし、UTF-8とWindows-1252の両方を含むドキュメントを渡したら、ドキュメント全体がWindows-1252と判断しがちです。そして、そしてそのドキュメントの出力は、 ` テ「ヒ愴津「ヒ愴津「ヒ愴停廬 like snowmen!窶拜` のようになります。

+

UnicodeDammit.detwingle() はBeautiful Soup 4.1.0からの機能です。

+
+
+
+
+

ドキュメントの一部をパース

+

あるドキュメントの<a>タグに対してBeautiful Soupを使いたい場合、ドキュメント全部をパースして、その中から<a>タグを探すのは、時間とメモリの無駄です。 +最初にでてくる<a>タグ以外を全て無視すれば、処理は速くなります。 +SoupStrainer クラスは、与えられたドキュメントのどの部分をパースするかを選ぶことができます。 +そのためには、 SoupStrainer を作成し、それを BeautifulSoup コンストラクタに parse_only 属性として渡すだけです。

+

(この機能はhtml5libパーサーを使っているときは、使えないことにご注意ください。 +もしhtml5libを使うときはどんなときでも、ドキュメント全体がパースされます。 +これは、html5libがパースツリーをそのように継続的に再構築するためです。 +もし、ドキュメントの一部がパースツリーに組み込まれてない場合は、それは裏ッシュします。 +それをさけるためには、例において、Beautiful SoupがPythonの組み込みパーサーを利用させてください)

+
+

SoupStrainer

+

SoupStrainer (スープ漉し器)クラスは、 パースツリーを検索: するときの典型的なメソッドである name, attrs, text, and **kwargs をもちます。 +以下は、 SoupStrainer オブジェクトの3通りの例です。:

+
from bs4 import SoupStrainer
+
+only_a_tags = SoupStrainer("a")
+
+only_tags_with_id_link2 = SoupStrainer(id="link2")
+
+def is_short_string(string):
+    return len(string) < 10
+
+only_short_strings = SoupStrainer(text=is_short_string)
+
+
+

ここで、”three sisters”ドキュメントをもう一回とりあげます。 +ドキュメントを SoupStrainer オブジェクトで3通りにパースするので、どうなるかを見てみましょう。:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
+# <a class="sister" href="http://example.com/elsie" id="link1">
+#  Elsie
+# </a>
+# <a class="sister" href="http://example.com/lacie" id="link2">
+#  Lacie
+# </a>
+# <a class="sister" href="http://example.com/tillie" id="link3">
+#  Tillie
+# </a>
+
+print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify())
+# <a class="sister" href="http://example.com/lacie" id="link2">
+#  Lacie
+# </a>
+
+print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
+# Elsie
+# ,
+# Lacie
+# and
+# Tillie
+# ...
+#
+
+
+

SoupStrainer パースツリーを検索 のメソッドに渡すことができます。 +これは、とても便利です。少しだけ説明します。:

+
soup = BeautifulSoup(html_doc)
+soup.find_all(only_short_strings)
+# [u'\n\n', u'\n\n', u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
+#  u'\n\n', u'...', u'\n']
+
+
+
+
+
+

トラブルシューティング

+
+

diagnose()

+

もし、Beautiful Soupがドキュメントに何かをしてトラブルになっているときは、どのドキュメントを diagnose() 関数に渡してみてください。(これはBeautiful Soup 4.2.0の新機能です) +Beautiful Soupは、どのようにパーサーがそのドキュメントを扱ったかというレポートを出力し、BeautifulSoupが使っているパーサーが失っているかどうかを教えてくれます。:

+
from bs4.diagnose import diagnose
+data = open("bad.html").read()
+diagnose(data)
+
+# Diagnostic running on Beautiful Soup 4.2.0
+# Python version 2.7.3 (default, Aug  1 2012, 05:16:07)
+# I noticed that html5lib is not installed. Installing it may help.
+# Found lxml version 2.3.2.0
+#
+# Trying to parse your data with html.parser
+# Here's what html.parser did with the document:
+# ...
+
+
+

diagnose()の出力をみてみると、どのように問題を解決すればよいかわかるでしょう。もし、わからなくても、助けをもとめるときに、 diagnose() の出力を貼り付けることができます。

+
+
+

パース時に出るエラー

+

パースエラーには2種類あります。 +1つは、クラッシュです。Beautifuls Soupにドキュメントを読み込ませたときに、例外が発生します。たいていそれは HTMLParser.HTMPParserError です。 +もう1つは、想定外の動作です。Beautiful Soupのパースツリーが、元のドキュメントのパースツリーとかなり違うことがあります。

+

これらのエラーは、たいていBeautiful Soupが原因ではありません。そのように言えるのは、Beautiful Soupがよくできたソフトウェアだからではなく、Beautiful Soupがパース処理のコードを含んでいないためです。 +代わりに、Beautiful Soupは外部のパーサーに頼っています。もしあるパーサーが正しいドキュメントをパースできないときは、他のパーサーを試してみるというのが一番良い対処です。 +パーサーのインストール に、これについての詳細とパーサーの比較が載っています。

+

一番よくみるパースエラーは、 HTMLParser.HTMLParseError: malformed start tag と +HTMLParser.HTMLPraseError: bad end tag でしょう。 +これらはともに、Python組み込みのHTMLパーサーライブラリが返します。 +この場合は、 lxml か html5lib をインストール するとよいです。

+

想定外の動作のエラーで最も多いのは、あると思っていたタグを見つけられないときです。 +見たことあると思いますが、そのとき find_all()[] を返し、 find()None を返します。 +これも、Python組み込みHTMLパーサーにとっては、よくある問題です。やはり、一番よい対処は、 lxml か html5lib をインストール することです。

+
+
+

バージョン違いの問題

+
    +
  • SyntaxError: Invalid syntax (on the line ROOT_TAG_NAME = +u'[document]'): Python 2バージョンのBeautiful Soupを、変換しないでPython 3で実行したためです。
  • +
+
    +
  • ImportError: No module named HTMLParser - Python 2バージョンのBeautiful Soupを、Python 3で実行したためです。
  • +
+
    +
  • ImportError: No module named html.parser - Python 3バージョンのBeautiful Soupを、Python 2で実行したためです。
  • +
+
    +
  • ImportError: No module named BeautifulSoup - Beautiful Soup 3のコードを、BS3がインストールされてない環境で実行したため、またはBeautiful Soup 4のコードをパッケージ名を bs4 に変えずに実行したためです。
  • +
+
    +
  • ImportError: No module named bs4 - Beautiful Soup 4のコードを、BS4がインストールされてない環境で実行したためです。
  • +
+
+
+

XMLのパース

+

デフォルトでは、Beautiful SoupはドキュメントをHTMLとしてパースします。XMLとしてパースするには、 BeautifulSoup コンストラクタの第二引数に、 “xml” を渡します。:

+
soup = BeautifulSoup(markup, "xml")
+
+
+

このためには、 lxml をインストール している必要があります。

+
+
+

その他のパーサーの問題

+
    +
  • If your script works on one computer but not another, it’s probably +because the two computers have different parser libraries +available. For example, you may have developed the script on a +computer that has lxml installed, and then tried to run it on a +computer that only has html5lib installed. See パーサーの違い +for why this matters, and fix the problem by mentioning a +specific parser library in the BeautifulSoup constructor.
  • +
  • Because HTML tags and attributes are case-insensitive, all three HTML +parsers convert tag and attribute names to lowercase. That is, the +markup <TAG></TAG> is converted to <tag></tag>. If you want to +preserve mixed-case or uppercase tags and attributes, you’ll need to +parse the document as XML.
  • +
+
+
+

その他

+
    +
  • UnicodeEncodeError: 'charmap' codec can't encode character +u'\xfoo' in position bar (or just about any other +UnicodeEncodeError) - This is not a problem with Beautiful Soup. +This problem shows up in two main situations. First, when you try to +print a Unicode character that your console doesn’t know how to +display. (See this page on the Python wiki for help.) Second, when +you’re writing to a file and you pass in a Unicode character that’s +not supported by your default encoding. In this case, the simplest +solution is to explicitly encode the Unicode string into UTF-8 with +u.encode("utf8").
  • +
  • KeyError: [attr] - Caused by accessing tag['attr'] when the +tag in question doesn’t define the attr attribute. The most +common errors are KeyError: 'href' and KeyError: +'class'. Use tag.get('attr') if you’re not sure attr is +defined, just as you would with a Python dictionary.
  • +
  • AttributeError: 'ResultSet' object has no attribute 'foo' - This +usually happens because you expected find_all() to return a +single tag or string. But find_all() returns a _list_ of tags +and strings–a ResultSet object. You need to iterate over the +list and look at the .foo of each one. Or, if you really only +want one result, you need to use find() instead of +find_all().
  • +
  • AttributeError: 'NoneType' object has no attribute 'foo' - This +usually happens because you called find() and then tried to +access the .foo` attribute of the result. But in your case, +find() didn’t find anything, so it returned None, instead of +returning a tag or a string. You need to figure out why your +find() call isn’t returning anything.
  • +
+
+
+

パフォーマンス改善

+

Beautiful Soup will never be as fast as the parsers it sits on top +of. If response time is critical, if you’re paying for computer time +by the hour, or if there’s any other reason why computer time is more +valuable than programmer time, you should forget about Beautiful Soup +and work directly atop lxml.

+

That said, there are things you can do to speed up Beautiful Soup. If +you’re not using lxml as the underlying parser, my advice is to +start. Beautiful Soup parses documents +significantly faster using lxml than using html.parser or html5lib.

+

You can speed up encoding detection significantly by installing the +cchardet library.

+

ドキュメントの一部をパース won’t save you much time parsing +the document, but it can save a lot of memory, and it’ll make +searching the document much faster.

+
+
+
+

Beautiful Soup 3

+

Beautiful Soup 3は一つ前のリリースで、すでに開発は停止しています。 +現在でも、全ての主なLinuxディストリビューションに含まれています。:

+

$ apt-get install python-beautifulsoup

+

Pypiでも BeautifulSoup として利用できます。

+

$ easy_install BeautifulSoup

+

$ pip install BeautifulSoup

+

次のリンクからダウンロードできます。tarball of Beautiful Soup 3.2.0.

+

easy_install beautifulsoup , easy_install BeautifulSoup というコマンドでBeautiful Soupをインストールすると、あなたのコードは動きません。 easy_install beautifulsoup4 と入力しましょう。

+

Beautiful Soup 3 のドキュメントはアーカイブされています。

+

日本語版は次のリンクから参照できます。 Beautiful Soup ドキュメント +Beautiful Soup 4での変更点が理解するために、これらのドキュメントを読んでみてください。

+
+

BS4への移行

+

多くのBS3で書かれたコードは、一か所変更するだけでBS4で動きます。パッケージ名を BeautifulSoup から bs4 に変更するだけです。これを、、:

+
from BeautifulSoup import BeautifulSoup
+
+
+

以下のようにします。:

+
from bs4 import BeautifulSoup
+
+
+
    +
  • ImportError “No module named BeautifulSoup” が表示された場合、BS4しかインストールされていないのに、BS3のコードを実行しようとしたのが問題です。
  • +
+
    +
  • ImportError “No module named bs4” が表示された場合、BS3しかインストールされていないのに、BS4のコードを実行しようとしたのが問題です。
  • +
+

BS4はBS3の大部分について後方互換性がありますが、それらのメソッドのほとんどは変更され`PEP 8 規約 <http://www.python.org/dev/peps/pep-0008/>`_ に沿った新しい名前になっています。多くの名前等の変更により、後方互換性の一部が損なわれています。

+

以下は、BS3のコードをBS4に変換するのに知っておくべき事項です。:

+
+

パーサー

+

Beautiful Soup 3 used Python’s SGMLParser, a module that was +deprecated and removed in Python 3.0. Beautiful Soup 4 uses +html.parser by default, but you can plug in lxml or html5lib and +use that instead. See パーサーのインストール for a comparison.

+

Since html.parser is not the same parser as SGMLParser, it +will treat invalid markup differently. Usually the “difference” is +that html.parser crashes. In that case, you’ll need to install +another parser. But sometimes html.parser just creates a different +parse tree than SGMLParser would. If this happens, you may need to +update your BS3 scraping code to deal with the new tree.

+
+
+

メソッド名

+
    +
  • renderContents -> encode_contents
  • +
  • replaceWith -> replace_with
  • +
  • replaceWithChildren -> unwrap
  • +
  • findAll -> find_all
  • +
  • findAllNext -> find_all_next
  • +
  • findAllPrevious -> find_all_previous
  • +
  • findNext -> find_next
  • +
  • findNextSibling -> find_next_sibling
  • +
  • findNextSiblings -> find_next_siblings
  • +
  • findParent -> find_parent
  • +
  • findParents -> find_parents
  • +
  • findPrevious -> find_previous
  • +
  • findPreviousSibling -> find_previous_sibling
  • +
  • findPreviousSiblings -> find_previous_siblings
  • +
  • nextSibling -> next_sibling
  • +
  • previousSibling -> previous_sibling
  • +
+

Some arguments to the Beautiful Soup constructor were renamed for the +same reasons:

+
    +
  • BeautifulSoup(parseOnlyThese=...) -> BeautifulSoup(parse_only=...)
  • +
  • BeautifulSoup(fromEncoding=...) -> BeautifulSoup(from_encoding=...)
  • +
+

I renamed one method for compatibility with Python 3:

+
    +
  • Tag.has_key() -> Tag.has_attr()
  • +
+

I renamed one attribute to use more accurate terminology:

+
    +
  • Tag.isSelfClosing -> Tag.is_empty_element
  • +
+

I renamed three attributes to avoid using words that have special +meaning to Python. Unlike the others, these changes are not backwards +compatible. If you used these attributes in BS3, your code will break +on BS4 until you change them.

+
    +
  • UnicodeDammit.unicode -> UnicodeDammit.unicode_markup
  • +
  • Tag.next -> Tag.next_element
  • +
  • Tag.previous -> Tag.previous_element
  • +
+
+
+

ジェネレーター

+

I gave the generators PEP 8-compliant names, and transformed them into +properties:

+
    +
  • childGenerator() -> children
  • +
  • nextGenerator() -> next_elements
  • +
  • nextSiblingGenerator() -> next_siblings
  • +
  • previousGenerator() -> previous_elements
  • +
  • previousSiblingGenerator() -> previous_siblings
  • +
  • recursiveChildGenerator() -> descendants
  • +
  • parentGenerator() -> parents
  • +
+

So instead of this:

+
for parent in tag.parentGenerator():
+    ...
+
+
+

You can write this:

+
for parent in tag.parents:
+    ...
+
+
+

(But the old code will still work.)

+

Some of the generators used to yield None after they were done, and +then stop. That was a bug. Now the generators just stop.

+

There are two new generators, .strings and +.stripped_strings. .strings yields +NavigableString objects, and .stripped_strings yields Python +strings that have had whitespace stripped.

+
+
+

XML

+

There is no longer a BeautifulStoneSoup class for parsing XML. To +parse XML you pass in “xml” as the second argument to the +BeautifulSoup constructor. For the same reason, the +BeautifulSoup constructor no longer recognizes the isHTML +argument.

+

Beautiful Soup’s handling of empty-element XML tags has been +improved. Previously when you parsed XML you had to explicitly say +which tags were considered empty-element tags. The selfClosingTags +argument to the constructor is no longer recognized. Instead, +Beautiful Soup considers any empty tag to be an empty-element tag. If +you add a child to an empty-element tag, it stops being an +empty-element tag.

+
+
+

エンティティ

+

An incoming HTML or XML entity is always converted into the +corresponding Unicode character. Beautiful Soup 3 had a number of +overlapping ways of dealing with entities, which have been +removed. The BeautifulSoup constructor no longer recognizes the +smartQuotesTo or convertEntities arguments. (Unicode, +Dammit still has smart_quotes_to, but its default is now to turn +smart quotes into Unicode.) The constants HTML_ENTITIES, +XML_ENTITIES, and XHTML_ENTITIES have been removed, since they +configure a feature (transforming some but not all entities into +Unicode characters) that no longer exists.

+

If you want to turn Unicode characters back into HTML entities on +output, rather than turning them into UTF-8 characters, you need to +use an output formatter.

+
+
+

その他

+

Tag.string now operates recursively. If tag A +contains a single tag B and nothing else, then A.string is the same as +B.string. (Previously, it was None.)

+

値が複数のとき like class have lists of strings as +their values, not strings. This may affect the way you search by CSS +class.

+

If you pass one of the find* methods both text and +a tag-specific argument like name, Beautiful Soup will +search for tags that match your tag-specific criteria and whose +Tag.string matches your value for text. It will not find the strings themselves. Previously, +Beautiful Soup ignored the tag-specific arguments and looked for +strings.

+

The BeautifulSoup constructor no longer recognizes the +markupMassage argument. It’s now the parser’s responsibility to +handle markup correctly.

+

The rarely-used alternate parser classes like +ICantBelieveItsBeautifulSoup and BeautifulSOAP have been +removed. It’s now the parser’s decision how to handle ambiguous +markup.

+

The prettify() method now returns a Unicode string, not a bytestring.

+
+
+
+ + +
+
+
+
+
+

Table Of Contents

+ + +

This Page

+ + + + + + + + +
+
+
+
+ + + + diff --git a/doc.html/index.kr.html b/doc.html/index.kr.html new file mode 100644 index 0000000..03319c6 --- /dev/null +++ b/doc.html/index.kr.html @@ -0,0 +1,2476 @@ + + + + + + + + 뷰티플수프 문서 — 뷰티플수프 4.0.0 문서 + + + + + + + + + + + + + + +
+
+
+
+ +
+

뷰티플수프 문서

한글판 johnsonj 2012.11.08 원문 위치 +"The Fish-Footman began by producing from under his arm a great letter, nearly as large as himself." +

뷰티플수프는 HTML과 XML 파일로부터 데이터를 뽑아내기 위한 파이썬 라이브러리이다. 여러분이 선호하는 해석기와 함께 사용하여 일반적인 방식으로 해석 트리를 항해, 검색, 변경할 수 있다. 주로 프로그래머의 수고를 덜어준다.

+

이 지도서에서는 뷰티플수프 4의 중요한 특징들을 예제와 함께 모두 보여준다. 이 라이브러리가 어느 곳에 유용한지, 어떻게 작동하는지, 또 어떻게 사용하는지, 어떻게 원하는대로 바꿀 수 있는지, 예상을 빗나갔을 때 어떻게 해야 하는지를 보여준다.

+

이 문서의 예제들은 파이썬 2.7과 Python 3.2에서 똑 같이 작동한다.

+

혹시 뷰티플수프 3에 관한 문서를 찾고 계신다면 뷰티플수프 3는 더 이상 개발되지 않는다는 사실을 꼭 아셔야겠다. 새로 프로젝트를 시작한다면 뷰티플수프 4를 적극 추천한다. 뷰티플수프 3와 뷰티플수프 4의 차이점은 BS4 코드 이식하기를 참조하자.

+
+

도움 얻기

+

뷰피플수프에 의문이 있거나, 문제에 봉착하면 토론 그룹에 메일을 보내자.

+
+
+
+

바로 시작

+

다음은 이 문서에서 예제로 사용할 HTML 문서이다. 이상한 나라의 앨리스 이야기의 일부이다:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+
+

“three sisters” 문서를 뷰피플수프에 넣으면 BeautifulSoup 객체가 나오는데, 이 객체는 문서를 내포된 데이터 구조로 나타낸다:

+
from bs4 import BeautifulSoup
+soup = BeautifulSoup(html_doc)
+
+print(soup.prettify())
+# <html>
+#  <head>
+#   <title>
+#    The Dormouse's story
+#   </title>
+#  </head>
+#  <body>
+#   <p class="title">
+#    <b>
+#     The Dormouse's story
+#    </b>
+#   </p>
+#   <p class="story">
+#    Once upon a time there were three little sisters; and their names were
+#    <a class="sister" href="http://example.com/elsie" id="link1">
+#     Elsie
+#    </a>
+#    ,
+#    <a class="sister" href="http://example.com/lacie" id="link2">
+#     Lacie
+#    </a>
+#    and
+#    <a class="sister" href="http://example.com/tillie" id="link2">
+#     Tillie
+#    </a>
+#    ; and they lived at the bottom of a well.
+#   </p>
+#   <p class="story">
+#    ...
+#   </p>
+#  </body>
+# </html>
+
+
+

다음은 간단하게 데이터 구조를 항해하는 몇 가지 방법이다:

+
soup.title
+# <title>The Dormouse's story</title>
+
+soup.title.name
+# u'title'
+
+soup.title.string
+# u'The Dormouse's story'
+
+soup.title.parent.name
+# u'head'
+
+soup.p
+# <p class="title"><b>The Dormouse's story</b></p>
+
+soup.p['class']
+# u'title'
+
+soup.a
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+soup.find_all('a')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.find(id="link3")
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+
+
+

일반적인 과업으로 한 페이지에서 <a> 태그에 존재하는 모든 URL을 뽑아 낼 일이 많다:

+
for link in soup.find_all('a'):
+    print(link.get('href'))
+# http://example.com/elsie
+# http://example.com/lacie
+# http://example.com/tillie
+
+
+

또 다른 과업으로 페이지에서 텍스트를 모두 뽑아낼 일이 많다:

+
print(soup.get_text())
+# The Dormouse's story
+#
+# The Dormouse's story
+#
+# Once upon a time there were three little sisters; and their names were
+# Elsie,
+# Lacie and
+# Tillie;
+# and they lived at the bottom of a well.
+#
+# ...
+
+
+

이것이 여러분이 필요한 것인가? 그렇다면, 계속 읽어 보자.

+
+
+

뷰티플 수프 설치하기

+

데비안이나 우분투 리눅스 최신 버전을 사용중이라면, 시스템 꾸러미 관리자로 뷰티플수프를 설치하자:

+

$ apt-get install python-bs4

+

+뷰티블수프 4는 PyPi를 통하여도 출간되어 있으므로, 시스템 꾸러미 관리자로 설치할 수 없을 경우, easy_install로 설치하거나 +pip로 설치할 수 있다. 꾸러미 이름은 beautifulsoup4이며, 같은 꾸러미로 파이썬 2 그리고 파이썬 3에 작동한다.

+

$ easy_install beautifulsoup4

+

$ pip install beautifulsoup4

+

(이 BeautifulSoup 꾸러미가 혹시 원하는 것이 아니라면. 이전 버전으로 뷰티플수프 3가 있다. 많은 소프트웨에서 BS3를 사용하고 있으므로, 여전히 사용할 수 있다. 그러나 새로 코드를 작성할 생각이라면 beautifulsoup4를 설치하시기 바란다.)

+

easy_installpip도 설치되어 있지 않다면, +뷰티플수프 4 소스를 내려 받아 setup.py로 설치하실 수 있다.

+

$ python setup.py install

+

+다른 모든 것이 실패하더라도, 뷰티플수프 라이센스는 여러분의 어플리케이션에 통채로 꾸려 넣는 것을 허용하므로 전혀 설치할 필요없이 소스를 내려받아 bs4 디렉토리를 통채로 코드베이스에 복사해서 사용하셔도 된다.

+

+본인은 파이썬 2.7과 파이썬 3.2에서 뷰티플수프를 개발하였지만, 다른 최신 버전에도 작동하리라 믿는 바이다.

+
+

설치 이후의 문제

+

뷰티플 수프는 파이썬 2 코드로 꾸려 넣어져 있다. 파이썬 3에 사용하기 위해 설치하면, 파이썬 3 코드로 자동으로 변환된다. 꾸러미가 설치되어 있지 않다면, 당연히 변환되지 않는다. 또한 윈도우즈 머신이라면 잘못된 버전이 설치되어 있다고 보고된다.

+

“No module named HTMLParser”와 같은 ImportError 에러가 일어나면, 파이썬 3 아래에서 파이썬 2 버전의 코드를 실행하고 있기 때문이다.

+

“No module named html.parser”와 같은 ImportError 에러라면, 파이썬 3 버전의 코드를 파이썬 2 아래에서 실행하고 있기 때문이다.

+

두 경우 모두 최선의 선택은 시스템에서 (압축파일을 풀 때 만들어진 디렉토리를 모두 포함하여) 뷰티플수프를 제거하고 다시 설치하는 것이다.

+

다음 ROOT_TAG_NAME = u'[document]' 줄에서 SyntaxError “Invalid syntax”를 맞이한다면, 파이썬 2 코드를 파이썬 3 코드로 변환할 필요가 있다. 이렇게 하려면 다음과 같이 패키지를 설치하거나:

+

$ python3 setup.py install

+

아니면 직접 파이썬의 2to3 변환 스크립트를 +bs4 디렉토리에 실행하면 된다:

+

$ 2to3-3.2 -w bs4

+
+
+

해석기 설치하기

+

뷰티플수프는 파이썬 표준 라이브러리에 포함된 HTML 해석기를 지원하지만, 또 수 많은 제-삼자 파이썬 해석기도 지원한다. 그 중 하나는 lxml 해석기이다. 설정에 따라, 다음 명령어들 중 하나로 lxml을 설치하는 편이 좋을 경우가 있다:

+

$ apt-get install python-lxml

+

$ easy_install lxml

+

$ pip install lxml

+

파이썬 2를 사용중이라면, 또다른 대안은 순수-파이썬 html5lib 해석기를 사용하는 것인데, 이 해석기는 HTML을 웹 브라우저가 해석하는 방식으로 해석한다. 설정에 따라 다음 명령어중 하나로 html5lib를 설치하는 것이 좋을 때가 있다:

+

$ apt-get install python-html5lib

+

$ easy_install html5lib

+

$ pip install html5lib

+

다음 표에 각 해석 라이브러리의 장점과 단점을 요약해 놓았다:

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
해석기전형적 사용방법장점단점
파이썬의 html.parserBeautifulSoup(markup, "html.parser")
    +
  • 각종 기능 완비
  • +
  • 적절한 속도
  • +
  • 관대함 (파이썬 2.7.3과 3.2에서.)
  • +
+
    +
  • 별로 관대하지 않음 +(파이썬 2.7.3이나 3.2.2 이전 버전에서)
  • +
+
lxml의 HTML 해석기BeautifulSoup(markup, "lxml")
    +
  • 아주 빠름
  • +
  • 관대함
  • +
+
    +
  • 외부 C 라이브러리 의존
  • +
+
lxml의 XML 해석기BeautifulSoup(markup, ["lxml", "xml"]) +BeautifulSoup(markup, "xml")
    +
  • 아주 빠름
  • +
  • 유일하게 XML 해석기 지원
  • +
+
    +
  • 외부 C 라이브러리 의존
  • +
+
html5libBeautifulSoup(markup, html5lib)
    +
  • 아주 관대함
  • +
  • 웹 브라우저의 방식으로 페이지를 해석함
  • +
  • 유효한 HTML5를 생성함
  • +
+
    +
  • 아주 느림
  • +
  • 외부 파이썬 라이브러리 의존
  • +
  • 파이썬 2 전용
  • +
+
+

가능하다면, 속도를 위해 lxml을 설치해 사용하시기를 권장한다. 2.7.3 이전의 파이썬2, 또는3.2.2 이전의 파이썬 3 버전을 사용한다면, lxml을 사용하는 것이 필수이다. 그렇지 않고 구형 버전의 파이썬 내장 HTML 해석기 html5lib는 별로 좋지 않다.

+

문서가 유효하지 않을 경우 해석기마다 다른 뷰티플수프 트리를 생산한다는 사실을 주목하자. 자세한 것은 해석기들 사이의 차이점들을 살펴보자.

+
+
+
+

수프 만들기

+

문서를 해석하려면, 문서를 BeautifulSoup 구성자에 건네주자. 문자열 혹은 열린 파일 핸들을 건네면 된다:

+
from bs4 import BeautifulSoup
+
+soup = BeautifulSoup(open("index.html"))
+
+soup = BeautifulSoup("<html>data</html>")
+
+
+

먼저, 문서는 유니코드로 변환되고 HTML 개체는 유니코드 문자로 변환된다:

+
BeautifulSoup("Sacr&eacute; bleu!")
+<html><head></head><body>Sacré bleu!</body></html>
+
+

다음 뷰티플수프는 문서를 가장 적당한 해석기를 사용하여 해석한다. 특별히 XML 해석기를 사용하라고 지정해 주지 않으면 HTML 해석기를 사용한다. ( XML 해석하기 참조.)

+
+
+

객체의 종류

+

뷰티플수프는 복합적인 HTML 문서를 파이썬 객체로 구성된 복합적인 문서로 변환한다. 그러나 +객체의 종류를 다루는 법만 알면 된다.

+
+

태그

+

Tag 객체는 원래 문서의 XML 태그 또는 HTML 태그에 상응한다:

+
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
+tag = soup.b
+type(tag)
+# <class 'bs4.element.Tag'>
+
+
+

태그는 많은 속성과 메쏘드가 있지만, 그 대부분을 나중에 트리 항해하기 그리고 트리 검색하기에서 다룰 생각이다. 지금은 태그의 가장 중요한 특징인 이름과 속성을 설명한다.

+
+

이름

+

태그마다 이름이 있고, 다음 .name 과 같이 접근할 수 있다:

+
tag.name
+# u'b'
+
+
+

태그의 이름을 바꾸면, 그 변화는 뷰티블수프가 생산한 HTML 조판에 반영된다:

+
tag.name = "blockquote"
+tag
+# <blockquote class="boldest">Extremely bold</blockquote>
+
+
+
+
+

속성

+

태그는 속성을 여러개 가질 수 있다. <b +class="boldest"> 태그는 속성으로 “class”가 있는데 그 값은 +“boldest”이다. 태그의 속성에는 사전처럼 태그를 반복해 접근하면 된다:

+
tag['class']
+# u'boldest'
+
+
+

사전에 .attrs와 같이 바로 접근할 수 있다:

+
tag.attrs
+# {u'class': u'boldest'}
+
+
+

태그의 속성을 추가, 제거, 변경할 수 있다. 역시 태그를 사전처럼 취급해서 처리한다:

+
tag['class'] = 'verybold'
+tag['id'] = 1
+tag
+# <blockquote class="verybold" id="1">Extremely bold</blockquote>
+
+del tag['class']
+del tag['id']
+tag
+# <blockquote>Extremely bold</blockquote>
+
+tag['class']
+# KeyError: 'class'
+print(tag.get('class'))
+# None
+
+
+
+

값이-여럿인 속성

+

HTML 4에서 몇몇 속성은 값을 여러 개 가질 수 있도록 정의된다. HTML 5에서 그 중 2개는 제거되었지만, 몇 가지가 더 정의되었다. 가장 흔한 다중값 속성은 class이다 (다시 말해, 태그가 하나 이상의 CSS 클래스를 가질 수 있다). 다른 것으로는 rel, rev, accept-charset, +headers, 그리고 accesskey가 포함된다. 뷰티플수프는 다중-값 속성의 값들을 리스트로 나타낸다:

+
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
+css_soup.p['class']
+# ["body", "strikeout"]
+
+css_soup = BeautifulSoup('<p class="body"></p>')
+css_soup.p['class']
+# ["body"]
+
+
+

속성에 하나 이상의 값이 있는 것처럼 보이지만, HTML 표준에 정의된 다중-값 속성이 아니라면, 뷰티플수프는 그 속성을 그대로 둔다:

+
id_soup = BeautifulSoup('<p id="my id"></p>')
+id_soup.p['id']
+# 'my id'
+
+
+

태그를 다시 문자열로 바꾸면, 다중-값 속성은 합병된다:

+
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
+rel_soup.a['rel']
+# ['index']
+rel_soup.a['rel'] = ['index', 'contents']
+print(rel_soup.p)
+# <p>Back to the <a rel="index contents">homepage</a></p>
+
+
+

문서를 XML로 해석하면, 다중-값 속성은 없다:

+
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
+xml_soup.p['class']
+# u'body strikeout'
+
+
+
+
+
+ +
+

BeautifulSoup

+

+BeautifulSoup 객체 자신은 문서 전체를 대표한다. 대부분의 목적에, 그것을 Tag 객체로 취급해도 좋다. 이것은 곧 트리 항해하기트리 검색하기에 기술된 메쏘드들을 지원한다는 뜻이다.

+

+BeautifulSoup 객체는 실제 HTML 태그나 XML 태그에 상응하지 않기 때문에, 이름도 속성도 없다. 그러나 가끔 그의 이름 .name을 살펴보는 것이 유용할 경우가 있다. 그래서 특별히 +.name에 “[document]”라는 이름이 주어졌다:

+
soup.name
+# u'[document]'
+
+
+
+
+

주석과 기타 특수 문자열들

+

Tag, NavigableString, 그리고 BeautifulSoup 정도면 HTML이나 XML 파일에서 보게될 거의 모든 것들을 망라한다. 그러나 몇 가지 남은 것들이 있다. 아마도 신경쓸 필요가 있는 것이 유일하게 있다면 바로 주석이다:

+
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
+soup = BeautifulSoup(markup)
+comment = soup.b.string
+type(comment)
+# <class 'bs4.element.Comment'>
+
+
+

+Comment 객체는 그냥 특별한 유형의 NavigableString이다:

+
comment
+# u'Hey, buddy. Want to buy a used parser'
+
+
+

그러나 HTML 문서의 일부에 나타나면, Comment는 특별한 형태로 화면에 표시된다:

+
print(soup.b.prettify())
+# <b>
+#  <!--Hey, buddy. Want to buy a used parser?-->
+# </b>
+
+
+

뷰티플수프는 XML 문서에 나올만한 것들을 모두 클래스에다 정의한다: CData, ProcessingInstruction, +Declaration, 그리고 Doctype이 그것이다. Comment와 똑같이, 이런 클래스들은 NavigableString의 하위클래스로서 자신의 문자열에 다른 어떤것들을 추가한다. 다음은 주석을 CDATA 블록으로 교체하는 예이다:

+
from bs4 import CData
+cdata = CData("A CDATA block")
+comment.replace_with(cdata)
+
+print(soup.b.prettify())
+# <b>
+#  <![CDATA[A CDATA block]]>
+# </b>
+
+
+
+
+ +
+

트리 탐색하기

+

뷰티플수프에는 해석 트리를 탐색하기 위한 메쏘드들이 많이 정의되어 있지만, 모두 다 거의 비슷하다. 가장 많이 사용되는 두 가지 메쏘드를 설명하는데 시간을 많이 할애할 생각이다: find()find_all()이 그것이다. 다른 메쏘드는 거의 똑 같은 인자를 취한다. 그래서 그것들은 그냥 간략하게 다루겠다.

+

다시 또, “three sisters” 문서를 예제로 사용하자:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+from bs4 import BeautifulSoup
+soup = BeautifulSoup(html_doc)
+
+
+

find_all()과 같이 인자에 여과기를 건네면, 얼마든지 문서에서 관심있는 부분을 뜯어낼 수 있다.

+
+

여과기의 종류

+

find_all()과 유사 메쏘드들에 관하여 자세히 설명하기 전에 먼저, 이런 메쏘드들에 건넬 수 있는 다양한 여과기의 예제들을 보여주고 싶다. 이런 여과기들은 +탐색 API 전체에 걸쳐서 나타나고 또 나타난다. 태그의 이름, 그의 속성, 문자열 텍스트, 또는 이런 것들을 조합하여 여과할 수 있다.

+
+

문자열

+

+가장 단순한 여과기는 문자열이다. 문자열을 탐색 메쏘드에 건네면 뷰티플수프는 그 정확한 문자열에 맞게 부합을 수행한다. 다음 코드는 문서에서 <b> 태그를 모두 찾는다:

+
soup.find_all('b')
+# [<b>The Dormouse's story</b>]
+
+
+

바이트 문자열을 건네면, 뷰티플수프는 그 문자열이 UTF-8로 인코드되어 있다고 간주한다. 이를 피하려면 대신에 유니코드 문자열을 건네면 된다.

+
+
+

정규 표현식

+

정규 표현식 객체를 건네면, 뷰티플수프는 match() 메쏘드를 사용하여 그 정규 표현식에 맞게 여과한다. 다음 코드는 이름이 “b”로 시작하는 태그를 모두 찾는다; 이 경우, <body> 태그와 <b> 태그를 찾을 것이다:

+
import re
+for tag in soup.find_all(re.compile("^b")):
+    print(tag.name)
+# body
+# b
+
+
+

다음 코드는 이름에 ‘t’가 포함된 태그를 모두 찾는다:

+
for tag in soup.find_all(re.compile("t")):
+    print(tag.name)
+# html
+# title
+
+
+
+
+

리스트

+

리스트를 건네면, 뷰티플수프는 그 리스트에 담긴 항목마다 문자열 부합을 수행한다. 다음 코드는 모든 <a> 태그 그리고 모든 <b> 태그를 찾는다:

+
soup.find_all(["a", "b"])
+# [<b>The Dormouse's story</b>,
+#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+
+
+

True

+

True 값은 참이면 모두 부합시킨다. +다음 코드는 문서에서 태그를 모두 찾지만, 텍스트 문자열은 전혀 찾지 않는다:

+
for tag in soup.find_all(True):
+    print(tag.name)
+# html
+# head
+# title
+# body
+# p
+# b
+# p
+# a
+# a
+# a
+# p
+
+
+
+
+

함수

+

다른 어떤 부합 기준도 마음에 안든다면, 요소를 그의 유일한 인자로 취하는 함수를 정의하면 된다. 함수는 인자가 부합하면 +True를 돌려주고, 그렇지 않으면 False를 돌려주어야 한다.

+

다음은 태그에 “class”속성이 정의되어 있지만 “id” 속성은 없으면 True 를 돌려주는 함수이다:

+
def has_class_but_no_id(tag):
+    return tag.has_key('class') and not tag.has_key('id')
+
+
+

이 함수를 find_all()에 건네면 <p> 태그를 모두 얻게 된다:

+
soup.find_all(has_class_but_no_id)
+# [<p class="title"><b>The Dormouse's story</b></p>,
+#  <p class="story">Once upon a time there were...</p>,
+#  <p class="story">...</p>]
+
+
+

이 함수는 <p> 태그만 얻는다. <a> 태그는 획득하지 않는데, 왜냐하면 “class”와 “id”가 모두 정의되어 있기 때문이다. <html>과 <title>도 얻지 않는데, 왜냐하면 “class”가 정의되어 있지 않기 때문이다.

+

+다음은 태그가 문자열 객체로 둘러 싸여 있으면 True를 돌려주는 함수이다:

+
from bs4 import NavigableString
+def surrounded_by_strings(tag):
+    return (isinstance(tag.next_element, NavigableString)
+            and isinstance(tag.previous_element, NavigableString))
+
+for tag in soup.find_all(surrounded_by_strings):
+    print tag.name
+# p
+# a
+# a
+# a
+# p
+
+
+

이제 탐색 메쏘드들을 자세하게 살펴볼 준비가 되었다.

+
+
+
+

find_all()

+

서명: find_all(name, attrs, recursive, text, limit, **kwargs)

+

+find_all() 메쏘드는 태그의 후손들을 찾아서 지정한 여과기에 부합하면 모두 추출한다. 몇 가지 여과기에서 예제들을 제시했지만, 여기에 몇 가지 더 보여주겠다:

+
soup.find_all("title")
+# [<title>The Dormouse's story</title>]
+
+soup.find_all("p", "title")
+# [<p class="title"><b>The Dormouse's story</b></p>]
+
+soup.find_all("a")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.find_all(id="link2")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+import re
+soup.find(text=re.compile("sisters"))
+# u'Once upon a time there were three little sisters; and their names were\n'
+
+
+

어떤 것은 익숙하지만, 다른 것들은 새로울 것이다. text 혹은 id에 값을 건넨다는 것이 무슨 뜻인가? 왜 다음 +find_all("p", "title")은 CSS 클래스가 “title”인 <p> 태그를 찾는가? + find_all()에 건넨 인자들을 살펴보자.

+
+

name 인자

+

인자를 name에 건네면 뷰티플수프는 특정 이름을 가진 태그에만 관심을 가진다. 이름이 부합되지 않는 태그와 마찬가지로, 텍스트 문자열은 무시된다.

+

다음은 가장 단순한 사용법이다:

+
soup.find_all("title")
+# [<title>The Dormouse's story</title>]
+
+
+

여과기의 종류에서 보았듯이 name에 건넨 값이 문자열, 정규 표현식, 리스트, 함수, 또는 True 값일 수 있다는 사실을 기억하자.

+
+
+

키워드 인자

+

인지되지 않는 인자는 한 태그의 속성중 하나에 대한 여과기로 변환된다. + +id라는 인자에 대하여 값을 하나 건네면, 뷰티플수프는 각 태그의 ‘id’속성에 대하여 걸러낸다:

+
soup.find_all(id='link2')
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+

href에 대하여 값을 건네면, 뷰티플수프는 각 태그의 ‘href’속성에 대하여 걸러낸다:

+
soup.find_all(href=re.compile("elsie"))
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+
+

문자열, 정규 표현식, 리스트, 함수, 또는 True 값에 기반하여 속성을 걸러낼 수 있다.

+

다음 코드는 그 값이 무엇이든 상관없이, id 속성을 가진 태그를 모두 찾는다:

+
soup.find_all(id=True)
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

하나 이상의 키워드 인자를 건네면 한 번에 여러 값들을 걸러낼 수 있다:

+
soup.find_all(href=re.compile("elsie"), id='link1')
+# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
+
+
+
+
+

CSS 클래스로 탐색하기

+

특정 CSS 클래스를 가진 태그를 탐색하면 아주 유용하지만, CSS 속성의 이름인 “class”는 파이썬에서 예약어이다. 키워드 인자로 class를 사용하면 구문 에러를 만나게 된다. 뷰티플 4.1.2 부터, CSS 클래스로 검색할 수 있는데 class_ 키워드 인자를 사용하면 된다:

+
soup.find_all("a", class_="sister")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

다른 키워드 인자와 마찬가지로, class_에 문자열, 정규 표현식, 함수, 또는 True를 건넬 수 있다:

+
soup.find_all(class_=re.compile("itl"))
+# [<p class="title"><b>The Dormouse's story</b></p>]
+
+def has_six_characters(css_class):
+    return css_class is not None and len(css_class) == 6
+
+soup.find_all(class_=has_six_characters)
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

기억하자. 하나의 태그에 그의 “class” 속성에 대하여 값이 여러개 있을 수 있다. 특정 CSS 클래스에 부합하는 태그를 탐색할 때, 그의 CSS 클래스들 모두에 대하여 부합을 수행하는 것이다:

+
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
+css_soup.find_all("p", class_="strikeout")
+# [<p class="body strikeout"></p>]
+
+css_soup.find_all("p", class_="body")
+# [<p class="body strikeout"></p>]
+
+
+

+class 속성의 정확한 문자열 값을 탐색할 수도 있다:

+
css_soup.find_all("p", class_="body strikeout")
+# [<p class="body strikeout"></p>]
+
+
+

+그러나 문자열 값을 변형해서 탐색하면 작동하지 않는다:

+
css_soup.find_all("p", class_="strikeout body")
+# []
+
+
+

class_를 위한 간편한 방법이 뷰티플수프 모든 버전에 존재한다. find()-유형의 메쏘드에 건네는 두 번째 인자는 attrs인데, 문자열을 attrs에 건네면 그 문자열을 CSS 클래스처럼 탐색한다:

+
soup.find_all("a", "sister")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

정규 표현식, 함수 또는 사전을 제외하고 True–유형으로도 건넬 수 있다. 무엇을 건네든지 그 CSS 클래스를 탐색하는데 사용된다. class_ 키워드 인자에 건넬 때와 똑같다:

+
soup.find_all("p", re.compile("itl"))
+# [<p class="title"><b>The Dormouse's story</b></p>]
+
+
+

사전을 attrs에 건네면, 단지 그 CSS 클래스만 아니라 한번에 많은 HTML 속성을 탐색할 수 있다. 다음 코드 두 줄은 동등하다:

+
soup.find_all(href=re.compile("elsie"), id='link1')
+soup.find_all(attrs={'href' : re.compile("elsie"), 'id': 'link1'})
+
+
+

이것은 별로 유용한 특징은 아니다. 왜냐하면 보통 키워드 인자를 사용하는 편이 더 쉽기 때문이다.

+
+
+

text 인자

+

+text 인자로 태그 대신 문자열을 탐색할 수 있다. name과 키워드 인자에서처럼, 문자열, 정규 표현식, 리스트, 함수, 또는 True 값을 건넬 수 있다. +다음은 몇 가지 예이다:

+
soup.find_all(text="Elsie")
+# [u'Elsie']
+
+soup.find_all(text=["Tillie", "Elsie", "Lacie"])
+# [u'Elsie', u'Lacie', u'Tillie']
+
+soup.find_all(text=re.compile("Dormouse"))
+[u"The Dormouse's story", u"The Dormouse's story"]
+
+def is_the_only_string_within_a_tag(s):
+    """Return True if this string is the only child of its parent tag."""
+    return (s == s.parent.string)
+
+soup.find_all(text=is_the_only_string_within_a_tag)
+# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
+
+
+

+text가 문자열 찾기에 사용되지만, 태그를 찾는 인자와 결합해 사용할 수 있다: 뷰티플수프는 text에 대한 값에 자신의 .string이 부합하는 태그를 모두 찾는다. + +다음 코드는 자신의 .string이 “Elsie”인 <a> 태그를 찾는다:

+
soup.find_all("a", text="Elsie")
+# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
+
+
+
+
+

limit 인자

+

find_all() 메쏘드는 여과기에 부합하는 문자열과 태그를 모두 돌려준다. 이런 방법은 문서가 방대하면 시간이 좀 걸릴 수 있다. 결과가 모조리 필요한 것은 아니라면, limit에 숫자를 건넬 수 있다. 이 방법은 SQL에서의 LIMIT 키워드와 정확히 똑같이 작동한다. 뷰티플수프에게 특정 횟수를 넘어서면 결과 수집을 중지하라고 명령한다.

+

“three sisters” 문서에 링크가 세 개 있지만, 다음 코드는 앞의 두 링크만 찾는다:

+
soup.find_all("a", limit=2)
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+
+
+

recursive 인자

+

mytag.find_all()를 호출하면, 뷰티플수프는 mytag의 후손을 모두 조사한다: 그의 자손, 그 자손의 자손, 그리고 등등. 뷰티플수프에게 직계 자손만 신경쓰라고 시키고 싶다면, recursive=False를 건네면 된다. 다음에 차이점을 살펴보자:

+
soup.html.find_all("title")
+# [<title>The Dormouse's story</title>]
+
+soup.html.find_all("title", recursive=False)
+# []
+
+
+

다음은 예제 문서의 일부이다:

+
<html>
+ <head>
+  <title>
+   The Dormouse's story
+  </title>
+ </head>
+...
+
+

+<title> 태그는 <html> 태그 아래에 있지만, <html> 태그 바로 아래에 있는 것은 아니다: <head> 태그가 사이에 있다. 뷰티플수프는 <html> 태그의 모든 후손을 찾아 보도록 허용해야만 <title> 태그를 발견한다. 그러나 recursive=False가 검색을 +<html> 태그의 직접 자손으로 제한하기 때문에, 아무것도 찾지 못한다.

+

뷰티플수프는 트리-탐색 메쏘드들을 다양하게 제공한다 (아래에 다룸). 대부분 find_all()과 같은 인자를 취한다: name, +attrs, text, limit, 그리고 키워드 인자를 취한다. 그러나 recursive 인자는 다르다: find_all()find()만 유일하게 지원한다. recursive=Falsefind_parents() 같은 인자에 건네면 별로 유용하지 않을 것이다.

+
+
+
+

태그를 호출하는 것은 find_all()을 호출하는 것과 똑같다

+

+find_all()는 뷰티플수프 탐색 API에서 가장 많이 사용되므로, 그에 대한 간편 방법을 사용할 수 있다. BeautifulSoup 객체나 Tag 객체를 마치 함수처럼 다루면, 그 객체에 대하여 find_all()를 호출하는 것과 똑같다. 다음 코드 두 줄은 동등하다:

+
soup.find_all("a")
+soup("a")
+
+
+

다음 두 줄도 역시 동등하다:

+
soup.title.find_all(text=True)
+soup.title(text=True)
+
+
+
+
+

find()

+

서명: find(name, attrs, recursive, text, **kwargs)

+

+find_all() 메쏘드는 전체 문서를 훓어서 결과를 찾지만, 어떤 경우는 결과 하나만 원할 수도 있다. 문서에 오직 <body> 태그가 하나 뿐임을 안다면, 전체 문서를 훓어 가면서 더 찾는 것은 시간 낭비이다. find_all 메쏘드를 호출할 때마다, limit=1을 건네기 보다는 find() 메쏘드를 사용하는 편이 좋다. 다음 코드 두 줄은 거의 동등하다:

+
soup.find_all('title', limit=1)
+# [<title>The Dormouse's story</title>]
+
+soup.find('title')
+# <title>The Dormouse's story</title>
+
+
+

유일한 차이점은 find_all() 메쏘드가 단 한개의 결과만 담고 있는 리스트를 돌려주고, find()는 그냥 그 결과를 돌려준다는 점이다.

+

find_all()이 아무것도 찾을 수 없다면, 빈 리스트를 돌려준다. find()가 아무것도 찾을 수 없다면, None을 돌려준다:

+
print(soup.find("nosuchtag"))
+# None
+
+
+

+태그 이름을 사용하여 항해하기에서 soup.head.title 트릭을 기억하시는지? 그 트릭은 반복적으로 find() 를 호출해서 작동한다:

+
soup.head.title
+# <title>The Dormouse's story</title>
+
+soup.find("head").find("title")
+# <title>The Dormouse's story</title>
+
+
+
+
+

find_parents() 그리고 find_parent()

+

서명: find_parents(name, attrs, text, limit, **kwargs)

+

서명: find_parent(name, attrs, text, **kwargs)

+

+많은 시간을 할애해 find_all()과 +find()를 다루었다. 뷰티플수프 API에는 트리 탐색을 위해 다른 메쏘드가 열가지 정의되어 있지만, 걱정하지 말자. 이런 메쏘드중 다섯가지는 기본적으로 find_all()과 똑같고, 다른 다섯가지는 기본적으로 find()와 똑같다. 유일한 차이점은 트리의 어떤 부분을 검색할 것인가에 있다.

+

+먼저 find_parents()와 +find_parent()를 살펴보자. find_all()find()는 트리를 내려 오면서, 태그의 후손들을 찾음을 기억하자. 다음 메쏘드들은 정 반대로 일을 한다: 트리를 위로 올라가며, 한 태그의 (또는 문자열의) 부모를 찾는다. 시험해 보자.“three daughters” 문서 깊숙히 묻힌 문자열부터 시작해 보자:

+
a_string = soup.find(text="Lacie")
+a_string
+# u'Lacie'
+
+a_string.find_parents("a")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+a_string.find_parent("p")
+# <p class="story">Once upon a time there were three little sisters; and their names were
+#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
+#  and they lived at the bottom of a well.</p>
+
+a_string.find_parents("p", class="title")
+# []
+
+

세가지 <a> 태그 중 하나는 해당 문자열의 직계 부모이다. 그래서 탐색해서 그것을 찾는다. 세가지 <p> 태그 중 하나는 그 문자열의 방계 부모이고, 그것도 역시 잘 탐색한다. CSS 클래스가“title”인 <p> 태그가 문서 어딘가에 존재하지만, 그것은 이 문자열의 부모가 아니므로, find_parents()로 부모를 찾을 수 없다.

+

아마도 find_parent()find_parents(), 그리고 앞서 언급한 .parent.parents 속성 사이에 관련이 있으리라 짐작했을 것이다. 이 관련은 매우 강력하다. 이 탐색 메쏘드들은 실제로 .parents로 부모들을 모두 찾아서, 제공된 여과기준에 부합하는지 하나씩 점검한다.

+
+
+

find_next_siblings() 그리고 find_next_sibling()

+

서명: find_next_siblings(name, attrs, text, limit, **kwargs)

+

서명: find_next_sibling(name, attrs, text, **kwargs)

+

이 메쏘드들은 .next_siblings을 사용하여 트리에서 한 요소의 나머지 형제들을 반복한다. find_next_siblings() 메쏘드는 부합하는 형제들을 모두 돌려주고, find_next_sibling() 메쏘드는 그 중 첫 째만 돌려준다:

+
first_link = soup.a
+first_link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+first_link.find_next_siblings("a")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+first_story_paragraph = soup.find("p", "story")
+first_story_paragraph.find_next_sibling("p")
+# <p class="story">...</p>
+
+
+
+
+

find_previous_siblings() 그리고 find_previous_sibling()

+

서명: find_previous_siblings(name, attrs, text, limit, **kwargs)

+

서명: find_previous_sibling(name, attrs, text, **kwargs)

+

+이 메쏘드들은 .previous_siblings를 사용하여 트리에서 한 원소의 앞에 나오는 형제들을 반복한다. find_previous_siblings() 메쏘는 부합하는 형제들을 모두 돌려주고, find_previous_sibling()는 첫 째만 돌려준다:

+
last_link = soup.find("a", id="link3")
+last_link
+# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
+
+last_link.find_previous_siblings("a")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+first_story_paragraph = soup.find("p", "story")
+first_story_paragraph.find_previous_sibling("p")
+# <p class="title"><b>The Dormouse's story</b></p>
+
+
+
+
+

find_all_next() 그리고 find_next()

+

서명: find_all_next(name, attrs, text, limit, **kwargs)

+

서명: find_next(name, attrs, text, **kwargs)

+

+이 메쏘드들은 .next_elements를 사용하여 문서에서 한 태그의 뒤에 오는 태그이든 문자열이든 무엇이든지 반복한다. find_all_next() 메쏘드는 부합하는 것들을 모두 돌려주고, find_next()는 첫 번째 부합하는 것만 돌려준다:

+
first_link = soup.a
+first_link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+first_link.find_all_next(text=True)
+# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
+#  u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']
+
+first_link.find_next("p")
+# <p class="story">...</p>
+
+
+

첫 예제에서, 문자열 “Elsie”가 나타났다. 물론 그 안에 우리가 시작했던 <a> 태그 안에 포함되어 있음에도 불구하고 말이다. + +두 번째 예제를 보면, 문서의 마지막 <p> 태그가 나타났다. 물론 트리에서 우리가 시작했던 <a> 태그와 같은 부분에 있지 않음에도 불구하고 말이다. 이런 메쏘드들에게, 유일한 관심 사항은 원소가 여과 기준에 부합하는가 그리고 시작 원소 말고 나중에 문서에 나타나는가이다.

+
+
+

find_all_previous() 그리고 find_previous()

+

서명: find_all_previous(name, attrs, text, limit, **kwargs)

+

서명: find_previous(name, attrs, text, **kwargs)

+

+이 메쏘드들은 .previous_elements를 사용하여 문서에서 앞에 오는 태그나 문자열들을 반복한다. find_all_previous() 메쏘드는 부합하는 모든 것을 돌려주고, +find_previous()는 첫 번째 부합만 돌려준다:

+
first_link = soup.a
+first_link
+# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
+
+first_link.find_all_previous("p")
+# [<p class="story">Once upon a time there were three little sisters; ...</p>,
+#  <p class="title"><b>The Dormouse's story</b></p>]
+
+first_link.find_previous("title")
+# <title>The Dormouse's story</title>
+
+
+

find_all_previous("p")를 호출하면 문서에서 첫 번째 문단(class=”title”)을 찾지만, 두 번째 문단 <p> 태그도 찾는다. 이 안에 우리가 시작한 <a> 태그가 들어 있다. 이것은 그렇게 놀랄 일이 아니다: 시작한 위치보다 더 앞에 나타나는 태그들을 모두 찾고 있는 중이다. +<a> 태그가 포함된 <p> 태그는 자신 안에 든 <a> 태그보다 먼저 나타나는 것이 당연하다.

+
+
+

CSS 선택자

+

뷰티플수프는 CSS 선택자 표준의 부분집합을 지원한다. 그냥 문자열로 선택자를 구성하고 그것을 Tag.select() 메쏘드 또는 BeautifulSoup 객체 자체에 건네면 된다.

+

다음과 같이 태그를 검색할 수 있다:

+
soup.select("title")
+# [<title>The Dormouse's story</title>]
+
+
+

다른 태그 아래의 태그를 찾을 수 있다:

+
soup.select("body a")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select("html head title")
+# [<title>The Dormouse's story</title>]
+
+
+

다른 태그 바로 아래에 있는 태그를 찾을 수 있다:

+
soup.select("head > title")
+# [<title>The Dormouse's story</title>]
+
+soup.select("p > a")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select("body > a")
+# []
+
+
+

CSS 클래스로 태그를 찾는다:

+
soup.select(".sister")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select("[class~=sister]")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

ID로 태그를 찾는다:

+
soup.select("#link1")
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+soup.select("a#link2")
+# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
+
+
+

속성이 존재하는지 테스트 한다:

+
soup.select('a[href]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+
+

속성 값으로 태그를 찾는다:

+
soup.select('a[href="http://example.com/elsie"]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+soup.select('a[href^="http://example.com/"]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
+#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
+#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select('a[href$="tillie"]')
+# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
+
+soup.select('a[href*=".com/el"]')
+# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
+
+
+

언어 코덱을 일치 시킨다:

+
multilingual_markup = """
+ <p lang="en">Hello</p>
+ <p lang="en-us">Howdy, y'all</p>
+ <p lang="en-gb">Pip-pip, old fruit</p>
+ <p lang="fr">Bonjour mes amis</p>
+"""
+multilingual_soup = BeautifulSoup(multilingual_markup)
+multilingual_soup.select('p[lang|=en]')
+# [<p lang="en">Hello</p>,
+#  <p lang="en-us">Howdy, y'all</p>,
+#  <p lang="en-gb">Pip-pip, old fruit</p>]
+
+
+

이것은 CSS 선택자 구문을 알고 있는 사용자에게 유용하다. 이 모든 일들을 뷰티플수프 API로 할 수 있다. CSS 선택자만 필요하다면, lxml을 직접 사용하는 편이 좋을 것이다. 왜냐하면, 더 빠르기 때문이다. 그러나 이렇게 하면 간단한 CSS 선택자들을 뷰티플수프 API와 조합해 사용할 수 있다.

+
+
+
+

트리 변형하기

+

뷰티플수프의 강점은 해석 트리를 검색 하는데에 있다. 그러나 또한 해석 트리를 변형해서 새로운 HTML 또는 XML 문서로 저장할 수도 있다.

+
+

태그 이름과 속성 바꾸기

+

이에 관해서는 속성 부분에서 다룬 바 있지만, 다시 반복할 가치가 있다. 태그 이름을 바꾸고 그의 속성 값들을 바꾸며, 속성을 새로 추가하고, 속성을 삭제할 수 있다:

+
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
+tag = soup.b
+
+tag.name = "blockquote"
+tag['class'] = 'verybold'
+tag['id'] = 1
+tag
+# <blockquote class="verybold" id="1">Extremely bold</blockquote>
+
+del tag['class']
+del tag['id']
+tag
+# <blockquote>Extremely bold</blockquote>
+
+
+
+
+

.string 변경하기

+

태그의 .string 속성을 설정하면, 태그의 내용이 주어진 문자열로 교체된다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+
+tag = soup.a
+tag.string = "New link text."
+tag
+# <a href="http://example.com/">New link text.</a>
+
+
+

주의하자: 태그에 또 다른 태그가 들어 있다면, 그 태그는 물론 모든 내용이 사라진다.

+
+
+

append()

+

Tag.append()로 태그에 내용을 추가할 수 있다. 파이썬 리스트에 .append()를 호출한 것과 똑같이 작동한다:

+
soup = BeautifulSoup("<a>Foo</a>")
+soup.a.append("Bar")
+
+soup
+# <html><head></head><body><a>FooBar</a></body></html>
+soup.a.contents
+# [u'Foo', u'Bar']
+
+
+
+
+

BeautifulSoup.new_string() 그리고 .new_tag()

+

문자열을 문서에 추가하고 싶다면, 파이썬 문자열을 append()에 건네기만 하면 된다. 아니면 +BeautifulSoup.new_string() 공장 메쏘드를 호출하면 된다:

+
soup = BeautifulSoup("<b></b>")
+tag = soup.b
+tag.append("Hello")
+new_string = soup.new_string(" there")
+tag.append(new_string)
+tag
+# <b>Hello there.</b>
+tag.contents
+# [u'Hello', u' there']
+
+
+

완전히 새로 태그를 만들어야 한다면 어떻게 할까? 최선의 해결책은 BeautifulSoup.new_tag() 공장 메쏘드를 호출하는 것이다:

+
soup = BeautifulSoup("<b></b>")
+original_tag = soup.b
+
+new_tag = soup.new_tag("a", href="http://www.example.com")
+original_tag.append(new_tag)
+original_tag
+# <b><a href="http://www.example.com"></a></b>
+
+new_tag.string = "Link text."
+original_tag
+# <b><a href="http://www.example.com">Link text.</a></b>
+
+
+

오직 첫 번째 인자, 즉 태그 이름만 있으면 된다.

+
+
+

insert()

+

Tag.insert()Tag.append()와 거의 같은데, 단, 새 요소가 반드시 그의 부모의 .contents 끝에 갈 필요는 없다. 원하는 위치 어디든지 삽입된다. 파이썬 리스트의 .insert()와 똑같이 작동한다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+tag = soup.a
+
+tag.insert(1, "but did not endorse ")
+tag
+# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
+tag.contents
+# [u'I linked to ', u'but did not endorse', <i>example.com</i>]
+
+
+
+
+

insert_before() 그리고 insert_after()

+

+insert_before() 메쏘드는 태그나 문자열을 해석 트리에서 어떤 것 바로 앞에 삽입한다:

+
soup = BeautifulSoup("<b>stop</b>")
+tag = soup.new_tag("i")
+tag.string = "Don't"
+soup.b.string.insert_before(tag)
+soup.b
+# <b><i>Don't</i>stop</b>
+
+
+

+insert_after() 메쏘드는 해석 트리에서 다른 어떤 것 바로 뒤에 나오도록 태그나 문자열을 이동시킨다:

+
soup.b.i.insert_after(soup.new_string(" ever "))
+soup.b
+# <b><i>Don't</i> ever stop</b>
+soup.b.contents
+# [<i>Don't</i>, u' ever ', u'stop']
+
+
+
+
+

clear()

+

Tag.clear()은 태그의 내용을 제거한다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+tag = soup.a
+
+tag.clear()
+tag
+# <a href="http://example.com/"></a>
+
+
+
+
+

extract()

+

PageElement.extract()는 해석 트리에서 태그나 문자열을 제거한다. 추출하고 남은 태그나 문자열을 돌려준다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+i_tag = soup.i.extract()
+
+a_tag
+# <a href="http://example.com/">I linked to</a>
+
+i_tag
+# <i>example.com</i>
+
+print(i_tag.parent)
+None
+
+
+

이 시점에서 두 가지 해석 트리를 가지는 효과가 있다: 하나는 문서를 해석하는데 사용된 BeautifulSoup 객체에 뿌리를 두고, 또 하나는 추출된 그 태그에 뿌리를 둔다. 더 나아가 추출한 요소의 자손들에다 extract를 호출할 수 있다:

+
my_string = i_tag.string.extract()
+my_string
+# u'example.com'
+
+print(my_string.parent)
+# None
+i_tag
+# <i></i>
+
+
+
+
+

decompose()

+

Tag.decompose()는 태그를 트리에서 제거한 다음, 그와 그의 내용물을 완전히 파괴한다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+soup.i.decompose()
+
+a_tag
+# <a href="http://example.com/">I linked to</a>
+
+
+
+
+

replace_with()

+

PageElement.replace_with()는 트리에서 태그나 문자열을 제거하고 그것을 지정한 태그나 문자열로 교체한다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+new_tag = soup.new_tag("b")
+new_tag.string = "example.net"
+a_tag.i.replace_with(new_tag)
+
+a_tag
+# <a href="http://example.com/">I linked to <b>example.net</b></a>
+
+
+

replace_with()는 교체된 후의 태그나 문자열을 돌려준다. 그래서 검사해 보거나 다시 트리의 다른 부분에 추가할 수 있다.

+
+
+

wrap()

+

PageElement.wrap()는 지정한 태그에 요소를 둘러싸서 새로운 포장자를 돌려준다:

+
soup = BeautifulSoup("<p>I wish I was bold.</p>")
+soup.p.string.wrap(soup.new_tag("b"))
+# <b>I wish I was bold.</b>
+
+soup.p.wrap(soup.new_tag("div")
+# <div><p><b>I wish I was bold.</b></p></div>
+
+

다음 메쏘드는 뷰티플수프 4.0.5에 새로 추가되었다.

+
+
+

unwrap()

+

Tag.unwrap()wrap()의 반대이다. 태그를 그 태그 안에 있는 것들로 교체한다. 조판을 걷어내 버릴 때 좋다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+a_tag = soup.a
+
+a_tag.i.unwrap()
+a_tag
+# <a href="http://example.com/">I linked to example.com</a>
+
+
+

replace_with()처럼, unwrap()은 교체된 후의 태그를 돌려준다.

+

(이전 뷰티플수프 버전에서, unwrap()replace_with_children()이라고 불리웠으며, 그 이름은 여전히 작동한다.)

+
+
+
+

출력

+
+

예쁘게-인쇄하기

+

prettify() 메쏘드는 뷰티플수프 해석 트리를 멋지게 모양을 낸 유니코드 문자열로 변환한다. HTML/XML 태그마다 따로따로 한 줄에 표시된다:

+
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
+soup = BeautifulSoup(markup)
+soup.prettify()
+# '<html>\n <head>\n </head>\n <body>\n  <a href="http://example.com/">\n...'
+
+print(soup.prettify())
+# <html>
+#  <head>
+#  </head>
+#  <body>
+#   <a href="http://example.com/">
+#    I linked to
+#    <i>
+#     example.com
+#    </i>
+#   </a>
+#  </body>
+# </html>
+
+
+

최상위 BeautifulSoup 객체에 prettify()를 호출할 수 있으며, 또는 Tag 객체에 얼마든지 호출할 수 있다:

+
print(soup.a.prettify())
+# <a href="http://example.com/">
+#  I linked to
+#  <i>
+#   example.com
+#  </i>
+# </a>
+
+
+
+
+

있는-그대로 인쇄하기

+

멋진 모양 말고 그냥 문자열을 원한다면, BeautifulSoup 객체, 또는 그 안의 Tagunicode() 또는 str()을 호출하면 된다:

+
str(soup)
+# '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'
+
+unicode(soup.a)
+# u'<a href="http://example.com/">I linked to <i>example.com</i></a>'
+
+
+

str() 함수는 UTF-8로 인코드된 문자열을 돌려준다. 다른 옵션은 인코딩을 살펴보자.

+

encode()를 호출하면 bytestring을 얻을 수 있고, decode()로는 유니코드를 얻는다.

+
+
+

출력 포맷터

+

뷰티플수프 문서에 “&lquot;”와 같은 HTML 개체가 들어 있다면, 그 개체들은 유니코드 문자로 변환된다:

+
soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.")
+unicode(soup)
+# u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
+
+
+

문서를 문자열로 변환하면, 유니코드 문자들은 UTF-8로 인코드된다. HTML 개체는 다시 복구할 수 없다:

+
str(soup)
+# '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'
+
+
+

기본 값으로, 출력에서 피신 처리가 되는 유일한 문자들은 앰퍼센드와 옆꺽쇠 문자들이다. 이런 문자들은 “&amp;”, “&lt;”, 그리고 “&gt;”로 변환된다. 그래서 뷰티플수프는 무효한 HTML이나 XML을 생성하는 실수를 하지 않게 된다:

+
soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>")
+soup.p
+# <p>The law firm of Dewey, Cheatem, &amp; Howe</p>
+
+soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
+soup.a
+# <a href="http://example.com/?foo=val1&amp;bar=val2">A link</a>
+
+
+

이 행위를 바꾸려면 formatter 인자용 값을 prettify(), encode(), 또는 decode()에 제공하면 된다. + +뷰티플수프는 formatter에 대하여 가능한 네 가지 값을 인지한다.

+

기본값은 formatter="minimal"이다. 문자열은 뷰티플수프가 유효한 HTML/XML을 생산한다고 확신할 만큼 처리된다:

+
french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
+soup = BeautifulSoup(french)
+print(soup.prettify(formatter="minimal"))
+# <html>
+#  <body>
+#   <p>
+#    Il a dit &lt;&lt;Sacré bleu!&gt;&gt;
+#   </p>
+#  </body>
+# </html>
+
+
+

+formatter="html"을 건네면, 뷰티플수프는 유니코드 문자를 가능한한 HTML 개체로 변환한다:

+
print(soup.prettify(formatter="html"))
+# <html>
+#  <body>
+#   <p>
+#    Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;
+#   </p>
+#  </body>
+# </html>
+
+
+

formatter=None을 건네면, 뷰티플수프는 출력시 전혀 문자열을 건드리지 않는다. 이것이 가장 빠른 선택이지만, 다음 예제에서와 같이 잘못해서 뷰티플수프가 무효한 HTML/XML을 생산할 가능성이 있다:

+
print(soup.prettify(formatter=None))
+# <html>
+#  <body>
+#   <p>
+#    Il a dit <<Sacré bleu!>>
+#   </p>
+#  </body>
+# </html>
+
+link_soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
+print(link_soup.a.encode(formatter=None))
+# <a href="http://example.com/?foo=val1&bar=val2">A link</a>
+
+
+

+마지막으로, formatter에 함수를 건네면, 뷰티플수프는 문서에서 문자열과 속성 값에 대하여 하나하나 그 함수를 한 번 호출한다. 이 함수에서 무엇이든 할 수 있다. 다음은 문자열을 대문자로 바꾸고 다른 일은 절대로 하지 않는 포맷터이다:

+
def uppercase(str):
+    return str.upper()
+
+print(soup.prettify(formatter=uppercase))
+# <html>
+#  <body>
+#   <p>
+#    IL A DIT <<SACRÉ BLEU!>>
+#   </p>
+#  </body>
+# </html>
+
+print(link_soup.a.prettify(formatter=uppercase))
+# <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
+#  A LINK
+# </a>
+
+
+

따로 함수를 작성하고 있다면, bs4.dammit 모듈에 있는 EntitySubstitution 클래스에 관하여 알아야 한다. 이 클래스는 뷰티플수프의 표준 포맷터를 클래스 메쏘드로 구현한다: +“html”포맷터는 EntitySubstitution.substitute_html이고, “minimal” 포맷터는 EntitySubstitution.substitute_xml이다. 이 함수들을 사용하면 formatter=html나 +formatter==minimal를 흉내낼 수 있지만, 더 처리해야할 일이 있다.

+

다음은 가능하면 유니코드 문자를 HTML 개체로 교체하는 예제이다. 그러나 또한 모든 문자열을 대문자로 바꾼다:

+
from bs4.dammit import EntitySubstitution
+def uppercase_and_substitute_html_entities(str):
+    return EntitySubstitution.substitute_html(str.upper())
+
+print(soup.prettify(formatter=uppercase_and_substitute_html_entities))
+# <html>
+#  <body>
+#   <p>
+#    IL A DIT &lt;&lt;SACR&Eacute; BLEU!&gt;&gt;
+#   </p>
+#  </body>
+# </html>
+
+
+

마지막 단점: CData 객체를 만들면, 그 객체 안의 텍스트는 언제나 포맷팅 없이도, 정확하게 똑같이 나타난다. 문서에서 문자열 같은 것들을 세는 메쏘드를 손수 만들 경우, 뷰티플수프는 포맷터 메쏘드를 호출한다. 그러나 반환 값은 무시된다.

+
+
from bs4.element import CData +soup = BeautifulSoup(“<a></a>”) +soup.a.string = CData(“one < three”) +print(soup.a.prettify(formatter=”xml”)) +# <a> +# <![CDATA[one < three]]> +# </a>
+
+
+

get_text()

+

문서나 태그에서 텍스트 부분만 추출하고 싶다면, get_text() 메쏘드를 사용할 수 있다. 이 메쏘드는 문서나 태그 아래의 텍스트를, 유니코드 문자열 하나로 모두 돌려준다:

+
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
+soup = BeautifulSoup(markup)
+
+soup.get_text()
+u'\nI linked to example.com\n'
+soup.i.get_text()
+u'example.com'
+
+
+

텍스트를 합칠 때 사용될 문자열을 지정해 줄 수 있다:

+
# soup.get_text("|")
+u'\nI linked to |example.com|\n'
+
+
+

뷰티플수프에게 각 테스트의 앞과 뒤에 있는 공백을 걷어내라고 알려줄 수 있다:

+
# soup.get_text("|", strip=True)
+u'I linked to|example.com'
+
+
+

그러나 이 시점에서 대신에 .stripped_strings 발생자를 사용해서, 텍스트를 손수 처리하고 싶을 수 있겠다:

+
[text for text in soup.stripped_strings]
+# [u'I linked to', u'example.com']
+
+
+
+
+
+

사용할 해석기 지정하기

+

단지 HTML만 해석하고 싶을 경우, 조판을 BeautifulSoup 구성자에 넣기만 하면, 아마도 잘 처리될 것이다. 뷰티플수프는 해석기를 여러분 대신 선택해 데이터를 해석한다. 그러나 어느 해석기를 사용할지 바꾸기 위해 구성자에 건넬 수 있는 인자가 몇 가지 더 있다.

+

BeautifulSoup 구성자에 건네는 첫 번째 인자는 문자열이나 열린 파일핸들-즉 해석하기를 원하는 조판이 첫 번째 인자이다. 두 번째 인자는 그 조판이 어떻게 해석되기를 바라는지 지정한다.

+

아무것도 지정하지 않으면, 설치된 해석기중 최적의 HTML 해석기가 배당된다. 뷰티플수프는 lxml 해석기를 최선으로 취급하고, 다음에 html5lib 해석기, 그 다음이 파이썬의 내장 해석기를 선택한다. 이것은 다음 중 하나로 덮어쓸 수 있다:

+
    +
  • 해석하고 싶은 조판의 종류. 현재 “html”, “xml”, 그리고 “html5”가 지원된다.
  • +
  • 사용하고 싶은 해석기의 이름. 현재 선택은 “lxml”, “html5lib”, 그리고 “html.parser” (파이썬의 내장 HTML 해석기)이다.
  • +
+

해석기 설치하기 섹션에 지원 해석기들을 비교해 놓았다.

+

적절한 해석기가 설치되어 있지 않다면, 뷰티플수프는 여러분의 요구를 무시하고 다른 해석기를 선택한다. 지금 유일하게 지원되는 XML 해석기는 lxml이다. lxml 해석기가 설치되어 있지 않으면, XML 해석기를 요구할 경우 아무것도 얻을 수 없고, “lxml”을 요구하더라도 얻을 수 없다.

+
+

해석기 사이의 차이점들

+

뷰티플수프는 다양한 해석기에 대하여 인터페이스가 같다. 그러나 각 해석기는 다르다. 해석기마다 같은 문서에서 다른 해석 트리를 만들어낸다. 가장 큰 차이점은 HTML 해석기와 XML 해석기 사이에 있다. 다음은 HTML로 해석된 짧은 문서이다:

+
BeautifulSoup("<a><b /></a>")
+# <html><head></head><body><a><b></b></a></body></html>
+
+
+

빈 <b /> 태그는 유효한 HTML이 아니므로, 해석기는 그것을 <b></b> 태그 쌍으로 변환한다.

+

다음 똑같은 문서를 XML로 해석한 것이다 (이를 실행하려면 lxml이 설치되어 있어야 한다). 빈 <b /> 태그가 홀로 남았음에 유의하자. 그리고 <html> 태그를 출력하는 대신에 XML 선언이 주어졌음을 주목하자:

+
BeautifulSoup("<a><b /></a>", "xml")
+# <?xml version="1.0" encoding="utf-8"?>
+# <a><b /></a>
+
+
+

+HTML 해석기 사이에서도 차이가 있다. 뷰티플수프에 완벽하게 모양을 갖춘 HTML 문서를 주면, 이 차이는 문제가 되지 않는다. 비록 해석기마다 속도에 차이가 있기는 하지만, 모두 원래의 HTML 문서와 정확하게 똑같이 보이는 데이터 구조를 돌려준다.

+

그러나 문서가 불완전하게 모양을 갖추었다면, 해석기마다 결과가 다르다. 다음은 짧은 무효한 문서를 lxml의 HTML 해석기로 해석한 것이다. 나홀로 </p> 태그는 그냥 무시된다:

+
BeautifulSoup("<a></p>", "lxml")
+# <html><body><a></a></body></html>
+
+
+

다음은 같은 문서를 html5lib로 해석하였다:

+
BeautifulSoup("<a></p>", "html5lib")
+# <html><head></head><body><a><p></p></a></body></html>
+
+
+

나홀로 </p> 태그를 무시하는 대신에, html5lib는 여는 <p> 태그로 짝을 맞추어 준다. 이 해석기는 또한 빈 <head> 태그를 문서에 추가한다.

+

다음은 같은 문서를 파이썬 내장 HTML 해석기로 해석한 것이다:

+
BeautifulSoup("<a></p>", "html.parser")
+# <a></a>
+
+
+

html5lib처럼, 이 해석기는 닫는 </p> 태그를 무시한다. html5lib와 다르게, 이 해석기는 <body> 태그를 추가해서 모양을 갖춘 HTML 문서를 생성하려고 아무 시도도 하지 않는다. lxml과 다르게, 심지어 <html> 태그를 추가하는 것에도 신경쓰지 않는다.

+

문서 “<a></p>”는 무효하므로, 이 테크닉중 어느 것도 “올바른” 처리 방법이 아니다. html5lib 해석기는 HTML5 표준에 있는 테크닉을 사용하므로, 아무래도 “가장 올바른” 방법이라고 주장할 수 있지만, 세 가지 테크닉 모두 같은 주장을 할 수 있다.

+

해석기 사이의 차이점 때문에 스크립트가 영향을 받을 수 있다. 스크립트를 다른 사람들에게 나누어 줄 계획이 있다면, 또는 여러 머신에서 실행할 생각이라면, BeautifulSoup 구성자에 해석기를 지정해 주는 편이 좋다. 그렇게 해야 여러분이 해석한 방식과 다르게 사용자가 문서를 해석할 위험성이 감소한다.

+
+
+
+

인코딩

+

HTML이든 XML이든 문서는 ASCII나 UTF-8 같은 특정한 인코딩으로 작성된다. 그러나 문서를 뷰티플수프에 적재하면, 문서가 유니코드로 변환되었음을 알게 될 것이다:

+
markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
+soup = BeautifulSoup(markup)
+soup.h1
+# <h1>Sacré bleu!</h1>
+soup.h1.string
+# u'Sacr\xe9 bleu!'
+
+
+

마법이 아니다(확실히 좋은 것이다.). 뷰티플수프는 Unicode, Dammit라는 하위 라이브러리를 사용하여 문서의 인코딩을 탐지하고 유니코드로 변환한다. 자동 인코딩 탐지는 BeautifulSoup 객체의 .original_encoding 속성으로 얻을 수 있다:

+
soup.original_encoding
+'utf-8'
+
+
+

Unicode, Dammit은 대부분 올바르게 추측하지만, 가끔은 실수가 있다. 가끔 올바르게 추측하지만, 문서를 바이트 하나 하나 오랫동안 탐색한 후에야 그렇다. 혹시 문서의 인코딩을 미리 안다면, 그 인코딩을 BeautifulSoup 구성자에 from_encoding로 건네면 실수를 피하고 시간을 절약할 수 있다.

+

다음은 ISO-8859-8로 작성된 문서이다. 이 문서는 Unicode, Dammit이 충분히 살펴보기에는 너무 짧아서, ISO-8859-7로 잘못 인식한다:

+
markup = b"<h1>\xed\xe5\xec\xf9</h1>"
+soup = BeautifulSoup(markup)
+soup.h1
+<h1>νεμω</h1>
+soup.original_encoding
+'ISO-8859-7'
+
+

이를 해결하려면 올바른 from_encoding을 건네면 된다:

+
soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
+soup.h1
+<h1>םולש</h1>
+soup.original_encoding
+'iso8859-8'
+
+

아주 드물게 (보통 UTF-8 문서 안에 텍스트가 완전히 다른 인코딩으로 작성되어 있을 경우), 유일하게 유니코드를 얻는 방법은 몇 가지 문자를 특별한 유니코드 문자 “REPLACEMENT CHARACTER” (U+FFFD, �)로 교체하는 것이다 . Unicode, Dammit이 이를 필요로 하면, UnicodeDammit이나 BeautifulSoup 객체에 대하여 .contains_replacement_characters 속성에 True를 설정할 것이다. +이렇게 하면 유니코드 표현이 원래의 정확한 표현이 아니라는 사실을 알 수 있다. 약간 데이터가 손실된다. 문서에 �가 있지만, .contains_replacement_charactersFalse라면, 원래부터 거기에 있었고 데이터 손실을 감내하지 않는다는 사실을 알게 될 것이다.

+
+

출력 인코딩

+

뷰티플수프로 문서를 작성할 때, UTF-8 문서를 얻는다. 그 문서가 처음에는 UTF-8이 아니었다고 할지라도 말이다. 다음은 Latin-1 인코딩으로 작성된 문서이다:

+
markup = b'''
+ <html>
+  <head>
+   <meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
+  </head>
+  <body>
+   <p>Sacr\xe9 bleu!</p>
+  </body>
+ </html>
+'''
+
+soup = BeautifulSoup(markup)
+print(soup.prettify())
+# <html>
+#  <head>
+#   <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
+#  </head>
+#  <body>
+#   <p>
+#    Sacré bleu!
+#   </p>
+#  </body>
+# </html>
+
+
+

+<meta> 태그가 재작성되어 문서가 이제 UTF-8이라는 사실을 반영하고 있음을 주목하자.

+

UTF-8이 싫으면, 인코딩을 prettify()에 건넬 수 있다:

+
print(soup.prettify("latin-1"))
+# <html>
+#  <head>
+#   <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
+# ...
+
+
+

또 encode()를 BeautifulSoup 객체, 또는 수프의 다른 어떤 요소에라도 호출할 수 있다. 마치 파이썬 문자열처럼 말이다:

+
soup.p.encode("latin-1")
+# '<p>Sacr\xe9 bleu!</p>'
+
+soup.p.encode("utf-8")
+# '<p>Sacr\xc3\xa9 bleu!</p>'
+
+
+

선택한 인코딩에서 표현이 불가능한 문자는 숫자의 XML 개체 참조로 변환된다. 다음은 유니코드 문자 SNOWMAN이 포함된 문자이다:

+
markup = u"<b>\N{SNOWMAN}</b>"
+snowman_soup = BeautifulSoup(markup)
+tag = snowman_soup.b
+
+
+

눈사람 문자는 UTF-8 문서에 포함될 수 있지만 (☃처럼 생김), ISO-Latin-1이나 ASCII에 그 문자에 대한 표현이 없다. 그래서 “&#9731”으로 변환된다:

+
print(tag.encode("utf-8"))
+# <b>☃</b>
+
+print tag.encode("latin-1")
+# <b>&#9731;</b>
+
+print tag.encode("ascii")
+# <b>&#9731;</b>
+
+
+
+
+

이런, 유니코드군

+

뷰티플수프를 사용하지 않더라도 유니코드를 사용할 수 있다. 인코딩을 알 수 없는 데이터가 있을 때마다 그냥 유니코드가 되어 주었으면 하고 바라기만 하면 된다:

+
from bs4 import UnicodeDammit
+dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!")
+print(dammit.unicode_markup)
+# Sacré bleu!
+dammit.original_encoding
+# 'utf-8'
+
+
+

+유니코드에 더 많은 데이터를 줄 수록, Dammit은 더 정확하게 추측할 것이다. 나름대로 어떤 인코딩일지 짐작이 간다면, 그것들을 리스트로 건넬 수 있다:

+
dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
+print(dammit.unicode_markup)
+# Sacré bleu!
+dammit.original_encoding
+# 'latin-1'
+
+
+

Unicode, Dammit는 뷰티플수프가 사용하지 않는 특별한 특징이 두 가지 있다.

+
+

지능형 따옴표

+

Unicode, Dammit을 사용하여 마이크로소프트 지능형 따옴표를 HTML이나 XML 개체로 변환할 수 있다:

+
markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"
+
+UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
+# u'<p>I just &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'
+
+UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
+# u'<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'
+
+
+

또 마이크로소프트 지능형 따옴표를 ASCII 따옴표로 변환할 수 있다:

+
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
+# u'<p>I just "love" Microsoft Word\'s smart quotes</p>'
+
+
+

모쪼록 이 특징이 쓸모가 있기를 바라지만, 뷰티플수프는 사용하지 않는다. 뷰티플수프는 기본 행위를 선호하는데, 기본적으로 마이크로소프트 지능형 따옴표를 다른 모든 것과 함께 유니코드 문자로 변환한다:

+
UnicodeDammit(markup, ["windows-1252"]).unicode_markup
+# u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'
+
+
+
+
+

비 일관적인 인코딩

+

어떤 경우 문서 대부분이 UTF-8이지만, 안에 (역시) 마이크로소프트 지능형 따옴표와 같이 Windows-1252 문자가 들어 있는 경우가 있다. 한 웹 사이트에 여러 소스로 부터 데이터가 포함될 경우에 이런 일이 일어날 수 있다. +UnicodeDammit.detwingle()을 사용하여 그런 문서를 순수한 UTF-8 문서로 변환할 수 있다. 다음은 간단한 예이다:

+
snowmen = (u"\N{SNOWMAN}" * 3)
+quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
+doc = snowmen.encode("utf8") + quote.encode("windows_1252")
+
+
+

이 문서는 뒤죽박죽이다. 눈사람은 UTF-8인데 따옴표는 Windows-1252이다. 눈사람 아니면 따옴표를 화면에 나타낼 수 있지만, 둘 다 나타낼 수는 없다:

+
print(doc)
+# ☃☃☃�I like snowmen!�
+
+print(doc.decode("windows-1252"))
+# ☃☃☃“I like snowmen!”
+
+
+

문서를 UTF-8로 디코딩하면 UnicodeDecodeError가 일어나고, Windows-1252로 디코딩하면 알 수 없는 글자들이 출력된다. 다행스럽게도, UnicodeDammit.detwingle()는 그 문자열을 순수 UTF-8로 변환해 주므로, 유니코드로 디코드하면 눈사람과 따옴표를 동시에 화면에 보여줄 수 있다:

+
new_doc = UnicodeDammit.detwingle(doc)
+print(new_doc.decode("utf8"))
+# ☃☃☃“I like snowmen!”
+
+
+

UnicodeDammit.detwingle()는 오직 UTF-8에 임베드된 (또는 그 반대일 수도 있지만) Windows-1252을 다루는 법만 아는데, 이것이 가장 일반적인 사례이다.

+

BeautifulSoup이나 UnicodeDammit 구성자에 건네기 전에 먼저 데이터에 UnicodeDammit.detwingle()을 호출하는 법을 반드시 알아야 한다. 뷰티플수프는 문서에 하나의 인코딩만 있다고 간주한다. 그것이 무엇이든 상관없이 말이다. UTF-8과 Windows-1252를 모두 포함한 문서를 건네면, 전체 문서가 Windows-1252라고 생각할 가능성이 높고, 그 문서는 다음 ` ☃☃☃“I like snowmen!”`처럼 보일 것이다.

+

UnicodeDammit.detwingle()은 뷰티플수프 4.1.0에서 새로 추가되었다.

+
+
+
+
+

문서의 일부만을 해석하기

+

뷰티플수프를 사용하여 문서에서 <a> 태그를 살펴보고 싶다고 해보자. 전체 문서를 해석해서 훓어가며 <a> 태그를 찾는 일은 시간 낭비이자 메모리 낭비이다. 처음부터 <a> 태그가 아닌 것들을 무시하는 편이 더 빠를 것이 분명하다. SoupStrainer 클래스는 문서에 어느 부분을 해석할지 고르도록 해준다. 그냥 SoupStrainer를 만들고 그것을 BeautifulSoup 구성자에 parse_only 인자로 건네면 된다.

+

(이 특징은 html5lib 해석기를 사용중이라면 작동하지 않음을 주목하자. html5lib을 사용한다면, 어쨋거나 문서 전체가 해석된다. 이것은 html5lib가 작업하면서 항상 해석 트리를 재정렬하기 때문이다. 문서의 일부가 실제로 해석 트리에 맞지 않을 경우, 충돌을 일으킨다. 혼란을 피하기 위해, 아래의 예제에서 뷰티플수프에게 파이썬의 내장 해석기를 사용하라고 강제하겠다.)

+
+

SoupStrainer

+

SoupStrainer 클래스는 트리 탐색하기의 전형적인 메쏘드와 같은 인자들을 취한다: name, attrs, text, 그리고 **kwargs이 그 인자들이다. 다음은 세 가지 SoupStrainer 객체이다:

+
from bs4 import SoupStrainer
+
+only_a_tags = SoupStrainer("a")
+
+only_tags_with_id_link2 = SoupStrainer(id="link2")
+
+def is_short_string(string):
+    return len(string) < 10
+
+only_short_strings = SoupStrainer(text=is_short_string)
+
+
+

다시 한 번 더“three sisters” 문서로 돌아가 보겠다. 문서를 세 가지 SoupStrainer 객체로 해석하면 어떻게 보이는지 살펴보자:

+
html_doc = """
+<html><head><title>The Dormouse's story</title></head>
+
+<p class="title"><b>The Dormouse's story</b></p>
+
+<p class="story">Once upon a time there were three little sisters; and their names were
+<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
+<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
+<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
+and they lived at the bottom of a well.</p>
+
+<p class="story">...</p>
+"""
+
+print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
+# <a class="sister" href="http://example.com/elsie" id="link1">
+#  Elsie
+# </a>
+# <a class="sister" href="http://example.com/lacie" id="link2">
+#  Lacie
+# </a>
+# <a class="sister" href="http://example.com/tillie" id="link3">
+#  Tillie
+# </a>
+
+print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify())
+# <a class="sister" href="http://example.com/lacie" id="link2">
+#  Lacie
+# </a>
+
+print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
+# Elsie
+# ,
+# Lacie
+# and
+# Tillie
+# ...
+#
+
+
+

또한 SoupStrainer트리 탐색하기에서 다룬 메쏘드에 건넬 수 있다. 이는 별로 유용하지는 않지만, 그럼에도 언급해 둔다:

+
soup = BeautifulSoup(html_doc)
+soup.find_all(only_short_strings)
+# [u'\n\n', u'\n\n', u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
+#  u'\n\n', u'...', u'\n']
+
+
+
+
+
+

문제 해결

+
+

버전 불일치 문제

+
    +
  • SyntaxError: Invalid syntax (다음 ROOT_TAG_NAME = +u'[document]' 줄에서): 코드를 변경하지 않고서, 파이썬 2 버전의 뷰티플수프를 파이썬 3 아래에서 사용하기 때문에 야기된다.
  • +
  • ImportError: No module named HTMLParser - 파이썬 2 버전의 뷰티플수프를 파이썬 3 아래에서 사용하기 때문에 야기된다.
  • +
  • ImportError: No module named html.parser - 파이썬 3 버전의 뷰티플수프를 파이썬 2에서 실행하기 때문에 야기된다.
  • +
  • ImportError: No module named BeautifulSoup - 뷰티플수프 3 코드를 BS3가 설치되어 있지 않은 시스템에서 실행할 때 야기된다. 또는 꾸러미 이름이 bs4로 바뀌었음을 알지 못하고 뷰티플수프 4 코드를 실행하면 야기된다.
  • +
  • ImportError: No module named bs4 - 뷰티플수프 4 코드를 BS4가 설치되어 있지 않은 시스템에서 실행하면 야기된다.
  • +
+
+
+

XML 해석하기

+

기본값으로, 뷰티플수프는 문서를 HTML로 해석한다. 문서를 XML로 해석하려면, “xml”를 두 번째 인자로 BeautifulSoup 구성자에 건네야 한다:

+
soup = BeautifulSoup(markup, "xml")
+
+
+

lxml이 설치되어 있어야 한다.

+
+
+

기타 해석기 문제

+
    +
  • 스크립트가 한 컴퓨터에서는 잘 되는데 다른 컴퓨터에서는 작동하지 않는다면, 아마도 두 컴퓨터가 다른 해석기를 가지고 있기 때문일 것이다. 예를 들어, lxml이 설치된 컴퓨터에서 스크립트를 개발해 놓고, 그것을 html5lib만 설치된 컴퓨터에서 실행하려고 했을 수 있다. 왜 이것이 문제가 되는지는 해석기들 사이의 차이점을 참고하고, BeautifulSoup 구성자에 특정 라이브러리를 지정해서 문제를 해결하자.
  • +
  • HTMLParser.HTMLParseError: malformed start tag or +HTMLParser.HTMLParseError: bad end tag - 파이썬의 내장 HTML 해석기에 처리가 불가능한 문서를 건네면 야기된다. 다른 HTMLParseError도 아마 같은 문제일 것이다. 해결책: +lxml이나 html5lib를 설치하자.
  • +
  • 알고 있는데 문서에서 그 태그를 발견할 수 없다면 (다시 말해, +find_all()[]를 돌려주거나 find()None을 돌려줄 경우), 아마도 파이썬의 내장 HTML 해석기를 사용하고 있을 가능성이 높다. 이 해석기는 가끔 이해하지 못하면 그 태그를 무시하고 지나간다. 해결책: lxml이나 html5lib를 설치하자.
  • +
  • +HTML 태그와 속성은 대소문자를 구별하므로, 세가지 HTML 해석기 모두 태그와 속성 이름을 소문자로 변환한다. 다시 말해, 다음 조판 <TAG></TAG>는 <tag></tag>로 변환된다. 태그와 속성에 대소문자 혼합 또는 대문자를 그대로 유지하고 싶다면, 문서를 XML로 해석할 필요가 있다.
  • +
+
+
+

기타

+
    +
  • KeyError: [attr] - tag['attr']에 접근했는데 해당 태그에 attr 속성이 정의되어 있지 않을 때 야기된다. 가장 흔한 에러는 KeyError: 'href' 그리고 KeyError: +'class'이다. attr이 정의되어 있는지 잘 모르겠다면, 파이썬 사전에 그렇게 하듯이, tag.get('attr')을 사용하자.
  • +
  • UnicodeEncodeError: 'charmap' codec can't encode character +u'\xfoo' in position bar (또는 그냥 기타 다른 UnicodeEncodeError에 관한 모든 것) - 이 에러는 뷰티플수프에 관련된 문제가 아니다 .이 문제는 두 가지 상황에서 출현한다. 첫 째, 유니코드 문자열을 인쇄했는데 콘솔이 표시할 줄 모를 경우가 있다. (파이썬 위키에서 도움을 받자.) 둘째, 파일에 쓰는데 기본 인코딩으로 지원되지 않는 유니코드 문자열을 건넬 경우가 있다. 이런 경우, 가장 쉬운 해결책은 u.encode("utf8")을 지정해서 그 유니코드 문자열을 UTF-8로 명시적으로 인코드하는 것이다.
  • +
+
+
+

수행성능 개선

+

뷰티플수프는 그 밑에 깔린 해석기보다 더 빠를 수는 없다. 응답 시간이 중요하다면, 다시 말해, 시간제로 컴퓨터를 쓰고 있거나 아니면 컴퓨터 시간이 프로그래머 시간보다 더 가치가 있는 다른 이유가 조금이라도 있다면, 그렇다면 뷰티플수프는 잊어 버리고 직접 lxml 위에 작업하는 편이 좋을 것이다.

+

그렇지만, 뷰티플수프의 속도를 높일 수 있는 방법이 있다. 아래에 해석기로 lxml을 사용하고 있지 않다면, 당장 시작해 보기를 조언한다. 뷰티플수프는 html.parser나 html5lib를 사용하는 것보다 lxml을 사용하는 것이 문서를 상당히 더 빠르게 해석한다.

+

cchardet 라이브러리를 설치하면 인코딩 탐지 속도를 상당히 높일 수 있다.

+

가끔 Unicode, Dammit는 바이트별로 파일을 조사해서 인코딩을 탐지할 수 있을 뿐이다. 이 때문에 뷰티플수프가 기어가는 원인이 된다. 본인의 테스트에 의하면 이런 일은 파이썬 2.x 버전대에서만 일어나고, 러시아나 중국어 인코딩을 사용한 문서에 아주 많이 발생했다. 이런 일이 일어나면, cchardet을 설치하거나, 스크립트에 Python 3를 사용하여 문제를 해결할 수 있다. 혹시 문서의 인코딩을 안다면, 그 인코딩을 BeautifulSoup 구성자에 from_encoding로 건네면, 인코딩 탐지를 완전히 건너뛴다.

+

문서의 일부만 해석하기는 문서를 해석하는 시간을 많이 절약해 주지는 못하겠지만, 메모리가 절약되고, 문서를 훨씬 더 빨리 탐색할 수 있을 것이다.

+
+
+
+

뷰티플수프 3

+

뷰티플수프 3는 이전의 구형으로서, 더 이상 활발하게 개발되지 않는다. 현재는 주요 리눅스 배포본에 모두 함께 꾸려넣어진다:

+

$ apt-get install python-beautifulsoup

+

또 PyPi를 통하여 BeautifulSoup 출간되어 있다:

+

$ easy_install BeautifulSoup

+

$ pip install BeautifulSoup

+

+또한 뷰티플수프 3.2.0 압축파일을 내려받을 수 있다.

+

easy_install beautifulsoup이나 easy_install BeautifulSoup을 실행했는데, 코드가 작동하지 않으면, 실수로 뷰티플수프 3을 설치한 것이다. easy_install beautifulsoup4을 실행할 필요가 있다.

+

뷰티플수프 3 문서는 온라인에 보관되어 있다. 모국어가 중국어라면, 뷰티플수프 3 문서 중국어 번역본을 보는 것이 더 쉬울 것이다. 그 다음에 이 문서를 읽고 뷰티플수프 4에서 변한 것들을 알아보자.

+
+

BS4로 코드 이식하기

+

+뷰티플수프 3용 코드는 하나만 살짝 바꾸면 뷰티플수프 4에도 작동한다. 꾸러미 이름을 BeautifulSoup에서 bs4로 바꾸기만 하면 된다. 그래서 다음은:

+
from BeautifulSoup import BeautifulSoup
+
+
+

다음과 같이 된다:

+
from bs4 import BeautifulSoup
+
+
+
    +
  • “No module named BeautifulSoup”와 같이 ImportError를 만난다면, 문제는 뷰티플수프 3 코드를 시도하는데 뷰티플수프 4만 설치되어 있기 때문이다.
  • +
  • “No module named bs4”와 같은 ImportError를 만난다면, 문제는 뷰티플수프 4 코드를 시도하는데 뷰티플수프 3만 설치되어 있기 때문이다.
  • +
+

BS4는 BS3와 대부분 하위 호환성이 있으므로, 대부분의 메쏘드는 폐기되고 PEP 8을 준수하기 위해 새로운 이름이 주어졌다. 이름바꾸기와 변화가 많이 있지만, 그 중에 몇 가지는 하위 호환성이 깨진다.

+

다음은 BS3 코드를 변환해 BS4에 이식하고자 할 때 알아야 할 것들이다:

+
+

해석기가 필요해

+

뷰티플수프 3는 파이썬의 SGMLParser해석기를 사용했다. 이 모듈은 파이썬 3.0에서 제거되었다. 뷰티플수프 4는 기본으로 html.parser을 사용하지만, 대신에 lxml이나 html5lib을 설치해 사용할 수있다. 비교는 해석기 설치하기를 참조하자.

+

+html.parserSGMLParser와 같은 해석기가 아니기 때문에, 무효한 조판을 다르게 취급한다. 보통 “차이점은” 무효한 조판을 다룰 경우 html.parser가 해석기가 충돌을 일으키는 것이다. 이런 경우, 또다른 해석기를 설치할 필요가 있다. 그러나 html.parserSGMLParser와는 다른 해석 트리를 생성한다. 이런 일이 일어나면, BS3 코드를 업데이트하여 새로운 트리를 다루도록 해야 할 필요가 있다.

+
+
+

메쏘드 이름

+
    +
  • renderContents -> encode_contents
  • +
  • replaceWith -> replace_with
  • +
  • replaceWithChildren -> unwrap
  • +
  • findAll -> find_all
  • +
  • findAllNext -> find_all_next
  • +
  • findAllPrevious -> find_all_previous
  • +
  • findNext -> find_next
  • +
  • findNextSibling -> find_next_sibling
  • +
  • findNextSiblings -> find_next_siblings
  • +
  • findParent -> find_parent
  • +
  • findParents -> find_parents
  • +
  • findPrevious -> find_previous
  • +
  • findPreviousSibling -> find_previous_sibling
  • +
  • findPreviousSiblings -> find_previous_siblings
  • +
  • nextSibling -> next_sibling
  • +
  • previousSibling -> previous_sibling
  • +
+

뷰티플수프 구성자에 건네는 인자들 중에서 같은 이유로 이름이 바뀌었다:

+
    +
  • BeautifulSoup(parseOnlyThese=...) -> BeautifulSoup(parse_only=...)
  • +
  • BeautifulSoup(fromEncoding=...) -> BeautifulSoup(from_encoding=...)
  • +
+

파이썬 3와의 호환을 위해 한 가지 메쏘드 이름을 바꾸었다:

+
    +
  • Tag.has_key() -> Tag.has_attr()
  • +
+

더 정확한 용어를 위해 한 속성의 이름을 바꾸었다:

+
    +
  • Tag.isSelfClosing -> Tag.is_empty_element
  • +
+

파이썬에서 특별한 의미가 있는 단어들을 피해서 세 가지 속성의 이름을 바꾸었다. 다른 것들과 다르게 이 변경사항은 하위 호환이 되지 않는다. 이런 속성을 BS3에 사용하면, BS4로 이식할 때 코드가 깨질 것이다.

+
    +
  • UnicodeDammit.unicode -> UnicodeDammit.unicode_markup
  • +
  • Tag.next -> Tag.next_element
  • +
  • Tag.previous -> Tag.previous_element
  • +
+
+
+

발생자

+

발생자에 PEP 8을-준수하는 이름을 부여하고, 특성으로 변환하였다:

+
    +
  • childGenerator() -> children
  • +
  • nextGenerator() -> next_elements
  • +
  • nextSiblingGenerator() -> next_siblings
  • +
  • previousGenerator() -> previous_elements
  • +
  • previousSiblingGenerator() -> previous_siblings
  • +
  • recursiveChildGenerator() -> descendants
  • +
  • parentGenerator() -> parents
  • +
+

그래서 다음과 같이 하는 대신에:

+
for parent in tag.parentGenerator():
+    ...
+
+
+

다음과 같이 작성할 수 있다:

+
for parent in tag.parents:
+    ...
+
+
+

(그러나 구형 코드도 여전히 작동한다.)

+

어떤 발생자들은 일이 끝난후 None을 돌려주곤 했다. 그것은 버그였다. 이제 발생자는 그냥 멈춘다.

+

두 가지 발생자가 새로 추가되었는데, .strings와 .stripped_strings가 그것이다. .strings는 NavigableString 객체를 산출하고, .stripped_strings는 공백이 제거된 파이썬 문자열을 산출한다.

+
+
+

XML

+

이제 XML 해석을 위한 BeautifulStoneSoup 클래스는 더 이상 없다. XML을 해석하려면“xml”을 두번째 인자로 BeautifulSoup 구성자에 건네야 한다. 같은 이유로, BeautifulSoup 구성자는 더 이상 isHTML 인자를 인지하지 못한다.

+

뷰티플수프의 빈-원소 XML 태그 처리 방식이 개선되었다. 전에는 XML을 해석할 때 명시적으로 어느 태그가 빈-원소 태그로 간주되는지 지정해야 했었다. 구성자에 selfClosingTags 인자를 보내 봐야 더 이상 인지하지 못한다. 대신에, +뷰티플수프는 빈 태그를 빈-원소 태그로 간주한다. 빈-원소 태그에 자손을 하나 추가하면, 더 이상 빈-원소 태그가 아니다.

+
+
+

개체

+

+HTML이나 XML 개체가 들어 오면 언제나 그에 상응하는 유니코드 문자로 변환된다. 뷰티플수프 3는 개체들을 다루기 위한 방법이 중첩적으로 많았다. 이제 중복이 제거되었다. BeautifulSoup 구성자는 더 이상 +smartQuotesTo이나 convertEntities 인자를 인지하지 않는다. (Unicode, Dammit은 여전히 smart_quotes_to가 있지만, 그의 기본값은 이제 지능형 따옴표를 유니코드로 변환하는 것이다.) + +HTML_ENTITIES, +XML_ENTITIES, 그리고 XHTML_ENTITIES 상수는 제거되었다. 왜냐하면 이제 더 이상 존재하지 않는 특징을 구성하기 때문이다 (유니코드 문자열을 제대로 모두 변환하지 못했다).

+

유니코드 문자들을 다시 출력시에 HTML 개체로 변환하고 싶다면, 그것들을 UTF-8 문자로 변환하기 보다, 출력 포맷터를 사용할 필요가 있다.

+
+
+

기타

+

Tag.string은 이제 재귀적으로 작동한다. 태그 A에 태그 B만 달랑 있고 다른 것이 없다면, A.string은 B.string과 똑같다. (이전에서는 None이었다.)

+

다중-값 속성class와 같이 문자열이 아니라 문자열 리스트를 그 값으로 가진다. 이 사실은 CSS 클래스로 검색하는 방식에 영향을 미친다.

+

+find* 메쏘드에 text 그리고 name 같은 태그-종속적 인자를 모두 건네면, 뷰티플수프는 태그-종속적 기준에 부합하고 그 태그의 Tag.stringtext 값에 부합하는 태그들을 탐색한다. 문자열 자체는 찾지 않는다. 이전에, 뷰티플수프는 태그-종속적 인자는 무시하고 문자열을 찾았다.

+

+BeautifulSoup 구성자는 더 이상 markupMassage 인자를 인지하지 않는다. 이제 조판을 제대로 처리하는 일은 해석기의 책임이다..

+

ICantBelieveItsBeautifulSoup 그리고 BeautifulSOAP와 같이 거의-사용되지 않는 해석기 클래스는 제거되었다. 이제 애매모호한 조판을 처리하는 방법은 해석기가 결정한다.

+

+prettify() 메쏘드는 이제, bytestring이 아니라 유니코드 문자열을 돌려준다.

+
+
+
+ + +
+
+
+
+
+

목차

+ + +

이 페이지

+ + + +
+
+
+
+ + + + + + + -- cgit v1.2.3