【Python】BeautifulSoupの使い方・基本メソッド一覧|スクレイピング

Pythonを使ったWebスクレイピングの比較的メジャーなライブラリBeautifulSoupのメソッドを一挙紹介します。

このページを読めばBeautifulSoupのほとんどの動作、メソッドを確認することができます。

以下の目次は展開でき、逆引きリファレンスの形式になっていますので、調べたい操作がある方は、気になる箇所へすぐにジャンプできます。

BeautifulSoupとは

一言で言うと、HTMLをパースするPythonのライブラリです。

スクレイピングという処理は、HTMLの取得と解析の二段構成です。

僕はHTMLの取得にはrequestsというモジュールを使うことが多いです。
一応標準のライブラリでもあるにはあるんですが、Pythonのhttpアクセスのディファクトスタンダードはrequestsかなと個人的には思っています。

BeautifulSoupは解析の部分を担当するライブラリですね。
主にスクレイピングという作業では目的のHTMLタグや、テキストの抜き出しに使うことが多いです。

とりあえずこれを使えばHTML色々いじれるんだー、便利なんだーって思ってもらえれば。

BeautifulSoupインストール

  • bs4(BeautifulSoup)
  • lxml

Anacondaでパッケージを管理していれば基本的にどちらも初期状態から入っています。

必要なHTMLを抜き出すためにbs4というモジュールを使います。Python用のHTMLパーサもいくつか存在しますが、bs4とlxmlの組み合わせは割とメジャーなのでこれらを使っていきます。

lxmlというのはパーサと言って、解析方法の一つです。Python標準の"html.parser"というパーサよりも高速で動作可能なのが特徴です。

ない場合はどちらもpipでインストール可能です。適宜インストールしてください。

BeautifulSoupの使い方

こんな感じでインポートして使います。

第一引数のHTML_TEXTの部分は取ってきたHTMLをそのまま渡して、第二引数はパーサを指定します。

パーサっていうのは、"読み取り方式" と思ってもらえればOKです。省略可能な引数で、省略した場合は、標準の'html.parser'が使用されます。

つまり例に挙げている'html.parser'の部分は全く書く必要がありません。

僕は、より実践的にこんな形で書くことが多いです。

インポートの時点でas bs4 として、今後はBeautifulSoupなんてながったらしく書かねーからな、という宣言を行います。

何回も書く可能性がある時に長いメソッド名書いているとそれだけでスペルミスとか、余計な失敗するので・・・。

bs4に渡している res.content というのはrequestsで取ってきたHTMLのbytes形式のデータです。

res.text を渡してももちろん動作するのですが、 res.content を渡した方が「文字化け」する可能性を減らせますのでこちらで書く癖をつけましょう。

上で少し説明しましたが、'lxml'というのはCで書かれたパース方式。標準の"html.parser"よりも早いです。ただ、HTMLの状況によってはパース出来ないことがたまにあります。

基本は'lxml'を使いつつ、なんか知らんが上手いこと行かない時は'html.parser'を使っています。

BeautifulSoupオブジェクトの操作

上の例のコードで挙げたsoup変数にはBeautifulSoupのオブジェクトが格納されています。

このsoupオブジェクトを操作して、htmlの中身を取得したり、書き換えたり出来ます。

タグ名で検索

最初のaタグを取得

どちらも同じ動作をします。html上で”最初に出てくるaタグ”を返します。

最初のaタグに囲まれたテキストのみを取得

.textを付けるだけですね。<a></a>に囲まれた文字列だけを返します。

.stringでも似たような動作になりますが、状況によってはNoneが返ってくるパターンがあります。以下の記事で両者の明確な挙動の差を解説しています。

全てのaタグを取得

どちらも同じ動作です。find_allは単純に省略可能です。

全てのaタグのテキストのみを取得

soup使い始めて最初の頃、やりがちなんですが、.findの時とは違い、返ってくるのはリスト型(bs4.element.ResultSet)ですので、そのままテキストだけを抜き出すことはできません。

JQueryとか使い慣れてると何となく「いけんじゃねーかな」と思っちゃうんですよね。無理です!

これですべてのaタグのテキストのみをリスト型で取得できます。

リスト内包表記というPythonの特徴的な書き方ですが、簡単に説明すると一行でfor文を書けて、結果をリスト型で返します。

上記例ではtag変数がリスト内全ての要素にアクセスして tag.text でtag内のテキストのみを抜き出しています。

指定タグ内の属性値を取得

例えば、多いパターンはaタグ内のhref属性を取得したいパターンなんかですね。

URLを集めたいスクレイピングなんかでよく使います。

上記はgetメソッドを使うパターンですが、下記はブラケットでアクセスするパターンです。

基本的にこれらはget()でアクセスした時と同じ結果を返します。

何が違うかというと、ブラケットアクセスの場合、その属性がタグ内に存在しなかった場合KeyErrorの例外が発生します。

対してget()メソッドの場合は "none" が返却され例外は発生しません。

属性名で検索

最初にクラス属性に一致するタグを取得

class= ではなく class_= です。

Pythonではclassは予約語(別の意味のある)単語なのでそれと区別するためアンダースコアが必要です。

クラス属性に一致するタグを全て取得

どちらも同じ動作。find_allは単純に省略可能です。

後から見た時の可読性を考慮すれば書いた方がいいかもしれません。
find_allなんて名前、何してるか一発で分かりますしね。

僕は書きません。

id属性に一致するタグを取得

id はそのまま id なんですね。ちなみにHTML構文の基本と矛盾しますが、

こんな書き方も通ります。どうせ id を find_all しても一個しか出てこないんですけどね。この場合返ってくるのはリスト型です。

その他の属性に一致するタグを取得する

classとidはよく使うのでもちろんあって当然ですが、その他の属性一致も、.find() や .find_al() のメソッドで検索取得することが可能です。

その他、各種属性検索もfind系のメソッドで取得可能です。

タグ内のテキストで検索して取得

テキストでの検索も可能です。注意点としては検索条件に一致するテキストを持っているタグが返ってくるのではなく、テキスト本体が一致して返ってきます。

以下で詳細を解説します。

テキストを検索して完全一致する文字列を取得

基本的にBeautifulSoupに実装されているテキスト検索は完全一致がベースです。そのままでは正規表現やワイルドカード的なものは使えません。

上位タグが返却されるわけではなく、文字列本体が返却されます。
つまり、上記の例の場合、返ってくる値は'search_text'自体となりますので、このままではすでにメソッドに引数として渡してる値の取得になり、使い道はありません。

しかし、返却される形式が普通の文字列型(str)ではなく"NavigableString"ですので、BeautifulSoupの各種メソッドチェーンが適用可能です。

などが実践的な使い方になります。

ちなみにこんな書き方でも同様の動作をします。

両者の引数としての違いは調べたけどわかりませんでした。
挙動的には違いは全く感じないですね。

アトリビュートとしては明確な違いがありますので、以下の記事を参考にしてみてください。

テキストを正規表現で検索して文字列を取得

上記の文字列検索では”完全一致”が前提でしたが、部分一致で取得する方法もあります。正規表現モジュール「re」を使います。

正規表現ですので、あらゆる条件一致で文字列を探せます。

また似たような動作は後述するCSSセレクタ形式で取得できる.select()メソッドでも実現可能です。

複合的なタグ名または属性名で検索

複数の条件を組み合わせてタグを取得したい時があります。

全て同じ動作です。

attrsで指定するときはDict型(辞書型)で渡します。この場合classのアンダースコアはいりません。

CSSセレクタ型の検索

ここまで読んできて、JQueryを普段使い慣れている人からしたら、「BeautifulSoupめんどくさくね?」って思っているでしょう?

そんなあなたに、

こんな感じでCSSセレクタも使えます。返却型はリスト型になります。

その他のタグの取得検索方法

上記メソッドの他にもいくつかタグやタグ内テキスト、属性などを絞り込んで取得する方法が存在しますので紹介します。

タグ名を返却

ちなみにこの書き方だと当然 "a" がstring型で返却されてきますので、ほとんど意味がありませんね。メソッド書いてる時点で"a"だと確定しますからね。

基本的には取得自体より、比較とか代入が目的のアトリビュートです。

直下子要素をリスト形式で返却

そのタグ内の子要素をそれぞれ、要素として格納したリストを返却します。

直下子要素をリストイテレータ形式で返却

根本の動作はcontentsと一緒なんですが、返却の形式がリストイテレータという、そのままでは参照できない形で返ってきます。
使いどころはイマイチ僕も分かりません。

子孫要素をジェネレータ形式で再帰的に返却

DOMツリーの最深部に行きつくまで各要素を再帰的に取得し続けます。

子要素に含まれる文字列全てをジェネレータ形式で返却

文字列取得には他にも".string"や".text"など存在します。

両者の明確な違いについての記事も書いていますので、参考にしてみてください。

親要素全体を取得

先祖要素を再帰的に取得

"parent"に対して、複数形で再帰取得という感じですね。
BeautifulSoupはこのメソッドが単数形、複数形のそれぞれ存在しているパターンが多いので、気に留めておいてください。

最初のa要素の次の兄弟要素を取得

最初のa要素の前の兄弟要素を取得

兄弟要素をジェネレータで一気に取得

上記で紹介したnext_sibling、previous_sibling はそれぞれ、"次"と"前"の兄弟要素を「ひとつだけ」取得する方法ですが、以下のようにすると、"以降の兄弟要素" と "以前の兄弟要素" を全て取得できるようになります

ちなみに返却の形式はジェネレータ形式です。
parent と parents みたいな感じですね。

最初のa要素の次の兄弟要素を取得(要素内も順次取得)

要素の書き換え

BeautiflSoupという名前だけあって、きれいにHTMLを整形したり、置き換えたりすることもできます。もしかしたら本来はこっちの使い方がメインなのかもしれませんが・・・。

純粋なスクレイピングを行うだけならあまり使う機会のないメソッドが多いかもしれませんが、機械的な引用転載なんかで活躍したりしますね。

要素の書き換えの基本事項

基本的には以下で紹介するメソッドは新規のsoupオブジェクトを返すワケではなく、現状のsoupオブジェクトを改変するものと思ってください。

Rubyでいう「破壊的メソッド」なイメージです。

書き換えだけではなく、以降に紹介する削除系のメソッドも同様に元本のオブジェクトを改変します。

また、オブジェクトが格納された変数などを操作して得られたオブジェクトは参照渡しとなります。
どういうことか、以下に例を挙げます。

後に紹介しますが、decompose()はタグを削除するメソッドです。

この場合、変数"a_tags"に格納されたaタグが全て削除されるのは理解できると思いますが、実は、aタグの削除は元の"soup"という変数にも及びます

意外と見落としがちで、挙動が変なことに後から気づくケースが多いので、念頭に入れてください。

最初のdivタグをpタグに変更

divタグをpタグに変更します。この置き換えでは文字列で直接指定でき、new_tag()メソッドでの指定は必要ありません。

最初のpタグのタグ内テキストをreplace_textに変更

タグ内テキストを変更可能です。
ちなみに以下の方法では書き換えが失敗します。

stringはbs4の独自クラス(bs4.element.NavigableString)textアトリビュートはpython標準クラスのstrクラスであることに起因します。

最初のp要素内にappend_textを追加

最初のa要素のhref属性を変更

属性には[]ブラケットでアクセスできます。
その場合、オブジェクトtypeはbs4.element.Tagである必要があります。

最初のpタグを新規divタグで囲う

指定した要素を覆うように新規タグを配置します。

soup.new_tag()メソッドでタグを作る必要があるのがミソです。

要素の削除

soupオブジェクトから要素を削除することが可能です。

要素の削除の基本事項

基本的には以下で紹介するメソッドは新規のsoupオブジェクトを返すワケではなく、現状のsoupオブジェクトを改変するものと思ってください。

Rubyでいう「破壊的メソッド」なイメージです。

削除だけではなく、上で紹介している書き換え系のメソッドも同様に元本のオブジェクトを改変します。

また、オブジェクトが格納された変数などを操作して得られたオブジェクトは参照渡しとなります。
どういうことか、以下に例を挙げます。

後に紹介しますが、decompose()はタグを削除するメソッドです。

この例の場合、変数"a_tags"に格納されたaタグが全て削除されるのはもちろん理解できると思いますが、実は、aタグの削除はaタグ取り出し元の"soup"という変数にも及びます

意外と見落としがちで、挙動が変なことに後から気づくケースが多いので、念頭に入れておいてください。

最初のdivタグからクラス属性を削除

属性を削除したい場合はdelステートメントを使います。

最初のスクリプトタグを削除

decompose()はタグを丸ごと取り除きます。

string 系のメソッドで取得した文字列 (bs4.element.NavigableString)に対しては decompose は効きません。例外が発生します。

NavigableStringクラスを削除したい場合は以下で紹介するextract()を利用する必要があります。

最初のpタグを取り出し、かつ、元の変数からその要素を削除

decompose()と何が違うのかというと、extract()は結果を返すという点が異なります。

pという変数には最初のpタグが代入された状態になり、元のsoupオブジェクトからはそのタグが削除されます

jQueryとか使ってる人なら「pop」がイメージしやすいですかね。

最初のpタグの中身を削除する

decompose()との違いは中身だけを消去する点です。
つまり、この場合<p>~</p>の枠は残ります。

最初のaタグから中身を残してタグだけ削除

指定した要素のタグだけ取り除きます。
この場合、aタグの中の文字列などの子要素は存続します。

親要素が消えるのではなく、指定されたそのタグ自身が消えることに注意。
jQuery使っていると少し違和感あるかもしれません。

消えたタグは bs4.element.Tagとして 返却されます。イメージとしてはextract()の中身消えない版です。

BeautifulSoupでXMLをパースする

BeautifulSoupはHTMLのパースというイメージがあるかと思いますが、実はXMLの解析にも使えます。

pythonのXML解析用のライブラリには他にもElementTreeなどがありますが、BeautifulSoupを使い慣れているならこちらの方が直感的に操作しやすいかもしれません。

メソッドなどの取り扱いはHTMLをパースする時とほとんど同じです。

ただ、XMLのパース時は少しだけ、オブジェクトの作り方が異なります。

基本的にはパーサに'lxml-xml'を指定するのがいいと思います。

'lxml'のままでも動くには動きますが、キャメルバックのタグが全て小文字になったり、少し挙動が変化します。

特に理由がないのであれば専用のパーサを使っておくのが無難です。

BeautifulSoupを用いたXMLの解析は以下の記事で、例として、AmazonのMWSから取得できるXMLデータを用いて詳細に解説しています。

BeautifulSoupでXMLドキュメントを作成する

HTMLを削除・改変・作成できることなどから推測できるかもしれませんが、実はXML形式のドキュメントの作成もBeautifulSoupによって行うことができます。

リクエストにXML形式のドキュメントをpostする必要があった時にこの方法で作成してみました。

詳しくはこちらの記事で解説しています。

BeautifulSoup 総括

スクレイピングでは主に要素の取得が目的になることが多いかと思いますが、実際のBeautifulSoupはjQueryライクな、総合的にHTMLの加工をすることを得意としています。

CSSセレクタ型の操作にも対応していて、Pythonによるスクレイピングでは手放せない便利なライブラリです。

¥4,070 (2019/10/15 22:51:03時点 Amazon調べ-詳細)