DEV Community

Cover image for WebテーブルからPandas DataFrameへ30秒で変換
circobit
circobit

Posted on

WebテーブルからPandas DataFrameへ30秒で変換

Webサイトで完璧なデータセットを見つけました。これをPandasに取り込みたい。

従来のアプローチ:

import pandas as pd

# Webサイトの構造がシンプルであることを祈る
tables = pd.read_html('https://example.com/data')

# どのテーブルか推測する
df = tables[0]  # たぶん?見てみよう...

# 問題が発覚
print(df.dtypes)
# すべてが 'object'(文字列)
# 数値にカンマが入っている
# 日付がパースできない
# カラム名にスペースが入っている

# 30分かけてクリーニング...
Enter fullscreen mode Exit fullscreen mode

もっと速い方法をお見せします。

pd.read_html() の問題点

Pandasの read_html() は便利ですが限界があります:

  1. テーブルの選択ができない — すべてのテーブルを取得。どのインデックスか推測する必要がある。

  2. クリーニングなし — "1,234,567" のような数値は文字列のまま。

  3. CORSの問題 — 多くのサイトがプログラムによるアクセスをブロック。

  4. JavaScriptレンダリング — 動的テーブルは生のHTMLには存在しない。

  5. 認証 — ログインが必要なコンテンツにアクセスできない。

簡単なスクリプトなら動きます。本格的な分析には、もっと良い方法が必要です。

30秒ワークフロー

実際のやり方はこうです:

ステップ1:ブラウザからエクスポート(5秒)

HTML Table Exporter を使って:

  1. 欲しいテーブルをクリック
  2. 拡張機能アイコンをクリック
  3. 「For Pandas」プロファイルを選択
  4. ハイライトされたテーブルからCSVでエクスポート

拡張機能はブラウザが表示しているものをそのまま見ます — JavaScriptレンダリングされたコンテンツ、認証済みページ、すべて。

ステップ2:Pandasで読み込み(5秒)

import pandas as pd

df = pd.read_csv('export.csv')
print(df.dtypes)
Enter fullscreen mode Exit fullscreen mode

以上です。データはすでにクリーンです。

「クリーン」の具体的な意味

「For Pandas」プロファイルでエクスポートすると、拡張機能が以下を処理します:

数値の正規化

前: "1.234.567,89" (ヨーロッパフォーマット)
後: 1234567.89     (float)

前: "$1,234.56"
後: 1234.56
Enter fullscreen mode Exit fullscreen mode

CSVには正規化された数値が含まれ、Pandasが正しくパースします:

# クリーニングなしの場合:
df['revenue'].sum()  # TypeError: can only concatenate str

# クリーニング済みの場合:
df['revenue'].sum()  # 4892341.50 ✓
Enter fullscreen mode Exit fullscreen mode

Boolean変換

前: "Yes", "No", "Y", "N", "True", "False"
後: true, false
Enter fullscreen mode Exit fullscreen mode
# フィルターがすぐ動く
active_users = df[df['is_active'] == True]
Enter fullscreen mode Exit fullscreen mode

Null処理

前: "-", "N/A", "n/a", "", "null", "—"
後: (空、NaNとしてパース)
Enter fullscreen mode Exit fullscreen mode
# Null検出が正常に動作
df['optional_field'].isna().sum()  # 正確なカウント
Enter fullscreen mode Exit fullscreen mode

snake_caseヘッダー

前: "Revenue ($M)", "User Count", "Growth Rate %"
後: revenue_m, user_count, growth_rate
Enter fullscreen mode Exit fullscreen mode
# クリーンなカラムアクセス
df['revenue_m']  # df['Revenue ($M)'] の代わりに
Enter fullscreen mode Exit fullscreen mode

実例:FBRefのサッカー統計

プレミアリーグの選手統計をFBRefから取得したいとします。

従来の方法

import pandas as pd
import requests
from bs4 import BeautifulSoup

url = 'https://fbref.com/en/comps/9/stats/Premier-League-Stats'
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')

# 正しいテーブルを見つける(たくさんある)
table = soup.find('table', {'id': 'stats_standard'})

# 手動でパース(ヘッダーが複雑なため)
# FBRefはグループ化ヘッダーを使用:"Playing Time" が複数列にスパン
# これが pd.read_html() を壊す

# 45分後...
Enter fullscreen mode Exit fullscreen mode

新しい方法

  1. ブラウザでFBRefを開く
  2. 拡張機能をクリック → テーブルを選択 → 「For Pandas」プロファイルでエクスポート
  3. 読み込み:
df = pd.read_csv('fbref_stats.csv')
print(df.columns.tolist())
# ['player', 'nation', 'squad', 'playing_time_mp', 
#  'playing_time_starts', 'performance_gls', ...]
Enter fullscreen mode Exit fullscreen mode

グループヘッダー("Playing Time"、"Performance")がサブヘッダーと自動的にマージされます。

もう書かなくていいコード

以前、Webスクレイピングのたびに書いていたクリーンアップコード:

def clean_web_data(df):
    """もう必要ない関数"""

    for col in df.select_dtypes(include='object'):
        try:
            cleaned = df[col].str.replace(r'[$€£¥]', '', regex=True)
            cleaned = cleaned.str.replace(',', '')
            df[col] = pd.to_numeric(cleaned, errors='ignore')
        except:
            pass

    bool_map = {
        'yes': True, 'no': False,
        'true': True, 'false': False,
        'y': True, 'n': False,
        '1': True, '0': False,
    }
    for col in df.columns:
        if df[col].str.lower().isin(bool_map.keys()).all():
            df[col] = df[col].str.lower().map(bool_map)

    null_values = ['', '-', 'N/A', 'n/a', 'null', 'NULL', '', '']
    df = df.replace(null_values, np.nan)

    df.columns = (df.columns
        .str.lower()
        .str.replace(r'[^a-z0-9]+', '_', regex=True)
        .str.strip('_'))

    return df
Enter fullscreen mode Exit fullscreen mode

この関数はすべてのデータセットで実行していました。今はエクスポートが処理してくれます。

いつ何を使うか

シナリオ 最適なアプローチ
一回限りの分析 ブラウザエクスポート → CSV → Pandas
繰り返しスクレイピング Pythonスクリプト + requests
JavaScript多用サイト ブラウザエクスポート(レンダリング済みコンテンツを表示)
認証済みデータ ブラウザエクスポート(セッションを使用)
APIが利用可能 APIを直接使用
シンプルな静的テーブル pd.read_html() で十分

プロのヒント:複雑なデータにはJSON

ネストされたデータや型付きデータには、JSONでエクスポート:

import pandas as pd
import json

with open('export.json') as f:
    data = json.load(f)

df = pd.DataFrame(data)
Enter fullscreen mode Exit fullscreen mode

JSONエクスポートは型を保持します:

  • 数値は数値(文字列ではなく)
  • Booleanはboolean
  • NullはNull
print(df.dtypes)
# player         object
# goals          int64   # すでに数値!
# is_starter     bool    # すでにboolean!
# injury_date    object  # datetimeにパース可能
Enter fullscreen mode Exit fullscreen mode

JSONワークフローの詳細は、HTMLテーブルスクレイパー Chrome拡張機能ガイドをご覧ください。

ワークフローまとめ

旧ワークフロー(30分以上):

  1. スクレイピングスクリプトを書く
  2. CORS/認証の問題に対処
  3. 複雑なHTMLをパース
  4. 数値をクリーニング
  5. Booleanをクリーニング
  6. Nullをクリーニング
  7. カラム名を修正
  8. エッジケースをデバッグ
  9. ようやく:分析

新ワークフロー(30秒):

  1. 拡張機能をクリック
  2. クリーニングプロファイルでエクスポート
  3. pd.read_csv()
  4. 分析

試してみよう

  1. HTML Table Exporter をインストール
  2. 分析したいテーブルを見つける
  3. クリーニングプリセットでエクスポート
  4. Pandasで読み込み

無料版で基本的なエクスポートが可能。PROはPandas最適化出力のためのクリーニングプリセットとプロファイルを追加します。

詳しくは gauchogrid.com/ja/html-table-exporter をご覧いただくか、Chrome Web Store でお試しください。


WebデータをPandasに取り込む現在のワークフローは?クリーニングステップにどれくらい時間を費やしていますか?コメントで教えてください。

Top comments (0)