BeautifulSoupオブジェクトについての備忘録


Introduction

きっかけ

最近クローラーの開発を始めたのですが、いかんせんwebスクレピングに関して不勉強すぎたため、BeautifulSoup4の公式ドキュメント(日本語訳)とにらめっこしていました。BeautifulSoupと検索すれば大体の使用法はヒットします。でも実用するには、断片的過ぎて応用できませんでした…orz 実際にクロールしたいサイトってサンプルHTMLと違って、ものすごい深いんだもの・・・
だから今回のにらめっこで得た知見や、忘れたくないことなどをなるべくわかりやすく備忘録としてまとめようと考えました。

要約

BeautifulSoupって、selectとかfindで要素を指定できるけどBeautifulSoupのルールで抽出した要素のオブジェクトは定まるから、bs4.element.tagオブジェクトか否かを判断する必要があるよね!オブジェクトごとに使用法は決まってるから、どんな指定の仕方をしたら、どんなオブジェクトになるかわかると便利よね!って内容です。

スクレイピングって合法なのか?

以前Webスクレイピングに関して書いた記事をそのまま以下に記しておきます。

Cf.)Webスクレイピングに関しての注意点
参照サイトにも記載されていたので、まるまる引用させてもらいます。
岡崎市立中央図書館事件(Librahack事件) - Wikipedia
Webスクレイピングの注意事項一覧

「岡崎市立中央図書館事件(Librahack事件) - Wikipedia」に関してはシャレにならないので不安しかないです。

BeautifulSoup4に関する参考情報

公式ドキュメントの日本語訳版
本日のSempleはほとんどの時間をこのページと開発ページとを往復していただけです。

スクレイピング - Beautiful Soup の DOM ツリーのアクセス方法 まとめ
公式ドキュメントの例がわからないときは、このページを見ました。

PythonとBeautiful Soupでスクレイピング
BeautifulSoup4の使い方で迷ったときはこちらを参考にしました。

スクレイピングに関する参考情報

またスクレイピングを実装しているときにお世話になりすぎた記事も以下に載せておきます。誠にありがとうございました。

入門系記事にはない、実践/現場のPythonスクレイピング流儀【2019年最新】
スクレイピングの技術に関して事細かに説明してくれていて、わかりやすかったです。

自己紹介

自己紹介ページ

環境情報
Python 3.6.5 |Anaconda, Inc.|
Windows10

対象者

・Python3系を使っているユーザー(Anacondaでインストール)
・Webスクレイピングの初心者に毛が生えたレベル(筆者もこのレベル)
・BeautifulSoupの文法構造がよくわからない方
・1を聞いて10を理解できるエンジニア
Windowsの人

非対象者

・Python2系を使っているユーザー
・Webスクレイピングが超お得意な人
・Seleniumだけで事足りる人
・説明下手な筆者を攻撃しようとするエンジニア
Macの人

Cf.)Python2系とかPython3系の意味が分からない人へ!
Pythonには2系と3系があって、最近始めた人ならほとんどPython3系だと思います。一応バージョンを確認する方法を記載しておきます。
コマンドプロンプトでPythonのインタラクティブ(対話)モードを起動すればバージョン情報が表示されます。

$ Python
>Python 3.6.5 |Anaconda, Inc.| (default, Mar 29 2018, 13:32:41) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

インタラクティブモードを辞めるにはexit()を入力しましょう!

Let's Start

BeautifulSoupを使えば、HTMLから要素を抽出することができます。
簡単な使い方はこちらのページを参考にいくつか紹介いたします。
今回の記事も基本的に、Jupyter Notebook上での実装です。

from bs4 import BeautifulSoup


html = """
<html>
<head>
    <title>レストランメニュー</title>
</head>
<body>
    <h1>本日の<b>おすすめ</b>メニュー</h1>
    <ul class="restaurant_menu">
        <li>あじの塩焼き</li>
        <li>海鮮<i>チャーハン</i></li>
    </ul>
    <p>メニューは日替わりです。</p>
    <hr>
    <p>サンプル画像</p>
    <p>
        <img src="sample.png" alt="サンプル画像"></p>
    <p>お気に入りに<a href="sample.com">Web site</a>を登録してください。</p>
</body>
</html>
"""

soup = BeautifulSoup(html, 'lxml')

さてここでやっと今回の本題です。
BeautifulSoupオブジェクトに関することです。
HTMLファイルを書いたことがある人なら大体わかると思いますが、BeautifulSoupでは以下のようなオブジェクトに分けて定義しているそうです。記述すると以下のようになります。

bs4.element.Tag
#コード内に含まれる<>で囲まれているタグ部分です。
#基本的な文法を使うことができるBeautifulSoupといえばのオブジェクトです。

bs4.element.NavigatableString
#コード内に含まれる文字列などのTagで囲まれた部分です。

bs4.element.Comment
#コード内に含まれるコメントアウトされた部分です。

bs4.BeautifulSoup
#上記の3つの上位概念という感じでしょうか? 

BeautifulSoupオブジェクトというものがに関しては、いまいち意味が分からなかったので、省略します。紹介しても、あまり実用的でない気もします… 自分が使わないだけ?
もっと詳しく知りたい方はこちら

(日本語版)
(英語版)

オブジェクトの種類はtype()で調べることがわかります。

print(soup.find("title").string)
#'レストランメニュー'

print(type(soup.find("title").string))
#bs4.element.NavigatableString

オブジェクトに関して一つ一つ見ていこうと思います。
といっても実用的にはTagオブジェクトくらいしか説明することないです

Tagオブジェクト

Tagオブジェクトで要素を抽出するための方法には「select()」,「find()」,「find_all()」などがあります。
「select()」はCSSセレクタで指定した値を取得できる。
「find()」はタグで指定した値を前から取得できる。
「find_all()」はタグで指定した値すべてをリスト形式で取得できる。

cf) CSSセレクタの使い方に関しては、こちらの記事→Python Webスクレイピング テクニック集「取得できない値は無い」JavaScript対応@追記あり6/12をご覧ください。

早速3つの手法で「あじの塩焼き」を取得してみましょう。

print(soup.select("ul.restaurant_menu > li")[0])
print(soup.find("li"))
print(soup.find_all("li")[0])

#3つとも <li>あじの塩焼き</li> が取得できます。

それぞれのオブジェクトのタイプは、

print(type(soup.select("ul.restaurant_menu > li")[0]))
#<class 'bs4.element.Tag'>

print(type(soup.find("li")))
#<class 'bs4.element.Tag'>

print(type(soup.find_all("li")[0]))
#<class 'bs4.element.Tag'>

「select()」と「find_all()」は要素が複数あると、リスト形式で出力します。

print(soup.select("ul.restaurant_menu > li"))
#[<li>あじの塩焼き</li>, <li>海鮮<i>チャーハン</i></li>]
print(type(soup.select("ul.restaurant_menu > li")))
#<class 'list'>

print(soup.find_all("li"))
#[<li>あじの塩焼き</li>, <li>海鮮<i>チャーハン</i></li>]
print(type(soup.find_all("li"))
#<class 'list'>

どの手法でも「bs4.element.Tag」で出力できたので、さらにfind()やfind_all()で深堀りしていくことができます。
リスト形式で出力されると、深堀り出来ないので、注意が必要です。

これだけでは解決しない場合もあると思いますので、質問いただければさらにいろいろな場合で追記していこうと思います!
実際のスクレイピングとサンプルコードってだいぶ違いますよね!

NavigatableStringオブジェクト

bs4.element.tagで囲まれている文字列をしめすオブジェクトです。

print(soup.select("ul.restaurant_menu > li")[0].string)
print(soup.find("li").string)
print(soup.find_all("li")[0].string)

#3つとも あじの塩焼き が取得できます。

print(type(soup.select("ul.restaurant_menu > li")[0].string))
print(type(soup.find("li").string))
print(type(soup.find_all("li")[0].string))
#<class 'bs4.element.NavigableString'>

もちろん文字列をさらに深堀りすることはできないので、もう「find()」などを使ってもエラーが出るだけです。

Commentオブジェクト

コメントオブジェクトに関しては、コメントアウトされている部分なので、スクレイピングという観点では特に価値はないのかなと思います。

ドキュメントでもあまり触れられていません。1

さいごに…

ほとんどの人2にはこの記事は意味をなさない気もするのですが、あくまで自分の「備忘録」として書いたのでまぁ卵とか投げないでください。3
自分には皆目見当もつかず、一日つぶしてしまったので,同じように頭を抱えた誰かのためにと思って書きました。参考になれば幸いです。

その他参照サイト

https://qiita.com/itkr/items/513318a9b5b92bd56185
https://www.dataquest.io/blog/web-scraping-tutorial-python/
http://www.seleniumqref.com/api/python/window_get/Python_page_source.html
https://gammasoft.jp/blog/use-diffence-str-and-repr-python/


  1. ドキュメントで触れられていないのだから、もちろん日本語訳版でも触れられていない。 

  2. ドキュメント読める人にはドキュメントで事足りるし、初心者の人にはほかに良い記事がある。 

  3. 卵に限らずものを投げられるのは嫌である。