Web ページに載っている表を取りたい、という場面はよくあります。が無く、の配布も無く、画面には表として出ている。こういうとき の read_html を使うと、URL を渡すだけで表の一覧が返ってきます。うまくいけば1行で終わります。
問題は、うまくいかないときに理由が分かりにくいことです。「表が見つかりません」という例外だけが出て、画面には確かに表が見えている。この落差でつまずきます。この回では、取れる条件と取れない条件を切り分けます。
取れるのは table タグの表だけ
read_html が探すのは HTML の table 要素です。見た目が表でも、div を並べて格子状に見せているだけなら1つも見つかりません。最近の Web サイトは表を div で組んでいることが多く、これが最初の関門になります。
これは技術的な限界なので、粘っても取れません。確認は簡単で、ブラウザでページのソースを表示して <table を検索すれば分かります。無ければ read_html では取れないので、別の手段を考えることになります。ここで判断が付くので、動かない理由を延々と探すより先に確かめる価値があります。
逆に table が見つかりすぎる場合もあります。ページの装飾やレイアウトに table を使っているサイトだと、目当ての表以外もすべて拾われます。返ってきた一覧から欲しいものを選ぶことになるので、行数や列名で絞り込む処理を書いておくと安定します。
import re
import requests
resp = requests.get(url, timeout=(5, 30), headers={"User-Agent": "..."})resp.raise_for_status()html = resp.text
n = len(re.findall(r"<table[\s>]", html, re.I))print(f"table タグ: {n} 個")if n == 0: print("read_html では取れない。div で組まれているか、後から描画されている")後から描かれる表は取れない
table タグがあるのに取れない場合、ページを開いた直後の HTML には表が無く、あとから JavaScript が描いている可能性があります。ブラウザで見れば表がありますが、requests が受け取る HTML には入っていません。
見分け方は、受け取った HTML を保存して中身を見ることです。表の中の具体的な値(見えている数字のどれか)を検索して、見つからなければ後から描かれています。この場合はブラウザを動かす道具が要りますが、その前に確かめるべきことがあります。
画面が JavaScript で表を作っているなら、その裏で API を呼んでいることがほとんどです。ブラウザの開発者ツールで通信を見ると、整った が返っているのが分かる場合があります。そちらを直接叩くほうが、速くて壊れにくいです。
取れたあとの型に注意する
read_html は表を読むだけで、型は推測に任されます。桁区切りのカンマ、全角の数字、単位付きの値、注記の記号。こうしたものが混ざると、数値の列が文字列として読まれます。そのまま集計すると、足し算のつもりが文字列の連結になります。
取得直後に型を確認して、数値にできない値がどれだけあるかを数えてください。想定より多ければ、変換の規則が足りていません。ここでも一緒に済ませておくと、後の処理が楽になります。
import pandas as pd
def to_number(s: pd.Series) -> pd.Series: """桁区切り・全角・空欄記号を落としてから数値にする。""" cleaned = ( s.astype(str) .str.replace(",", "", regex=False) .str.replace("−", "-", regex=False) # 全角のマイナス .str.replace("△", "-", regex=False) # 会計表記の負数 .str.strip() .replace({"": None, "-": None, "―": None, "…": None, "n.a.": None}) ) return pd.to_numeric(cleaned, errors="coerce")
def check_conversion(df: pd.DataFrame, col: str) -> None: converted = to_number(df[col]) lost = converted.isna().sum() - df[col].isna().sum() print(f"{col}: 数値に直せなかった値 {lost} 件 / 全 {len(df)} 件") if lost: bad = df.loc[converted.isna() & df[col].notna(), col].unique()[:10] print(f" 例: {list(bad)}")使ってよい場面かを先に考える
技術的に取れることと、取ってよいことは別です。に当たる行為なので、利用規約と を確認するのが前提になります。取得の頻度も、人が見るのと同程度に抑えるのが礼儀です。
- や CSV の配布が無いか先に探す — たいてい存在する。探す時間のほうが安い
- 規約で禁止されていないか — 明示的に禁じているサイトは実在する
- 表の構造が変わったら壊れる — 列の順序に依存した書き方をしない
取得の間隔も決めておいてください。人が画面を見る速さを超えて叩く理由はありません。1ページごとに数秒空けるだけで、相手から見た負荷は大きく変わります。急いで取る必要が本当にあるのかを一度考えると、たいていは急がなくてよいと分かります。
列の位置ではなく列名で参照するようにしておくと、列が1つ増えただけで壊れる事故を減らせます。それでも配信側の都合で表の作りは変わるので、壊れることを前提に監視を入れておく必要があります。次回は、開発中に相手を叩きすぎないための仕組みを扱います。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。