Python,BeautifulSoup -- table要素のデータを抽出してCSVファイルに書き込む

概要

  • table 要素のデータを抽出して CSV ファイルに書き込みたい
  • CSVファイルさえあれば、pandas.DataFrame() として読み込める
  • 世界中の (バラバラな書式の) table 要素に対応しようと一瞬思ったがやめた
  • 大学教授系の機械学習 (の授業) 用サイトからデータを抽出したい場合にサクッといきたい、など

ポイント

  • 読み込める情報はとりあえず読めるだけ読む込むことにする (CSVに書き込んだ後、データをどのように読み込むかは pandas.read_csv(), pandas.DataFrame 側でコントロールすれば足りるので)
  • (結果として) table 要素をパースする側で、バラバラな書式に対応する手間が (少し) 省ける

HTML データから table 要素を取得する

from bs4 import BeautifulSoup as BS


def get_tables(content, is_talkative=True):
    """table要素を取得する"""
    bs = BS(content, "lxml")
    tables = bs.find_all("table")
    n_tables = len(tables)
    if n_tables == 0:
        emsg = "table not found."
        raise Exception(emsg)
    if is_talkative:
        print("%d table tags found.." % n_tables)
    return tables

テーブルからデータを抽出する

def parse_table(table):
    """table要素のデータを読み込んで二次元配列を返す"""

    ##### thead 要素をパースする #####

    # thead 要素を取得 (存在する場合)
    thead = table.find("thead")

    # thead が存在する場合
    if thead:
        tr = thead.find("tr")
        ths = tr.find_all("th")
        columns = [th.text for th in ths]    # pandas.DataFrame を意識
    
    # thead が存在しない場合
    else:
        columns = []

    ##### tbody 要素をパースする #####

    # tbody 要素を取得
    tbody = table.find("tbody")

    # tr 要素を取得
    trs = tbody.find_all("tr")

    # 出力したい行データ
    rows = [columns]

    # td (th) 要素の値を読み込む
    # tbody -- tr 直下に th が存在するパターンがあるので
    # find_all(["td", "th"]) とするのがコツ
    for tr in trs:
        row = [td.text for td in tr.find_all(["td", "th"])]
        rows.append(row)

    return rows

抽出したデータを CSV ファイルに書き込む

import os, csv


def table2csv(path, rows, lineterminator="\n",
    is_talkative=True):
    """二次元データをCSVファイルに書き込む"""

    # 安全な方に転ばせておく
    if os.path.exists(path):
        emsg = "%s already exists." % path
        raise ValueError(emsg)

    # データを書き込む
    with open(path, "w") as f:
        writer = csv.writer(f, lineterminator=lineterminator)
        writer.writerows(rows)
        if is_talkative:
            print("%s successfully saved." % path)

使用イメージ

import requests


# HTML データを取得する
url = "http://www.somewhere.com/tables/"
res = requests.get(url)
content = res.text

# table 要素を取得する
# HTML 中のすべての table 要素が表として使われているとも
# 限らないし、またそれが望んでいるものであるとも限らないので
# このあたりのプロセスは関数としてまとめていない
# ( = テーブルの選択自体はユーザーが行う)
tables = get_tables(content)
table = tables[0]
rows = parse_table(table)

# CSV ファイルとして出力する
# 出力先が Windows なら以下のようにする
table2csv("./table.csv", rows, "\r\n")

または、HTML 中の table 要素をすべて出力する場合は、

# テーブル要素の取得後..
for i, table in enumerate(tables):
    table_name = input()    # コンソール上で保存名を指定
    save_path = "./%s.csv" % table_name    # 保存ファイル名 (名前部分のみ)
    table2csv(save_path, rows, "\r\n")

のようにしてもよいかもしれない。