[Python]BeautifulSoupの使い方

Python

BeautifulSoup」はPythonでスクレイピングを行う際の定番ライブラリです。

このページでは、BeautifulSoupのインストール、インポート、スープオブジェクトを作成してから、データの抽出方法を見ていきます。

参考ページ
Beautiful Soup Documentation

使用環境
WSL2 Ubuntu - 20.04
Python 3.10.0
beautifulsoup4 4.10.0

 Androidアプリを作成しました。
 感情用のメモ帳です。

スポンサーリンク
スポンサーリンク

インストール

外部ライブラリのためインストールが必要です。

バージョンには3と4がありますが、3はサポートが終了しているため、4の方をインストール。

pipから行います。

pip install beautifulsoup4

スープオブジェクトの作成

まずは、ライブラリのインポート。

from bs4 import BeautifulSoup

bs4」はパッケージの名前。
BeautifulSoup」はBとSが大文字なので注意。

つぎにスープオブジェクトを作成します。

BeautifulSoup(markup, 'html.parser')

最初の引数には解析したい任意のhtml、2つ目の引数のパーサー(解析)には「'html.parser'」を指定します。

任意のhtmlは、実際には「urllib.request」や「Requests」などのライブラリを使用し、欲しいページをインターネットから取得してくると思いますが、今回は用意したこちら↓を使います。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>レッツスクレイピング</title>
  </head>
  <body>
    <div class="main">
      <h1>見出しレベル1</h1>
      <p>テキスト1</p>
      <div class="link">
        <img src="sample.jpg">
        <a href="http://example.com/1">リンク1</a>
      </div>
      <h2 id="topic1">見出しレベル2-1</h2>
      <p>テキスト2</p>
      <h2 id="topic2">見出しレベル2-2</h2>
      <div class="sub">
        <h3 id="sub-topic">見出しレベル3</h3>
        <p><span class="bold">テキスト3</span></p>
        <div class="link">
          <img src="sample.png">
          <a href="http://example.com/2">リンク2</a>
        </div>
      </div>
    <div>
  </body>
</html>

ローカルにあるこのファイルを読み込みます。

ここまでをやってみます。

Python 3.10.0 (default, Oct 12 2021, 16:02:08) [GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from bs4 import BeautifulSoup
>>> with open('index.html', encoding='utf-8') as f:
...     html = f.read()
... 
>>> soup = BeautifulSoup(html, 'html.parser')

変数「soup」にスープオブジェクトが入りました。

これを使って欲しい情報を取り出します。


ファイルを読み込まなくても、htmlの中身をコピペし、文字列として定義することでも可能です。

>>> from bs4 import BeautifulSoup
>>> html = '''
... <!DOCTYPE html>
... <html lang="ja">
...   <head>
... 略
... </html>
... '''
>>> soup = BeautifulSoup(html, 'html.parser')

補足情報

lxml

スープオブジェクト作成時には「'html.parser'」を渡していました。
これはPythonの標準ライブラリ同梱のパーサーです。

しかし、解析速度が気になる場合や、XMLを扱うときには、外部ライブラリの「lxml」をインストールします。

pip install lxml

そしてスープオブジェクト作成時、パーサーとして指定します。

HTMLの解析
BeautifulSoup(markup, 'lxml')

XMLの解析
BeautifulSoup(markup, 'xml')

Requestsでhtmlを取得する

インストール

pip install requests

使用例

import bs4
import requests


url = 'http://example.com/'
html = requests.get(url)
soup = bs4.BeautifulSoup(html.content, 'html.parser')

htmlに入っているのは、Responseオブジェクト。
スープオブジェクト作成時、このhtmlに「.content」をつけて渡します。

データの検索

データの絞り込みには、メソッド「find」もしくは「find_all」を使うか、「select」メソッドを使います。

findとfind_all

タグ名で検索

find('タグ名')

find_all('タグ名')

findは、html内で最初に見つけたタグの要素を返します。

find_allは見つかったタグをすべてリストにして返します。

試しにpタグを渡してみましょう。

>>> soup.find('p')
<p>テキスト1</p>
>>> soup.find_all('p')
[<p>テキスト1</p>, <p>テキスト2</p>, <p><span class="bold">テキスト3</span></p>]

タグの検索に関しては、別の書き方をして、同じ結果を得ることができます。
ショートカット的な書き方。

# soup.find('p')と同じ
>>> soup.p
<p>テキスト1</p>
# soup.find_all('p')と同じ
>>> soup('p')
[<p>テキスト1</p>, <p>テキスト2</p>, <p><span class="bold">テキスト3</span></p>]

find_allのショートカット的な書き方は、タグ以外の検索においても、使うことができます。

それぞれfindとfind_allの結果と同じものが返ってきていますよね。

いちおう真偽値でも判定しておきます。

>>> type(soup.find('p'))
<class 'bs4.element.Tag'>
>>> soup.find('p') == soup.p
True
>>> type(soup.find_all('p'))
<class 'bs4.element.ResultSet'>
>>> soup.find_all('p') == soup('p')
True

ドットをつけた書き方は、さらにその内側でタグが入れ子になっているなら、同じようにドットをつけて中へ入っていくことができます。

>>> soup.find_all('p')[2]
<p><span class="bold">テキスト3</span></p>
>>> soup.find_all('p')[2].span
<span class="bold">テキスト3</span>

属性とその値で絞り込み

classなど大量に付与されている属性は、値で絞り込む方が便利でしょう。

findやfind_allの引数に「属性名='値'」を渡します。
※classを検索するときにはアンダースコアを付与します(class_)。
※classキーワードがPythonの予約語のため。

>>> soup.find_all(class_='link')
[<div class="link">
<img src="sample.jpg"/>
<a href="http://example.com/1">リンク1</a>
</div>,
<div class="link">
<img src="sample.png"/>
<a href="http://example.com/2">リンク2</a>
</div>]
>>> soup.find(id='sub-topic')
<h3 id="sub-topic">見出しレベル3</h3>

本文で絞り込み

html本文の内容で絞り込みたいときは、「string='値'」もしくは「text='値'

>>> soup.find(string='見出しレベル2-1')
'見出しレベル2-1'

ただ完全一致のため、ちょっと使いにくいです。
標準ライブラリの「re」と組み合わせると正規表現が使えます。

>>> soup.find_all(string='見出しレベル')
[]
>> import re
>>> soup.find_all(string=re.compile('見出しレベル'))
['見出しレベル1', '見出しレベル2-1', '見出しレベル2-2', '見出しレベル3']

select

select」メソッドは、CSSのセレクターと同じような書き方で検索することができます。

select('セレクター定義')

セレクターに関しては以下を参照。
CSS セレクター - CSS: カスケーディングスタイルシート | MDN

>>> type(soup.select('p'))
<class 'bs4.element.ResultSet'>
>>> soup.select('p')
[<p>テキスト1</p>, <p>テキスト2</p>, <p><span class="bold">テキスト3</span></p>]
>>> soup.select('img')
[<img src="sample.jpg"/>, <img src="sample.png"/>]
>>> soup.select('.bold')
[<span class="bold">テキスト3</span>]
>>> soup.select('#topic1')
[<h2 id="topic1">見出しレベル2-1</h2>]
>>> soup.select('a[href*=example]')
[<a href="http://example.com/1">リンク1</a>, <a href="http://example.com/2">リンク2</a>]

結果はリストで返ってきています。

CSSに慣れている人であれば、こちらのメソッドの方が使いやすいかもしれませんね。

タグの中身の取り出し

単一のタグ――bs4.element.Tag――の中身を取り出します。

内容の取り出し

ブラウザで実際に表示される内容――タグの中身の文字列を取り出したいときには、「text」で参照します。

>>> soup.title
<title>レッツスクレイピング</title>
>>> soup.title.text
'レッツスクレイピング'
>>> soup.find('title').text
'レッツスクレイピング'

動作が似ているものに「string」があります。

>>> soup.title.string
'レッツスクレイピング'

両者の違いは型です。
textstr型stringはBeatufulSoupのNavigableString型です。

>>> type(soup.title.text)
<class 'str'>
>>> type(soup.title.string)
<class 'bs4.element.NavigableString'>

textは文字列がどんな入れ子の状態になっていても取り出すことができますが(すべての文字列が結合される)、NavigableStringは文字列が別のタグで分断されていると取り出せません。

>>> soup.body.text
'\n\n見出しレベル1\nテキスト1\n\n\nリンク1\n\n見出しレベル2-1\nテキスト2\n見出しレベル2-2\n\n見出しレベル3\nテキスト3\n\n\nリンク2\n\n\n\n'
>>> print(soup.body.string)
None

例・NavigableStringとして取り出せるものと取り出せないもの

>>> html = '<p>abc<span>def</span></p>'
>>> soup = BeautifulSoup(html, 'html.parser')
>>> soup.p.string
>>> print(soup.p.string)
None
>>> soup.span.string
'def'

かわりにNavigableStringはBeautifulSoupのメソッドが使えます。

>>> # 例・文字列の置換
>>> soup.title.string.replace_with('スクレイピングをやってみよう')
'レッツスクレイピング'
>>> soup.title
<title>スクレイピングをやってみよう</title>

単純に文字列が欲しいなら「text」、メソッドも使いたいなら「string」を使うといいでしょう。

属性値の取り出し[href/src]

リンク先や画像のURLなど属性の値を取得したいときは、タグに対して「get」メソッドを使います。

get('属性名')

>>> soup.a.get('href')
'http://example.com/1'
>>> soup.img.get('src')
'sample.jpg'

タグに属性名をキーとして渡しても同じように取り出せます。

>>> soup.h2['id']
'topic1'
>>> soup.div['class']
['main']

ただし、キーが存在しないときにはエラーになります。
※getメソッドはNoneが返ってくる。

find_allでimgタグをすべて取得し、個別にgetメソッドを使って中身を取り出してみます。

>>> for img in soup.find_all('img'):
...     link = img.get('src')
...     print(link)
... 
sample.jpg
sample.png

ですが上記は相対パスなので、画像の取得はできません。

標準ライブラリ「urllib.parse」モジュールから「urljoin」メソッドを使って絶対パスのURLにします。

取得元のURLが「http://example.com/」だとすると、以下のようにします。

>>> import urllib.parse
>>> url = 'http://example.com/'
>>> for img in soup.find_all('img'):
...     link = img.get('src')
...     abs_url = urllib.parse.urljoin(url, link)
...     print(abs_url)
... 
http://example.com/sample.jpg
http://example.com/sample.png

あとはこのURLを他のライブラリに渡して取得すればOKです。

まとめ

インストール

pip install beautifulsoup4

スープオブジェクト作成

from bs4 import BeautifulSoup
BeautifulSoup(markup, 'html.parser')

このページで使用したBeautifulSoupのメソッド等

メソッド・プロパティ機能
find()タグや「属性='値'」の検索。ひとつだけ
find_all()同上。すべて取り出し
select()セレクターを指定して検索
get()属性の取り出し
text本文の取り出し(str型)
string同上(NavigableString型)

関連書籍のレビュー

タイトルとURLをコピーしました