BeautifulSoup4でtableをパースする方法【Python, スクレイピング】

120, 2020-11-26

目次

BeautifulSoup4でtable要素をパースする

Pythonの外部ライブラリにHTML/XMLパーサーである「BeautifulSoup4(ビューティフル・スープ・フォー)」があります。
このライブラリはPythonによるスクレイピングなどでよく利用されるパーサーです。

このBeautifulSoup4を使うと、テーブル(table)要素も簡単にパースすることが出来ます。

この記事ではBeautifulSoup4でテーブル要素をパースする方法を解説します。
具体的には↓を見ていきます。

  • パース対象のテーブル要素

  • BeautifulSoup4の準備

  • 1つ目のテーブルのパース

  • 2つ目のテーブルのパース

パース対象のテーブル要素

今回パースするのはHTMLのtable要素です。
tableは↓のタグで構成されます。

  • table

  • thead

  • tbody

  • tfoot

  • tr

  • th

  • td

テーブルの構成によって使われるタグは異なることが多いです。
今回は基本となる構成のテーブルをパースします。

1つ目は↓のテーブルです。

商品名 在庫量 値段 販売先
おいしいリンゴ 100 200円 スーパー池本
赤いブドウ 200 300円 スーパー橋本
小さいメロン 50 500円 田中商店

↑のテーブルのソースコード↓。

<table border="1">
  <thead>
    <tr>
      <th>商品名</th>
      <th>在庫量</th>
      <th>値段</th>
      <th>販売先</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>おいしいリンゴ</td>
      <td>100</td>
      <td>200円</td>
      <td>スーパー池本</td>
    </tr>
    <tr>
      <td>赤いブドウ</td>
      <td>200</td>
      <td>300円</td>
      <td>スーパー橋本</td>
    </tr>
    <tr>
      <td>小さいメロン</td>
      <td>50</td>
      <td>500円</td>
      <td>田中商店</td>
    </tr>
  </tbody>
</table>

theadtbodyが組み込まれているのが特徴です。
このタグは省略することも可能です。

2つ目は↓の構成のテーブルです。

商品名 在庫量 値段 販売先
おいしいリンゴ 100 200円 スーパー池本
赤いブドウ 200 300円 スーパー橋本
小さいメロン 50 500円 田中商店

↑のテーブルのソースコード↓。

<table border="1">
  <tr>
    <th>商品名</th>
    <th>在庫量</th>
    <th>値段</th>
    <th>販売先</th>
  </tr>
  <tr>
    <td>おいしいリンゴ</td>
    <td>100</td>
    <td>200円</td>
    <td>スーパー池本</td>
  </tr>
  <tr>
    <td>赤いブドウ</td>
    <td>200</td>
    <td>300円</td>
    <td>スーパー橋本</td>
  </tr>
  <tr>
    <td>小さいメロン</td>
    <td>50</td>
    <td>500円</td>
    <td>田中商店</td>
  </tr>
</table>

↑の構成はtheadtbodyが省略されているのが特徴です。
この記事では、これら2つの構成のテーブルのパースを行います。

BeautifulSoup4の準備

BeautifulSoup4を使うにはbs4パッケージからBeautifulSoupクラスをインポートします。

from bs4 import BeautifulSoup

bs4.BeautifulSoupクラスはbs4.element.Tagを継承したクラスです。
よってbs4.BeautifulSoupのオブジェクトで利用可能なメソッドのほとんどはbs4.element.Tagのものです。
今回のテーブルのパースでは、このbs4.element.Tagのメソッドを頻繁に使うことになります。
追って解説していきます。

1つ目のテーブルのパース

1つ目のテーブルのBeautifulSoupによるパースを行います。
BeautifulSoupでテーブルをパースしたら、それらのテーブルの情報はPythonのリストに保存していきます。
こうすることで、最終的にCSVのフォーマットなどに変形することも可能になります。

まず前提としてhtmlという変数に先ほどの1つ目のテーブルのコードを文字列で保存しておきます。

html = '''
<table>
  <thead>
    <tr>
      <th>商品名</th>
      <th>在庫量</th>
      <th>値段</th>
      <th>販売先</th>
    </tr>
    ~ 省略 ~
'''

このhtml変数を使って実際のパースを行います。

from bs4 import BeautifulSoup

html = '''
<table>
  <thead>
  ~ 省略 ~
'''

mat = []  # 保存先の行列
soup = BeautifulSoup(html, 'html.parser')

# tableの取得
table = soup.find('table')

# theadの解析
r = []  # 保存先の行
thead = table.find('thead')  # theadタグを探す
ths = thead.tr.find_all('th')
for th in ths:  # thead -> trからthタグを探す
    r.append(th.text)  # thタグのテキストを保存

mat.append(r)  # 行をテーブルに保存

# tbodyの解析
tbody = table.find('tbody')  # tbodyタグを探す
trs = tbody.find_all('tr')  # tbodyからtrタグを探す
for tr in trs:
    r = []  # 保存先の行
    for td in tr.find_all('td'):  # trタグからtdタグを探す
        r.append(td.text)  # tdタグのテキストを保存
    mat.append(r)

# 出力
for r in mat:
    print(','.join(r))  # カンマ(,)で列を結合して表示

↑のように主要な解析はtheadの解析とtbodyの解析になります。

まず最初にtableタグをsoup.find('table')で取得します。

theadタグはtrタグを持っていて、そのtrタグは複数のthタグを持っています。
そのためtable.find('thead')theadタグを取得し、thead.tr.find_all('th')とやってthタグをまとめて取得します。

bs4.element.Tagfind()メソッドは第1引数のタグ名からタグを検索するメソッドです。
これの返り値もbs4.element.Tagになっています。
find_all()との違いは、find_all()が条件に一致するタグをすべて取得するのに対し、find()は条件に一致した最初のタグのみ取得する点です。

find_all()の返り値はbs4.element.ResultSetです。これはPythonのlistを継承したクラスで、リストのように扱うことが出来ます。

find_all()で取得した要素をfor文で回し、thタグを走査していきます。そしてthタグの属性textを参照し、それを行に保存します。
こうすることでtheadの列を保存しています。

tbodyタグについても同様です。
まずtable.find('tbody')tbodyタグを取得します。
tbody内のtrタグをまとめて取得し、それをfor文で回します。
trタグ内のtdタグを探し、それをfor文で回しながらtdタグのテキストを行に保存します。
行を保存し終わったらテーブルに行を追加します。

最後に出力のためmatfor文で回します。
行列の行の列を','.join(r)で結合し、カンマ区切りのフォーマット、つまりCSVに変換します。

↑のコードの出力は↓のようになります。

商品名,在庫量,値段,販売先
おいしいリンゴ,100,200円,スーパー池本
赤いブドウ,200,300円,スーパー橋本
小さいメロン,50,500円,田中商店

2つ目のテーブルのパース

2つ目のテーブルのコードをhtml変数に保存しておきます。

html = '''
<table>
  <tr>
    <th>商品名</th>
    ~ 省略 ~ 
'''

2つ目のテーブルにはtheadtbodyがありません。
あるのはtable, tr, th, tdのみです。
そのため最初にtableタグを取得したらtrタグをまとめて取得します。
そしてマジックナンバーでtrのリストを参照し、商品名などを取得します。
残りのtrfor文で回し、行ごとに解析していきます。

from bs4 import BeautifulSoup

html = '''
<table>
  <tr>
    <th>商品名</th>
    ~ 省略 ~
'''

mat = []  # 保存先の行列
soup = BeautifulSoup(html, 'html.parser')

# tableの取得
table = soup.find('table')

# trの解析
trs = table.find_all('tr')

# ヘッダーの解析
r = []  # 保存先の行
tr = trs[0]
for td in tr.find_all('th'):  # thタグを走査する
    r.append(td.text)

mat.append(r)

# ボディの解析
for tr in trs[1:]:  # 最初の行を飛ばしてfor文で回す
    r = []  # 保存先の行
    for td in tr.find_all('td'):  # tdタグを走査する
        r.append(td.text)
    mat.append(r)

# 出力
for r in mat:
    print(','.join(r))  # カンマ(,)で列を結合して表示

theadtbodyタグがある構成のテーブルと比べると、解析は多少推測みたいなものが入っていて、エラーの考慮などが必要になってきます。

↑のコードの出力結果↓。

商品名,在庫量,値段,販売先
おいしいリンゴ,100,200円,スーパー池本
赤いブドウ,200,300円,スーパー橋本
小さいメロン,50,500円,田中商店

おわりに

BeautifulSoup4を使うと簡単にテーブルをパースすることが可能です。
スクレイピングに使ってみられてはいかがでしょうか。

(^ _ ^)

ユーニックス家の食卓



この記事のアンケートを送信する