Webスクレイピングにおいて、HTMLテーブルからデータを取得する作業は非常に一般的です。
PythonのBeautiful Soupライブラリを使用すれば、<table>タグ内の要素を抽出すること自体は決して難しくありません。
しかし、実務で遭遇するテーブルの多くには、セルの結合を意味するrowspanやcolspanが含まれています。
これらの属性が適切に処理されないと、抽出したデータの列がずれてしまい、分析に耐えない不正確なリストが作成されてしまいます。
本記事では、2026年現在のモダンなPython開発環境において、複雑な結合セルを持つテーブルを正しく解析し、整形されたデータ構造に変換する具体的な手法を解説します。
結合セル(rowspan/colspan)がスクレイピングを困難にする理由
Beautiful Soupの標準的な機能であるfind_all('tr')を用いてループ処理を行う場合、各行に含まれる<td>や<th>の数だけがカウントされます。
HTMLの構造上、rowspanによって縦方向に結合されたセルは、「2行目以降の対応する列にタグが存在しない」という状態を生み出します。
同様にcolspanによって横方向に結合されたセルは、その行における見かけ上のセル数を減少させます。
これを考慮せずに単純なリストへ格納していくと、結合された部分が空欄になったり、データが左側に詰められたりして、列の整合性が失われます。
正確なデータ抽出を実現するためには、人間がブラウザでテーブルを見ているときと同じように、仮想的な2次元座標(行列)をプログラム内で再現する必要があります。
結合セルを考慮した整形アルゴリズムの考え方
プログラミングでこの問題を解決するためには、まず最終的なテーブルの行数と列数を把握し、すべてのセルがNoneや空文字で埋められた2次元配列を用意するのが理想的です。
次に、元のHTMLテーブルを1行ずつ走査し、各セルが「すでに結合セルによって埋まっていないか」を確認しながらデータを流し込んでいきます。
2次元グリッドの事前構築
スクレイピング対象のテーブルが巨大な場合、動的に配列を拡張するよりも、最大サイズを計算して初期化する方が処理効率は高まります。
対象のテーブルを一度走査して、最大の列数を見積もることから始めましょう。
ポインタ管理によるデータのマッピング
各行(tr)を処理する際、現在書き込みを行っている「列のインデックス」を保持するポインタを用意します。
もしターゲットとなる座標が、前の行のrowspanによってすでに埋められている場合は、ポインタを次の列へ進めるロジックが必要です。
この「予約済みのセルをスキップする処理」こそが、rowspan/colspan対応の肝となります。
実装コード:Beautiful Soupで結合セルを正しく処理する
それでは、実際にBeautiful Soupを使用して、複雑なテーブルを2次元のリスト(リストのリスト)に変換する関数を作成してみましょう。
以下のコードは、行と列の結合を同時に考慮し、欠損のないデータ構造を出力します。
from bs4 import BeautifulSoup
def parse_html_table(table):
# すべての行を取得
rows = table.find_all('tr')
# 2次元配列をシミュレートするための辞書(またはリストのリスト)
# (row_index, col_index): value の形式で保持
table_data = {}
# 最大の行数と列数を追跡
max_rows = len(rows)
max_cols = 0
for row_idx, row in enumerate(rows):
cells = row.find_all(['td', 'th'])
# 現在の列ポインタ
col_idx = 0
for cell in cells:
# すでにrowspanなどで埋まっている座標をスキップ
while (row_idx, col_idx) in table_data:
col_idx += 1
# rowspanとcolspanの属性値を取得(デフォルトは1)
rowspan = int(cell.get('rowspan', 1))
colspan = int(cell.get('colspan', 1))
# セルのテキスト内容を取得
cell_text = cell.get_text(strip=True)
# rowspanとcolspanの範囲すべてに値を書き込む
for r in range(rowspan):
for c in range(colspan):
table_data[(row_idx + r, col_idx + c)] = cell_text
# 次のセルへ(colspan分進める)
col_idx += colspan
if col_idx > max_cols:
max_cols = col_idx
# 辞書形式から整列された2次元リストに変換
result = []
for r in range(max_rows):
current_row = []
for c in range(max_cols):
# データが存在しない場合は空文字を入れる
current_row.append(table_data.get((r, c), ""))
result.append(current_row)
return result
# サンプルHTML
html_content = """
<table border="1">
<tr>
<th rowspan="2">部署</th>
<th colspan="2">2026年度実績</th>
</tr>
<tr>
<th>売上</th>
<th>利益</th>
</tr>
<tr>
<td>営業部</td>
<td>1000</td>
<td>200</td>
</tr>
</table>
"""
soup = BeautifulSoup(html_content, 'html.parser')
target_table = soup.find('table')
formatted_data = parse_html_table(target_table)
# 結果の表示
for row in formatted_data:
print(row)
['部署', '2026年度実績', '2026年度実績']
['部署', '売上', '利益']
['営業部', '1000', '200']
実行結果を見ると、rowspan="2"が指定されていた「部署」というセルが、1行目と2行目の両方の先頭にコピーされていることがわかります。
また、colspan="2"が指定されていた「2026年度実績」も、横方向の2つの列に正しく展開されています。
このように、結合された範囲を同じ値で埋めることで、PandasのDataFrameなどへ変換した際にデータの欠落がなくなります。
Pandasを用いたより簡単な解決策
もしプロジェクトに強力なデータ解析ライブラリである「Pandas」を導入できる環境であれば、さらに簡潔な方法が存在します。
Pandasのread_html関数は、内部でBeautiful Soupやlxmlを使用しており、デフォルトでrowspanやcolspanをある程度解釈してくれます。
import pandas as pd
from io import StringIO
# HTML文字列を直接読み込む(URL指定も可能)
# read_htmlはリストを返すため、最初のテーブルを取得
dfs = pd.read_html(StringIO(html_content))
df = dfs[0]
print(df)
部署 2026年度実績 2026年度実績.1
0 部署 売上 利益
1 営業部 1000 200
Pandasを利用すると、結合されたセルを自動的に処理し、マルチインデックス(階層構造)として保持することも可能です。
ただし、WebサイトのHTML構造が極端に崩れている場合や、非常に特殊な属性が使われている場合には、先述したBeautiful Soupによる手動のパースが必要になる場面もあります。
状況に応じて、「柔軟性のBeautiful Soup」か「スピードのPandas」かを選択してください。
実務で注意すべき特殊なケース
実際のWebサイトでは、理論通りにいかないケースが多々あります。
例えば、<thead>や<tbody>、<tfoot>といったタグが混在している場合や、テーブルの中にさらにテーブルが入っている「入れ子構造」の場合です。
入れ子になったテーブルの除外
find_all('tr')を実行すると、子テーブルの中にある行まで取得してしまうことがあります。
これを防ぐためには、recursive=Falseオプションを活用するか、直下の要素のみを指定して取得するセレクタを使用しましょう。
不完全なrowspanの扱い
稀に、HTMLの記述ミスでrowspanの指定値が実際の行数より多く設定されているテーブルが存在します。
プログラムが無限ループに陥ったり、配列のインデックス範囲外エラーを出したりしないよう、例外処理や最大行数のチェックを必ず組み込んでください。
まとめ
PythonのBeautiful Soupを使用してrowspanやcolspanを含むテーブルを処理するには、単純なループではなく2次元座標を意識したロジックが不可欠です。
本記事で紹介した辞書形式を用いたマッピング手法を使えば、どんなに複雑に結合されたテーブルでも、列をずらすことなく正確にデータを抽出できます。
また、より効率的に作業を進めたい場合は、Pandasのread_htmlを第一の選択肢として検討することをおすすめします。
データの整合性を保つスクレイピング技術を習得して、2026年の高度なデータ分析業務に役立ててください。
