欲しいデータが Web ページにしか無い、という場面はあります。統計が表として貼られているだけ、一覧が検索画面の中にしかない、といった状態です。が公開されておらず、ファイルの配布も無い。画面には表示されているのだから取れるはずだ、と考えるのは自然です。
ただしは、技術の問題である前に、相手との関係の問題です。この回では、書き始める前に確認することと、確認した結果として「やらない」を選ぶ場合について扱います。
先に結論を書いておくと、確認の結果として実装しないと決めた例は実際にあります。取れることが分かっていても取らないという判断は、技術者としては物足りなく感じますが、後から問題になる余地を残さないという点では最も安全な選択です。
1. 本当に他の手段が無いか
最初にやるべきは、他の入手経路を探すことです。経験上、無いと思っていたものが見つかることは多いです。探す時間のほうが、スクレイピングを実装して保守する時間より圧倒的に短くて済みます。
- 画面の裏で呼んでいる — 開発者ツールの通信履歴に整った が流れていることがある
- ファイルの配布ページ — 「オープンデータ」「統計表ダウンロード」といった別の導線にある
- 問い合わせる — 研究や業務での利用なら、提供してもらえる場合がある
特に画面の裏の は見つかりやすく、しかもHTML を解析するより遥かに壊れにくいです。画面の見た目が変わっても、裏の応答の形はそう頻繁には変わりません。
2. 規約と robots.txt を読む
は、クローラに対してどこを取ってよいかを示すファイルです。取得を始める前に確認します。ただしこれは自動化された取得に対する指示であって、利用規約とは別物です。両方を見る必要があります。
利用規約に自動取得の禁止が明記されているサイトは実在します。「認められている」と書かれていることは稀なので、書かれていない場合にどう判断するかが実務上の論点になります。ここは技術の話ではないので、判断に迷うなら組織として決めるべき事柄です。
取得してよいかと、取得したものを再配布・公開してよいかは別の話です。手元の分析に使うだけなら問題なくても、集計結果を公開する段になって権利の確認が必要になることがあります。使い道が決まっているなら、先に確認しておいてください。
3. 相手に負担をかけない形にする
確認を通って実装する場合でも、人が閲覧するのと同程度の速さに抑えるのが基本です。1ページごとに数秒空ける、夜間に回す、同時実行はしない。無料で公開されている情報を取りに行く立場だという前提を忘れないでください。
に連絡先を書いておくのも実際的です。取得の仕方に問題があったとき、いきなり遮断されるかわりに連絡が来る可能性が上がります。相手にとっても、正体不明の通信より判断しやすくなります。
import timefrom urllib.parse import urljoinfrom urllib.robotparser import RobotFileParser
UA = "hukuhuku-data-fetch/1.0 (+https://hukuhuku.co.jp; contact@example.com)"INTERVAL = 3.0 # 1ページごとに空ける秒数
def make_checker(base_url: str) -> RobotFileParser: rp = RobotFileParser() rp.set_url(urljoin(base_url, "/robots.txt")) rp.read() return rp
def crawl(urls: list[str], base_url: str, fetch): rp = make_checker(base_url) for url in urls: if not rp.can_fetch(UA, url): print(f" robots.txt で許可されていない: {url}") continue fetch(url) time.sleep(INTERVAL) # 取得の後に必ず待つ壊れることを前提に組む
HTML の構造は、相手の都合でいつでも変わります。のように互換性を約束されていないので、ある日突然何も取れなくなることを前提に組む必要があります。
対策としては、取得件数がゼロになったら知らせる、想定した項目が見つからなければ例外にする、といった単純な検査で足ります。黙って空のデータを保存し続けるのが最悪の壊れ方です。
def parse_rows(soup) -> list[dict]: """想定した構造が見つからなければ、空を返さずに例外にする。 空を返すと「今日はデータが無かった」と区別が付かなくなる。""" table = soup.select_one("table.data") if table is None: raise RuntimeError("目当ての表が見つからない。ページ構造が変わった可能性")
rows = table.select("tbody tr") if not rows: raise RuntimeError("表はあるが行が無い。抽出条件が合っていない可能性")
out = [] for tr in rows: cells = [td.get_text(strip=True) for td in tr.select("td")] if len(cells) < 3: continue # 見出し行などは飛ばす out.append({"name": cells[0], "value": cells[1], "date": cells[2]})
if not out: raise RuntimeError("行はあるが1件も抽出できなかった。列の構成が変わった可能性") return out保守の負担も見積もっておいてください。相手が改装するたびに直す作業が発生します。改装は予告なく行われるので、直す作業もいつ発生するか読めません。他の仕事の最中に「昨日から取れていない」と言われる、という形で降ってきます。その手間を継続的に払えるかどうかが、やる/やらないの判断材料になります。一度作れば終わりではありません。
取得をやめる判断も、立派な選択肢として持っておいてください。手作業で月に一度ダウンロードするほうが、壊れ続ける自動化より安く済むことは実際にあります。自動化の目的は手間を減らすことであって、自動化そのものではありません。ここまでで、取得の作法と道具の話は一区切りです。次回からは、具体的なデータを題材にしていきます。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。