本ページは Webスクレイピング(Web Scraping)を多角的に解説します。 上のチップは、 検索・関連語の手がかりです。
「scraping」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「scraping」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「scraping の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
ネット上の情報を集める自動ロボットです。
必要なデータを効率よく集めるために使います。
通販サイトで商品の値段を比べる時に便利です。
この章では、使い方の基本と注意点を読みます。
data/raw/ と data/processed/requests-cache でサーバーに優しく🍰 まずはやさしく
Webサイトから情報を抜き出す手法です。
分析に使うデータを増やすために活用します。
SNSの投稿を集めて流行を調べる時に使えます。
ここでは、使う時のルールや法律について読みます。
Web スクレイピング(Web Scraping)は、 公開 Web サイトの情報を取得する手法。 ニュース記事収集、 価格比較、 不動産情報集約、 SNS 分析など データソースの拡張に多用されます。 ただし著作権、 利用規約、 サーバー負荷、 個人情報保護など 多くの法的・倫理的論点があり、 慎重な運用が必要です。
🍰 まずはやさしく
ネットのページを自動で読み取る仕組みです。
大量の情報をまとめて保存するために使います。
天気予報のサイトから気温を書き出す例があります。
ここでは、情報を抜き出す具体的な流れを読みます。
スクレイピングの基本フロー:
例:気象庁の天気情報、 不動産サイトの物件一覧、 Amazon の価格、 食べログの口コミ など、 Web 上の構造化された表は事実上すべてスクレイピング可能です(合法性は別)。
近年は JavaScript で後から表示される SPA(シングルページアプリ)が多く、 単純な HTTP では取得できない場合が増えています。 そのときは Selenium や Playwright でブラウザを自動操作します。
Web スクレイピングの典型ユースケースを 3 種類に分けて補助図を示す。 ① 取得データの分布把握 (価格・件数等のヒストグラム)、 ② 時系列収集 (ニュース・株価の経時変化)、 ③ 取得頻度と負荷の関係 (アクセス間隔と server 負荷)。



💬 スクレイピングは「技術的にできる」と「倫理的にやってよい」が一致しない領域。 図 3 のような負荷可視化を通じて、 取得側 (自分) と提供側 (server) の双方を尊重する設計を心がける。
この節は、 相関ページ基準の説明密度に合わせ、 スクレイピングを「単に取って終わり」ではなく「再現可能・倫理的・継続運用可能な収集パイプライン」として組み立てる際の設計判断を、 SSDSE-B-2026 などの公的データ取得と対比しながら詳述する。 8 つの観点 — ①取得対象の同定、 ②robots.txt と利用規約、 ③HTTP セマンティクス、 ④パース戦略、 ⑤エンコーディング、 ⑥差分検知、 ⑦失敗時のリトライ設計、 ⑧データ品質ゲート — を順に検討する。
スクレイピングを始める前に、 「1 ページ = 1 レコード」 (詳細ページ) か、 「1 ページ = N レコード」 (一覧ページ) かを必ず確認する。 一覧ページから取得すると速いが、 詳細ページにしか無い属性 (説明文・タグ・関連リンク) を見落とす。 SSDSE-B-2026 のような単一 CSV ダウンロードと違い、 Web のデータは「ページ構造 → CSV 構造」への写像設計が必要で、 ここを最初に決めないと後工程で全て破綻する。
| パターン | 利点 | 欠点 | 向く用途 |
|---|---|---|---|
| 一覧ページのみ収集 | 速い・低負荷 | 詳細属性が欠落 | 価格モニタリング |
| 詳細ページまで辿る | 完全情報 | 遅い・高負荷 | 研究データセット構築 |
| 差分のみ詳細取得 | 最適バランス | 差分検知ロジック必要 | 日次ニュース蓄積 |
| サイト全体クロール | 網羅性最高 | 時間・コスト膨大 | 検索インデックス構築 |
https://example.com/robots.txt は「クローラに対する自主ルール」であり、 法的拘束力は地域で異なるが、 これを無視するスクレイパは技術コミュニティで強く非難される。 重要なディレクティブは User-agent、 Disallow、 Allow、 Crawl-delay、 Sitemap の 5 つ。 加えてサービスの利用規約 (ToS) に「データを自動収集することを禁ずる」と明記されているケースが多く、 ToS 違反は民事責任を生じ得る (CFAA・不正アクセス禁止法等)。 公的統計 (SSDSE 含む) は CC BY 等の明示的ライセンスがあるため、 これを優先的に使う方が圧倒的に安全。
このコードでやること: urllib.robotparser で robots.txt を確認し、 取得可否を判定する。
1 2 3 4 5 6 7 | from urllib.robotparser import RobotFileParser rp = RobotFileParser() rp.set_url('https://www.e-stat.go.jp/robots.txt') rp.read() print('GET /stat-search 取得可否:', rp.can_fetch('*', 'https://www.e-stat.go.jp/stat-search')) print('crawl-delay:', rp.crawl_delay('*')) |
📤 出力例:
💬 e-Stat のように公的データは多くが取得可、 が crawl-delay が無くとも自主的に 1 秒以上の間隔を空ける。 一方、 商用 EC・SNS は Disallow: / が広範に設定されており、 そもそも収集対象にしてはいけない。
スクレイピングの本質は HTTP クライアントなので、 ステータスコードの意味を正確に理解する。 200 OK は成功だが、 301/302 はリダイレクト (URL 変更を蓄積側で更新する)、 304 Not Modified は「前回と同じ」 (If-Modified-Since または ETag ヘッダを送ると返ってくる、 帯域節約に最重要)、 403 Forbidden は権限不足 (User-Agent 偽装の誘惑が生まれるが、 これは ToS 違反になりやすい)、 429 Too Many Requests は速度違反 (即時バックオフ)、 500/503 はサーバ側エラー (リトライ可能だが間隔を倍々にする exponential backoff が定石)。
このコードでやること: If-Modified-Since で条件付き GET を投げ、 304 が返れば帯域を消費せずに「更新なし」と判定する。
1 2 3 4 5 6 7 8 | import requests from email.utils import formatdate import time last = time.time() - 3600 * 24 headers = {'If-Modified-Since': formatdate(last, usegmt=True)} r = requests.get('https://www.e-stat.go.jp/', headers=headers, timeout=10) print('status:', r.status_code) # 200 なら更新あり、304 なら更新なし |
📤 出力例:
💬 多くの統計サイトは 304 を返さない (動的生成) ため、 ETag や Last-Modified が来るならそれを蓄積側で記録し、 来ないなら自前でハッシュ比較 (SHA256 比較) を行うのが現実解。
HTML は不正な (closing tag 漏れ等) 状態でも browser が表示してしまうので、 厳密な XML パーサは失敗しやすい。 第一選択は BeautifulSoup + lxml バックエンド (lxml が高速、 BeautifulSoup が許容)。 CSS セレクタ (soup.select('table.data tr td:nth-child(2)')) が短く読みやすいので XPath より初心者向け。 正規表現で HTML を直接パースするのは、 ネストの取り扱いで破綻するため避ける (ただし「タグ内の data-id="123" だけ抽出」のような局所処理は OK)。
| 手法 | 速度 | 堅牢性 | 学習コスト | 推奨用途 |
|---|---|---|---|---|
| BeautifulSoup (html.parser) | 遅い | 中 | 低 | 学習用・小規模 |
| BeautifulSoup (lxml) | 速い | 高 | 低 | 汎用本番 |
| lxml.etree (XPath) | 最速 | 高 (要 well-formed) | 中 | 大規模・XML 由来 |
| parsel (Scrapy) | 速い | 高 | 中 | 大規模クロール |
| 正規表現 | 最速 | 低 | 低 | 局所抽出のみ |
日本の古い site では Shift_JIS や EUC-JP のページも残る。 requests の r.encoding は HTTP ヘッダや meta tag から推測するが、 サーバが嘘をついている (UTF-8 と宣言しつつ Shift_JIS で返す等) ケースが現実にある。 そのため r.apparent_encoding (chardet による中身ベース推測) を併用し、 不一致なら r.content.decode('cp932', errors='replace') 等で手動指定する。 SSDSE-B-2026 は UTF-8 (BOM 付) なので pd.read_csv(..., encoding='utf-8-sig') が安全。
日次収集では「URL ベース」「コンテンツハッシュベース」「公開日時ベース」の 3 種類の差分検知が考えられる。 URL ベース最も簡単だが、 同じ URL の中身が更新されるケース (記事の追記・訂正) を見落とす。 SHA-256 ハッシュベースは確実だが、 広告・タイムスタンプ等の本文に無関係な変動で誤検知する (本文だけ抽出してからハッシュ取ると緩和できる)。 ID + 公開日時 (RFC3339) で管理するのが理想形で、 後の研究データセットとしても再現性が高い。
このコードでやること: requests + tenacity で指数バックオフ付きリトライを実装する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import requests import random, time def fetch_with_retry(url, max_retry=5): for i in range(max_retry): try: r = requests.get(url, timeout=10) if r.status_code == 200: return r.text if r.status_code == 429: # too many requests wait = (2 ** i) + random.random() print(f'429 待機 {wait:.1f}秒') time.sleep(wait) continue if 500 <= r.status_code < 600: wait = (2 ** i) + random.random() time.sleep(wait) continue return None # 4xx (除 429) は即時諦め except requests.RequestException as e: time.sleep((2 ** i) + random.random()) return None html = fetch_with_retry('https://www.e-stat.go.jp/') print('取得長:', len(html) if html else 'failed') |
📤 出力例:
💬 jitter (ランダムな揺らぎ) を入れる理由は、 複数クライアントが同期して再試行することによる thundering herd を防ぐため。 production では tenacity ライブラリの宣言的 retry が読みやすい。
収集された CSV/JSON は「壊れていないことの保証」がない。 必須カラムが全て存在するか、 件数が前日比でゼロに突然落ちていないか (パース失敗の sentinel)、 型が崩れていないか、 をパイプラインの最後で必ず checkpoint する。 pandera や great_expectations のような契約検証ライブラリを使うと、 「全行で都道府県コードが 5 桁の数値」「人口は正の整数」のような期待値を宣言的に書ける。 失敗時は人間にアラートを送り、 downstream の分析を止める。
| ゲート | 検査内容 | 失敗時アクション |
|---|---|---|
| 行数 | 前日比 ±50% 以内 | 人手確認 |
| 必須カラム | 全列存在 | 即停止 |
| 型 | スキーマ一致 | 即停止 |
| 欠損率 | 10% 未満 | アラート |
| 外れ値 | 3σ 範囲内 | 記録のみ |
| 重複 | 主キー一意 | 即停止 |
技術的にコードを書くこと自体は数十行で済むが、 「何を取るか」「どこまで取るか」「どう品質保証するか」を事前に決めるのが本番。 SSDSE-B-2026 のような公的データを最大限活用しつつ、 補完情報だけスクレイピングで集める、 という分業が「速さ・倫理性・再現性」の三立を達成する現実的なアプローチ。
SPA (Single Page Application) のサイトでは、 初回 HTML が空っぽで JS 実行後に DOM が構築されるため、 通常の requests + BeautifulSoup では何も取得できない。 Selenium / Playwright / Puppeteer のようなヘッドレスブラウザでフル DOM をレンダリングする必要があるが、 これは CPU と RAM を大量に消費する (1 リクエストあたり 1 GB 以上)。 iframe 内コンテンツも同様の罠で、 親 DOM から子 iframe の中身は直接見えず、 driver.switch_to.frame() 等で文脈を切り替える必要がある。 大規模クロールではヘッドレスを「最後の手段」 として、 まず JSON API や RSS フィードが無いか必ず探す。
ログインが必要なサイトでは、 Cookie ベースのセッションを保持する必要がある。 requests.Session() オブジェクトを再利用すると Cookie が自動的に保持される。 ただし、 Cookie には ToS 違反や不正アクセス禁止法違反のリスクが伴うため、 「自分のアカウントで自分のデータを取得する」 場合を除き、 ログインを伴う自動収集は原則避ける。 OAuth で公式 API キーを取得できるサービス (Twitter API・Reddit API・GitHub API 等) があれば、 そちらを優先するのがプロの選択。
デフォルトの requests ライブラリの User-Agent は python-requests/2.x で、 これを許可しないサイトが多い。 一般的な作法として、 自分の連絡先 (URL や email) を含めた User-Agent を設定する: 'MyResearchBot/1.0 (contact: user@example.com)'。 これにより、 サイト管理者が異常を検知した際に直接連絡できる。 単に Chrome の UA を偽装するのは「正体を隠す」 振る舞いと見なされ、 後の信頼関係や法的議論で不利になる可能性があるため避けるべき。
スループットを上げるため並列化する場合、 単一ドメインへのアクセスは sequential のまま、 異なるドメインに対して並列化するのが安全。 asyncio + aiohttp や Scrapy の同時接続数設定 (CONCURRENT_REQUESTS_PER_DOMAIN=1) で適切に制限する。 1 ドメインに対し N 並列でアクセスすれば、 単純に N 倍の負荷をかけることになり、 DoS と判定されるリスクが急上昇する。 大規模クロール (数百万 URL) でも、 単一ドメインあたりは「1 秒に 1 リクエスト」 を遵守し、 並列度はドメイン間で確保する設計が原則。
スクレイパーは「失敗するもの」 と前提しておく。 サイト構造変更、 ネットワーク断、 一時的 BAN、 認証期限切れ — 失敗パターンは無数にある。 失敗時に「どの URL で、 いつ、 何のステータスコードが返り、 どのセレクタで XPath エラーが起きたか」 を構造化ログ (JSON Lines 形式) で記録しておけば、 後の調査が劇的に楽になる。 Sentry や Datadog 等のエラー監視サービスと連携すると、 失敗が一定数を超えた段階で自動通知が来る運用が組める。
スクレイピングで取得したデータをそのまま再配布するのは、 著作権の観点で問題になることが多い。 一方、 統計値 (平均・分散・件数等) を計算した「派生情報」 は事実情報として再利用しやすい。 ただし、 派生情報も「出典: ○○ サイトを 2026-05 にスクレイピングし、 N=10,000 サンプルで集計」 と明示すること。 SSDSE-B のように CC BY ライセンスが明確な公的データは、 利用条件さえ守れば自由に再配布できるので、 教育・研究用途では公的データを中核に据えるのがベスト。
サイトの DOM 構造は予告なく変わる。 「先週まで動いていたパーサが今日突然空配列を返す」 ことは日常茶飯事。 対策として、 (a) パーサ単体テストを書く (固定 HTML スナップショットを assertion)、 (b) 取得件数の異常を毎日チェック (前日比 -50% で警報)、 (c) Visual regression (スクリーンショット比較) で重大変更を検知、 という 3 重防御を組む。 これらが無いと、 ある日壊れたパーサが空データを蓄積し続け、 半年後に気付くという最悪パターンに陥る。
スクレイピングは強力な手段だが、 同等の情報が公的データセット (政府統計の総合窓口 e-Stat, RESAS, 各省庁オープンデータ) で得られるなら、 そちらを優先する設計思想を持つこと。 公的データは (a) 著作権・利用規約が明確、 (b) 集計品質が一定水準で保証、 (c) 引用しやすい (出典が公文書)、 (d) コミュニティの信頼を得やすい — の 4 つの利点がある。 教育・研究用途では「まず公的データで探し、 不足分のみ補助的にスクレイピング」 が王道。 SSDSE-B-2026 は都道府県別の社会経済データを 100 項目超で網羅しているので、 これだけで多くの卒論テーマが組める。
スクレイピングを単発スクリプトで完結させず、 ETL (Extract-Transform-Load) パイプラインの 「Extract」 ステージとして位置付ける。 (a) Extract: スクレイピングで raw HTML/JSON を取得し S3 等に蓄積、 (b) Transform: HTML → CSV、 型変換、 重複除去、 欠損補完、 (c) Load: 分析用 DB (BigQuery, Snowflake) に投入。 各ステージを Airflow / Prefect / Dagster で DAG として宣言すれば、 再実行・依存管理・障害復旧が容易になる。 「スクレイパー = データソース」 と抽象化すれば、 後工程の SQL ベース分析と同等に扱える。
日本では平成 21 年改正著作権法で「情報解析のための複製」 が認められ (47 条の 7、 現 30 条の 4)、 機械学習目的のスクレイピングはおおむね合法とされる。 ただし「著作者の利益を不当に害する」 場合は対象外。 米国の hiQ vs LinkedIn 訴訟は、 公開データのスクレイピングが CFAA 違反でないと判示したが、 不正アクセス禁止法 (日本) や DMCA (米国) との関係は今も流動的。 結論として、 「グレーかどうか不安なら法律家に確認」 「商用利用ならとくに慎重」 「公的データ優先」 の 3 原則で進めるのが安全。
技術的に取得できても、 「取らない選択」 が正しいことがある。 SNS の個人投稿、 子供の画像、 病気・宗教・性的指向に関わる情報 — これらは技術的にも法的にも難しい領域。 「データを集めて分析する自由」 と「情報主体の人格的権利」 のバランスを、 個別ケースごとに考える必要がある。 自分の研究や事業が、 もし大手新聞に取り上げられたとき、 自信を持って説明できるか? という「日経テスト」 が、 倫理判断の実用的なヒューリスティクス。
スクレイピングは「データを自分で取りに行く」 体験を通じて、 受講生のデータ感覚を一気に育てる優れた教材。 SSDSE-B-2026 に補助的な情報 (例: 各県の観光地数を Wikipedia から取得して相関分析) を組み合わせると、 公的データの限界と補完手段の必要性が体感できる。 推奨カリキュラムは、 (1) robots.txt と利用規約の読み方、 (2) BeautifulSoup で 1 ページ抽出、 (3) for ループで複数ページ、 (4) エラー処理とリトライ、 (5) 結果を CSV 保存、 (6) pandas で集計分析、 という 6 ステップ × 各 90 分で完結する。
🍰 まずはやさしく
Webページから情報を抽出する技術のことです。
データエンジニアリングという分野で使われます。
スマホの画面にある表をデータにするイメージです。
ここでは、この技術の定義について詳しく読みます。
Webページから情報を抽出する技術
英語名 Web Scraping、 カテゴリ:データエンジニアリング。
scraping の定義や代表的な数式を以下に示す。 数式の各記号の意味は次節で言葉に翻訳する。
scraping は文脈に応じて複数の定式化があるが、 教育目的では最も基本的な形を抑えることが重要。 具体的な値での計算例は後続セクションを参照。
$$\text{scraping}: f(\mathbf{X}, \boldsymbol{\theta}) \to y$$
記号の対応はこうです。 $t_{\text{wait}}$ は次のリクエストまで待つべき秒数、 $\text{RPS}_{\text{allowed}}$ は相手サイトが許容する 1 秒あたりリクエスト数、 $t_{\text{elapsed}}$ は前回のリクエストからすでに経過した時間です。 $1/\text{RPS}_{\text{allowed}}$ が「本来空けるべき間隔」で、 そこから経過ぶんを引いた残りだけ待ちます。 $\max(\cdot, 0)$ があるのは、 すでに十分待っていたら追加で待たないようにするためです。 RPS = 1(1 秒に 1 回)なら、 処理に 0.3 秒かかった直後の待ち時間は $1 - 0.3 = 0.7$ 秒。 「sleep(1) を機械的に入れる」より、 この式のほうが速く・かつ約束を破りません。 実際の上限は robots.txt の Crawl-delay や利用規約で決まります。
.class, #id, div > p)スクレイピングのライブラリ選択は、 「対象ページが静的か動的か」「JavaScript レンダリングが必要か」で決まる。 SSDSE-B-2026 のような 公開 CSV ダウンロードページは requests + BeautifulSoup で十分だが、 動的サイト (React/Vue で描画) は Selenium / Playwright が必要。 以下は判定フローと、 倫理ガイドラインに従った rate limiting 実装。
| ライブラリ | 適合ケース | 速度 | メモリ |
|---|---|---|---|
requests のみ | API / CSV / JSON 直接取得 | ★★★★★ (最速) | 最小 |
requests + bs4 | 静的 HTML パース (e-Stat 等) | ★★★★ (速い) | 小 |
Selenium | JavaScript 描画 / ログイン必須 | ★★ (遅い) | 大 (Chrome 起動) |
Playwright | SPA / 非同期描画 | ★★★ (中) | 中〜大 |
$$ t_{\text{wait}} = \max\left(\frac{1}{\text{RPS}_{\text{allowed}}} - t_{\text{elapsed}}, 0\right) $$
「許可された秒間リクエスト数 (RPS) の逆数 = 1 リクエスト当たりの最小間隔」から、 既に経過した時間を差し引いた分だけ追加で待つ。 e-Stat の robots.txt は 1 秒 1 リクエスト推奨。 これを守らないと IP ブロック (HTTP 429 Too Many Requests) のリスクがある。
このコードでやること: e-Stat の SSDSE-B-2026 ダウンロードページから CSV 直リンク URL を抽出し、 rate limit を守って取得する。 robots.txt を事前確認し、 User-Agent を明示。
📥 入力データ (e-Stat ページ URL):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import requests from bs4 import BeautifulSoup import time import urllib.robotparser BASE = 'https://www.nstac.go.jp/use/literacy/ssdse/' USER_AGENT = 'EducationalBot/1.0 (research; +mailto:contact@example.ac.jp)' # 1. robots.txt 確認 rp = urllib.robotparser.RobotFileParser() rp.set_url(BASE + 'robots.txt') rp.read() allowed = rp.can_fetch(USER_AGENT, BASE) print(f'robots.txt allows fetch: {allowed}') # 2. rate limit 付き取得 session = requests.Session() session.headers.update({'User-Agent': USER_AGENT}) start = time.time() resp = session.get(BASE, timeout=10) soup = BeautifulSoup(resp.text, 'html.parser') # CSV リンク抽出 csv_links = [a['href'] for a in soup.find_all('a', href=True) if a['href'].endswith('.csv')] print(f'Found {len(csv_links)} CSV links') # 1 秒間隔を守る elapsed = time.time() - start if elapsed < 1.0: time.sleep(1.0 - elapsed) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: robots.txt が許可していることを確認してから取得を行う。 User-Agent を明示すれば、 サーバ管理者から見て「教育目的のクローラ」と識別でき、 ブロックされにくい。 1 秒間隔の遵守は法的要件ではないが、 業界倫理。 違反すると偽計業務妨害罪 (岡崎図書館事件) のリスクすらある。
React / Vue / Next.js などの SPA は初回 HTML がほぼ空で、 JS 実行後にコンテンツが描画される。 requests.get() で取得すると <div id="root"></div> しか見えない。 この場合は Selenium / Playwright で「JS 実行後の DOM」を取得する必要がある。 まず curl で確認し、 空なら動的サイトと判定するワークフローが効率的。
スクレイピングのレートリミット制御に使われる待機時間の計算式を、 SSDSE-B-2026 の 47 都道府県コード(R01000 〜 R47000)を使って実際に計算する。
待機時間の計算式:
$$t_{\text{wait}} = \max\!\left(\frac{1}{\text{RPS}_{\text{allowed}}} - t_{\text{elapsed}},\; 0\right)$$RPSallowed = 1.0(1 秒に 1 リクエスト)、取得した 5 都道府県の実際の経過時間 $t_{\text{elapsed}}$(秒)を代入して計算する。
| 都道府県コード | 経過時間 $t_{\text{elapsed}}$ (秒) | 待機時間 $t_{\text{wait}}$ = max(1/1 − elapsed, 0) |
|---|---|---|
| R01000 (北海道) | 0.23 | max(1.00 − 0.23, 0) = 0.77 |
| R13000 (東京都) | 0.41 | max(1.00 − 0.41, 0) = 0.59 |
| R27000 (大阪府) | 0.89 | max(1.00 − 0.89, 0) = 0.11 |
| R40000 (福岡県) | 1.05 | max(1.00 − 1.05, 0) = 0.00 |
| R47000 (沖縄県) | 0.33 | max(1.00 − 0.33, 0) = 0.67 |
🎯 このコードでやること: 上の手計算(Step 1・Step 2)と同じ待機時間計算を numpy で再現し、 結果が一致することを確認する。
📥 入力データ: 5 都道府県の実取得時間(秒)のリスト
1 2 3 4 5 6 7 8 9 10 11 12 | import numpy as np # 5 都道府県の実取得時間 (秒) elapsed = np.array([0.23, 0.41, 0.89, 1.05, 0.33]) rps_allowed = 1.0 # 1 リクエスト/秒 # t_wait = max(1/rps - elapsed, 0) t_wait = np.maximum(1.0 / rps_allowed - elapsed, 0.0) print('各待機時間:', np.round(t_wait, 2)) print(f'合計待機時間: {t_wait.sum():.2f} 秒') print(f'平均待機時間: {t_wait.mean():.3f} 秒/件') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 手計算の合計 2.14 秒・平均 0.428 秒 と Python 出力が完全一致。 取得が速い(経過時間が短い)都道府県ほど、 1 秒間隔に均すための待機が長くなり、 経過時間が 1 秒を超えた都道府県(福岡県 1.05 秒など)はほぼ待機なし。 RPS を 0.5 に下げれば待機時間は 2 倍になる(サーバーへの敬意)。
| ステップ | 所要時間 | 注意点 |
|---|---|---|
| 1. requests で HTML 取得 | 0.5 秒 | User-Agent 設定 |
| 2. BeautifulSoup でパース | 0.1 秒 | table タグを探す |
| 3. 表データ抽出 | 0.05 秒 | pandas.read_html が便利 |
| 4. クレンジング | 0.1 秒 | カンマ、 単位を削除 |
| 5. CSV 保存 | 0.01 秒 | UTF-8 BOM 注意 |
1 件 1 秒以下で完了。 ただし 1000 件取得なら 1000 秒待つ必要があり、 並列化と 礼儀ある間隔のバランスが必要。
日本の公的統計データは、 主に e-Stat (政府統計の総合窓口) と SSDSE (教育用データセット) の 2 系統で公開されています。 スクレイピングの観点で比較すると:
| 項目 | e-Stat | SSDSE |
|---|---|---|
| 提供元 | 総務省統計局 | 独立行政法人統計センター |
| 収録範囲 | 全省庁の統計、 数万ファイル | 教育用に整形された主要指標 |
| 更新頻度 | 随時 (1 日数ファイル) | 年 1 回 |
| API | 公開 (appId 必要) | なし (CSV 直接 DL) |
| スクレイピング難度 | ★★★ (動的ページ多い) | ★ (CSV を 1 回 DL するだけ) |
| サンプル URL | https://www.e-stat.go.jp/api/... | https://www.nstac.go.jp/SSDSE/... |
| SSDSE-B-2026 例 | 個別ファイルを多数取得して結合必要 | 1 ファイル (564 行 × 112 列) で完結 |
本ページの SSDSE-B-2026 (47 都道府県 × 12 年 = 564 行、 112 指標) は、 e-Stat なら数十ファイルを結合する手間がかかります。 教育や研究の第一歩には SSDSE が圧倒的に楽。
合成データで N ページ取得の所要時間を計算する。
1 2 3 4 5 6 7 | pages = 10000 sec_per = 2 parallel = 5 serial_h = pages * sec_per / 3600 parallel_m = pages * sec_per / parallel / 60 print(f"直列: {serial_h:.1f} h") print(f"並列5: {parallel_m:.0f} 分") |
💬 手計算 (Step 2) 67 分と Python 出力が完全一致。
🎯 このコードでやること: requests + BeautifulSoup を使い、 HTTP リクエスト→ HTML パース→ テーブル抽出→ CSV 保存の最小スクレイピング処理を 1 つのスクリプトで実行する。 礼儀ある 1 秒間隔と User-Agent 設定も含む。
📥 入力データ: 任意の Web ページ URL(例: https://example.com/data)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import os os.makedirs('data', exist_ok=True) # 書き出し先を先に作る import os os.makedirs('data/raw', exist_ok=True) # 保存先のフォルダを作っておく import requests from bs4 import BeautifulSoup import pandas as pd import time url = 'https://example.com/data' headers = {'User-Agent': 'Mozilla/5.0 (research bot)'} r = requests.get(url, headers=headers) time.sleep(1) # 礼儀(1秒間隔) soup = BeautifulSoup(r.text, 'html.parser') table = soup.find('table', class_='data') df = pd.read_html(str(table))[0] df.to_csv('data/raw/scraped.csv', index=False, encoding='utf-8') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 1 秒の sleep を挟むことでサーバーへの礼儀を守りながら HTML を取得。 table が class="data" で特定できた場合に CSV として保存される。 実際のサイトではセレクタをそのページの DOM 構造に合わせて変更する。
🎯 このコードでやること: SSDSE-B-2026 を独立行政法人統計センターのサイトから直接ダウンロードし、 pandas で読み込んで都道府県人口を確認する。 最も基本的な「単一ファイル取得」型スクレイピング。
📥 入力データ: URL (https://www.nstac.go.jp/SSDSE/...) のみ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import requests import pandas as pd from pathlib import Path # 公開 URL からダウンロード (実際には独立行政法人統計センターの URL) url = 'https://www.nstac.go.jp/SSDSE/data/SSDSE-B-2026.csv' headers = {'User-Agent': 'Research bot (matsumoto@example.jp)'} response = requests.get(url, headers=headers, timeout=30) response.raise_for_status() # 保存 Path('data/raw').mkdir(parents=True, exist_ok=True) with open('data/raw/SSDSE-B-2026.csv', 'wb') as f: f.write(response.content) # 読み込み (cp932 エンコーディング、 2 行目は日本語ヘッダ) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) print('行数 × 列数:', df.shape) print(df[['Code', 'Prefecture', 'A1101']].head()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 47 都道府県 × 12 年 = 564 行のデータを 1 回の HTTP リクエストで取得。 CSV 直接 DL は最も「礼儀正しい」スクレイピング形態 (サーバー負荷最小)。
🎯 このコードでやること: 47 都道府県の人口ランキング Wikipedia ページから表データを抽出し、 SSDSE-B-2026 のローカルデータと突合して整合性を確認する。
📥 入力データ: 1) Wikipedia URL、 2) ローカル SSDSE-B-2026.csv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import requests from bs4 import BeautifulSoup import pandas as pd import time url = 'https://ja.wikipedia.org/wiki/都道府県の人口一覧' headers = {'User-Agent': 'Research bot'} r = requests.get(url, headers=headers, timeout=30) time.sleep(1) # 礼儀 soup = BeautifulSoup(r.text, 'html.parser') tables = soup.find_all('table', class_='wikitable') # pandas.read_html で HTML テーブル → DataFrame wiki_df = pd.read_html(str(tables[0]))[0] print('Wikipedia 取得行数:', len(wiki_df)) print(wiki_df.head()) # SSDSE と比較 ssdse = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) ssdse_2023 = ssdse[ssdse['SSDSE-B-2026'] == 2023] print('SSDSE 2023 行数:', len(ssdse_2023)) print('東京都人口 (SSDSE):', ssdse_2023[ssdse_2023.Prefecture=='東京都']['A1101'].values[0]) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 同じ「都道府県人口」でも複数ソース間で数値が微妙に違うことが分かる。 これは 推計時期 (10/1 or 1/1) や算出方法の違いによる。 スクレイピングで複数ソースを比較するのはデータ品質保証の基本。
🎯 このコードでやること: 47 都道府県分の HTML ページを順次取得する際に、 礼儀ある間隔 (1 秒) を保ち、 失敗時に指数バックオフでリトライする実務的なクローラを実装する。
📥 入力データ: SSDSE-B-2026 の Code 列 (R01000 〜 R47000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import requests import time import pandas as pd def fetch_with_retry(url, max_retries=3, base_delay=1.0): for attempt in range(max_retries): try: r = requests.get(url, timeout=10, headers={'User-Agent': 'Research bot'}) r.raise_for_status() return r.text except requests.RequestException as e: wait = base_delay * (2 ** attempt) # 指数バックオフ print(f' リトライ {attempt+1}/{max_retries} (待機 {wait}s): {e}') time.sleep(wait) return None # SSDSE から都道府県コード取得 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) codes = df[df['SSDSE-B-2026'] == 2023]['Code'].tolist() # 47 件 results = {} for code in codes[:5]: # 最初の 5 件だけテスト url = f'https://example.com/pref/{code}' # 仮想 URL print(f'取得中: {code}') html = fetch_with_retry(url) results[code] = '成功' if html else '失敗' time.sleep(1) # 礼儀の 1 秒 print('\n結果:', results) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 礼儀ある間隔と指数バックオフでサーバーに優しいクローラに。 「岡崎市立中央図書館事件」(2010 年、 1 秒に 1 回のクロールで偽計業務妨害として書類送検) の教訓を踏まえた実装。
🎯 このコードでやること: requests では取得できない JS で描画されるページを、 Playwright (ヘッドレスブラウザ自動操作) で取得する。 現代 Web の半分以上はこのパターン。
📥 入力データ: JavaScript で動的描画される URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from playwright.sync_api import sync_playwright import pandas as pd with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto('https://example.com/dynamic-stats') # JS の描画完了を待つ page.wait_for_selector('table.data-table', timeout=10000) # HTML を取得 html = page.content() browser.close() # pandas で表を抽出 df = pd.read_html(html)[0] print('JS 描画ページから取得した行数:', len(df)) print(df.head()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: Playwright は実ブラウザを起動するので JS が完全に動く。 ただし requests より 10〜100 倍遅い。 必要なときだけ使うべき。
日本のスクレイピングは 3 つの法律に注意が必要です:
| 法律 | 主な論点 | 違反のリスク |
|---|---|---|
| 著作権法 | 取得データの再配布 | 第 47 条の 4 「情報解析目的」なら多くは合法 |
| 不正アクセス禁止法 | 認証突破、 ID/PW 入力 | 違反は懲役 3 年以下 |
| 偽計業務妨害罪 | 過剰アクセスでサーバー停止 | 懲役 3 年以下 (岡崎図書館事件) |
| 個人情報保護法 | 個人を識別可能なデータ取得 | 違反は罰金 + 行政指導 |
| 不正競争防止法 | 営業秘密の不正取得 | 差止 + 損害賠償 |
判例ハイライト:
実践的ルール: ① robots.txt を必ず確認 ② 利用規約を読む ③ User-Agent に連絡先 ④ 1 秒に 1 回以下のアクセス頻度 ⑤ 取得データの再配布は避ける ⑥ 個人情報を含むデータは慎重に
| 観点 | API | スクレイピング |
|---|---|---|
| 合法性 | 明示的に許可された使い方 | グレーゾーン |
| 安定性 | 仕様変更は事前通知 | 突然壊れる |
| 速度 | JSON / Protocol Buffers で高速 | HTML パース必要 |
| データ品質 | 構造化済み | クレンジング必要 |
| 利用制限 | API key + レート制限 | サーバー側の任意 |
| コスト | 有料の場合あり | 無料 (法的リスクは別) |
| 取得範囲 | API が提供する範囲のみ | 表示できるものなら何でも |
結論: API があれば 必ず API 優先。 e-Stat、 Twitter (X)、 GitHub、 YouTube、 Google Maps など大手はほぼ API 公開済み。 スクレイピングは「API がない場合の最終手段」と心得る。
| ツール | 用途 | 長所 | 短所 |
|---|---|---|---|
| requests | HTTP リクエスト | シンプル、 デファクト | JS 実行不可 |
| httpx | requests の async 版 | 非同期で高速 | 学習コスト |
| aiohttp | 純粋な async HTTP | 大規模並列に強い | API が複雑 |
| BeautifulSoup | HTML パース | シンプル、 初心者向け | 大規模で遅い |
| lxml | HTML/XML 高速パース | C 実装で 10 倍速 | API が冗長 |
| parsel | Scrapy の抽出エンジン | CSS + XPath 両対応 | 知名度低い |
| Scrapy | 本格クローラフレームワーク | 大規模に最適 | 学習コスト高 |
| Selenium | ブラウザ自動操作 | 歴史長い、 情報豊富 | 遅い、 重い |
| Playwright | モダンなブラウザ自動操作 | 高速、 API 洗練 | 新しい |
| Puppeteer | Chrome 専用 (Node.js) | Chrome ベース安定 | Python 不向き |
| pyppeteer | Puppeteer の Python 版 | Python 派には便利 | 更新が遅い |
| pandas.read_html | HTML 表を直接 DataFrame | 超シンプル | 複雑な表に弱い |
https://example.com/robots.txt を確認SSDSE-B-2026 を補完するためのスクレイピング戦略例:
| シナリオ | 取得対象 | 取得手段 |
|---|---|---|
| 気象データ補完 | 気象庁の過去気温・降水量 | 気象庁 CSV ダウンロードページ |
| 選挙データ | 各都道府県知事選結果 | 総務省サイト + 各選管 |
| 観光統計 | 都道府県観光客数 | 観光庁データベース |
| SNS 動向 | 地域別 Twitter 投稿数 | X (旧 Twitter) API 推奨 |
| 不動産 | 家賃相場 | SUUMO、 LIFULL HOME'S (利用規約注意) |
| 求人 | 地域別求人倍率 | ハローワーク Web |
| 交通 | 新幹線時刻表 | JR 各社サイト |
| 食材価格 | 地域別食料品価格 | 農水省 価格動向 |
SSDSE 単体では取れない情報を補完することで、 コンペ分析の独自性と深さが増します。
find が None を返すならセレクタ更新技術的に可能でも、 倫理的に許されるとは限りません。 次の 5 つの問いを自問してください:
これらに「Yes」と答えられない場合、 一度立ち止まって考え直すべきです。 技術者倫理の根幹。
| 年 | 出来事 |
|---|---|
| 1993 | Web Wanderer (世界初のクローラ、 MIT) |
| 1994 | robots.txt 規格策定 (Martijn Koster) |
| 1998 | Google PageRank、 大規模クロールの幕開け |
| 2002 | Internet Archive Wayback Machine 公開 |
| 2004 | BeautifulSoup 初版リリース (Leonard Richardson) |
| 2008 | Scrapy 公開 |
| 2010 | 岡崎市立中央図書館事件 (日本) |
| 2011 | Selenium WebDriver 普及 |
| 2017 | Puppeteer 公開 (Google) |
| 2018 | 日本著作権法第 47 条の 4 改正 (情報解析目的の合法化) |
| 2020 | Playwright 公開 (Microsoft) |
| 2022〜 | LLM 学習用大規模 Web スクレイピングが社会問題化 |
| 2024 | OpenAI vs NYT 訴訟、 スクレイピングと著作権の論点が再燃 |
🎯 このコードでやること: スクレイピングで取得した SSDSE-B-2026 の数値カラムを自動クレンジングし、 異常値や欠損を検出する。 数式を言葉で読み解くと「3σ 外れ値検出」を行う実例。
📥 入力データ: SSDSE-B-2026.csv (564 行 × 112 列)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 欠損確認 missing = df.isnull().sum() print('欠損あるカラム:', missing[missing > 0].head()) # 数値カラムの基本統計 print('\n基本統計 (人口列 A1101):') print(df['A1101'].describe()) # 3σ ルールで外れ値検出 def detect_outliers(s): mu, sigma = s.mean(), s.std() return s[(s < mu - 3*sigma) | (s > mu + 3*sigma)] outliers = detect_outliers(df['A1101']) print(f'\n人口の外れ値 ({len(outliers)} 件):') print(outliers.head()) print('→ 東京都が外れ値として検出される') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 東京都の人口 (約 1400 万) は他県と比べて圧倒的に大きく、 3σ ルールで外れ値判定される。 これは「異常」ではなく「現実」なので、 統計手法によっては対数変換などの工夫が必要。
🎯 このコードでやること: スクレイピングで複数年のデータを取得した場合、 各年に 47 都道府県すべてが揃っているか自動チェックする。 取得漏れの早期検出。
📥 入力データ: SSDSE-B-2026.csv の年と都道府県の組み合わせ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 年×都道府県のクロス集計 pivot = df.pivot_table( index='SSDSE-B-2026', columns='Prefecture', values='A1101', aggfunc='count' ) # 47 都道府県揃っているか years_complete = (pivot.notna().sum(axis=1) == 47) print('完全な年:', years_complete.sum(), '/', len(years_complete)) # 欠損のある年があれば表示 if not years_complete.all(): incomplete = years_complete[~years_complete].index.tolist() print('不完全な年:', incomplete) # 取得年範囲 print('年範囲:', df['SSDSE-B-2026'].min(), '〜', df['SSDSE-B-2026'].max()) print('総レコード数:', len(df)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: スクレイピング後に「47 都道府県揃っている」「想定年数 12 年揃っている」を必ず検証。 サイトの構造変化や取得失敗で欠落することがあるため。
「スクレイピング」とは結局、 何が起こっているのか? 数式を言葉で読み解く視点で内部を見てみましょう。
クライアント (Python) はまずサーバーと TCP 接続を確立。 HTTPS なら追加で TLS ハンドシェイク (証明書検証 + 鍵交換)。 これだけで 100〜300 ms かかる。 だから「同じサーバーへの複数リクエスト」は Session で接続再利用が高速化のコツ。
クライアントは次のような文字列を送信:
サーバーは次を返す:
受信した HTML を DOM ツリーに変換。 BeautifulSoup は内部で C 実装の lxml や html.parser を使用。 数千要素なら数ミリ秒。
CSS セレクタや XPath で目的の要素を選択。 内部的にはツリー走査 (DFS/BFS)。 適切なセレクタなら O(n) で完了。
抽出したテキストを文字列正規化、 数値変換、 日付パース、 重複排除等。 ここが実は 一番工数がかかる。
スクレイピングの核心は「HTML の構造を手がかりに、欲しい要素だけを指名して抜き出す」こと。 下のデモは実際の Web にはアクセスせず、 このページに埋め込んだ架空のニュースページ風 HTML を対象に、 ブラウザの querySelectorAll(BeautifulSoup の soup.select() と同じ CSS セレクタ言語)で本当に抽出を実行します。 セレクタを選ぶ(または自由入力する)と、 左でマッチ要素が黄色くハイライトされ、 右に抽出結果と DOM ツリー図がリアルタイムで更新されます。
HTML は入れ子のツリー(DOM)であり、 CSS セレクタは「table の中の td」「class が price の要素」のように構造上の住所で要素を指名する言語。 上のデモで .price を押すと価格セルだけが正確に 3 件抽出されるのは、 ページ制作者が価格に共通の class を付けているから。 スクレイピングとは、 この「制作者が付けた構造の規則性」に相乗りしてデータを掘り出す行為であり、 Python の BeautifulSoup でも soup.select('.price') と全く同じセレクタがそのまま使える。
h2 / table td / .price / #sc3-main li が軒並み 0 件になる一方、 a[href] は生き残る。 実際のスクレイパーも同じで、 エラーではなく「空の結果」が返るため気付かず空データを蓄積し続けるのが最悪パターン。 取得件数の監視(前日比チェック)と、 属性ベースなど壊れにくいセレクタの選択が防御策。Disallow / Crawl-delay)と利用規約の確認が必須。 違反は民事・刑事リスクに直結する(本文②・⑱節参照)。querySelectorAll('table td') は、 Python では BeautifulSoup(html, 'lxml').select('table td') に対応する。 ブラウザの開発者ツール(F12)で試したセレクタを、 そのまま Python コードに移植するのが実務の定番ワークフロー。requests では何も取れず、 Selenium / Playwright で実ブラウザを駆動してから同じセレクタを適用する(本文⑨節参照)。requests では取れない。 Selenium / Playwright で対応。chardet や r.apparent_encoding で推定するが、 必ず正しいとは限らない。requests.Session() を使わずに毎回新規セッションは失敗する。href="../page" のような相対 URL は urllib.parse.urljoin で絶対 URL に変換。| ツール | 役割 | 使い所 |
|---|---|---|
| tqdm | 進捗バー | 大量取得時の可視化 |
| tenacity | リトライデコレータ | 失敗時の自動再試行 |
| fake-useragent | UA ローテーション | 取得拒否回避 (倫理注意) |
| chardet | 文字コード推定 | 日本語サイト対応 |
| cssselect | CSS → XPath 変換 | lxml で CSS セレクタ使用 |
| parsel | Scrapy の抽出器 | 軽量・高速 |
| requests-cache | レスポンスキャッシュ | 開発中の負荷軽減 |
| splash | JS レンダラ (Scrapy 統合) | 大規模 JS 対応 |
| pyppeteer | Puppeteer の Python 版 | Chrome 自動操作 |
| html2text | HTML → プレーンテキスト | 本文抽出 |
| newspaper3k | ニュース記事抽出 | 本文・著者・日付自動抽出 |
| extruct | 構造化データ抽出 | JSON-LD, microdata |
encoding='utf-8-sig'wait_for_selector なしだとデータが取れない'10' < '9' が True になる罠| 手段 | 速さ | 合法性 | 難易度 | 用途 |
|---|---|---|---|---|
| 公式 API | ★★★ | ★★★ | ★★ | 常に第一選択 |
| 公開 CSV ダウンロード | ★★★ | ★★★ | ★ | SSDSE, e-Stat 等 |
| RSS フィード | ★★★ | ★★★ | ★ | ニュース |
| Sitemap.xml + 静的 HTML | ★★ | ★★★ | ★★ | 大手サイト |
| requests + BeautifulSoup | ★★ | ★★ | ★★ | 静的サイト一般 |
| Scrapy | ★★ | ★★ | ★★★ | 大規模クロール |
| Playwright | ★ | ★★ | ★★★ | JS 動的サイト |
| 商用スクレイピングサービス | ★★ | ★★★ | ★ | 大規模で予算あり |
| アンチボット回避 | ★ | ★ (グレー) | ★★★★ | 非推奨 |
結論: 「API → 公開ダウンロード → RSS → 静的スクレイピング → 動的スクレイピング」の順で検討。 後ろの手段になるほど技術的にも法的にも複雑。
本ページで例として使った SSDSE-B-2026 について深く理解しておくと、 統計データコンペでの活用力が上がります。
| 項目 | 内容 |
|---|---|
| 正式名称 | SSDSE (Standardized Statistical Data Set for Education) |
| B-2026 の意味 | 都道府県別データ、 2026 年公開版 |
| 提供元 | 独立行政法人 統計センター (NSTAC) |
| 収録範囲 | 47 都道府県 × 12 年 (2012〜2023) = 564 行 |
| 指標数 | 112 指標 (人口、 経済、 教育、 医療、 環境等) |
| ファイル形式 | CSV (cp932 エンコーディング) |
| 1 行目 | 英語コード (A1101 等) |
| 2 行目 | 日本語ラベル (人口総数 等) |
| 主要カラム | SSDSE-B-2026 (年), Code (都道府県コード), Prefecture (都道府県名), A1101 (人口) |
| 取得方法 | nstac.go.jp/SSDSE/ から直接 DL |
| ライセンス | 政府標準利用規約 (CC BY 互換) |
| 用途 | 教育・研究、 統計データ解析コンペティション |
SSDSE-B-2026 はスクレイピング不要で取得できる優良データセット。 統計データ解析コンペティション 2026 の中心データでもあります。
| 都道府県 | 2023 年人口 | 順位 |
|---|---|---|
| 東京都 | 14,086,000 | 1 位 (圧倒的最大) |
| 神奈川県 | 9,229,000 | 2 位 |
| 大阪府 | 8,763,000 | 3 位 |
| 愛知県 | 7,477,000 | 4 位 |
| 埼玉県 | 7,331,000 | 5 位 |
| 千葉県 | 6,257,000 | 6 位 |
| 兵庫県 | 5,370,000 | 7 位 |
| 福岡県 | 5,103,000 | 8 位 |
| 北海道 | 5,092,000 | 9 位 |
| 静岡県 | 3,555,000 | 10 位 |
| ... | ... | ... |
| 福井県 | 744,000 | 43 位 |
| 徳島県 | 695,000 | 44 位 |
| 高知県 | 666,000 | 45 位 |
| 島根県 | 650,000 | 46 位 |
| 鳥取県 | 537,000 | 47 位 (最小) |
最大 (東京都) と最小 (鳥取県) の人口比は 26.2 倍。 47 都道府県平均は 2,645,808 人。 中央値は 1,549,000 人 (鹿児島県)。
Web スクレイピングは 「世界の構造化情報を自分の手で集める」強力な技術です。 統計データコンペで SSDSE-B-2026 を起点に複数ソースを統合できれば、 他チームより一段深い分析が可能になります。
ただし「技術的に取れる」と「取ってよい」は別問題。 robots.txt、 利用規約、 著作権法、 個人情報保護法、 礼儀の 5 つを常に意識してください。 これらを守らないスクレイピングは、 個人の信用も社会全体のデータ利活用環境も損ないます。
本ページが、 あなたが「データを取る側」になるための確かな出発点となれば幸いです。 SSDSE-B-2026 の取得から始めて、 徐々に複雑なサイトに挑戦してください。
スクレイピングで取得した SSDSE-B-2026.csv は一見シンプルな CSV ですが、 内部には複数の工夫が施されています。 数式を言葉で読み解く視点で解剖:
| 位置 | 内容 | 役割 |
|---|---|---|
| 1 行目 | SSDSE-B-2026, Code, Prefecture, A1101, A110101, ... | 英語コードヘッダ (機械可読) |
| 2 行目 | 年度, 地域コード, 都道府県, 人口総数, ... | 日本語ラベル (人間可読) |
| 3 行目以降 | 2023, R01000, 北海道, 5092000, ... | 実データ |
| エンコーディング | cp932 (Shift-JIS 系) | Excel との互換性 |
| 区切り | カンマ (CSV) | 標準形式 |
| 改行 | CRLF | Windows 互換 |
pandas で読むときは encoding='cp932' と skiprows=[1] (2 行目の日本語ラベルを飛ばす) が必須です。
最後に、 これからスクレイピングを始める方へのメッセージです:
スクレイピングは強力なツール。 だからこそ 責任ある使い方を心がけてください。 統計データコンペでも、 取得したデータの出典を明示することで信頼性が増します。
| 状況 | 対処 |
|---|---|
| HTML が崩れている | lxml の recover モード、 html5lib パーサ |
| 巨大ページ (10MB+) | ストリーミング読み込み、 部分パース |
| iframe で囲まれている | iframe の src を取得して再帰 |
| 無限スクロール | Playwright で scroll + wait |
| ライトボックス・モーダル | JS の click イベント発火 |
| 地域別表示の切替 | Cookie の region 値を設定 |
| 多言語サイト | Accept-Language ヘッダ |
| レスポンシブ HTML 違い | User-Agent でモバイル/PC 切替 |
| 圧縮レスポンス | requests は自動解凍、 raw 取得時は手動 |
| チャンク転送 | iter_content で逐次処理 |
スクレイピングは 「AI 学習データ争奪戦」の主戦場として、 法的・倫理的・技術的に大きく変わる予測:
「Web は自由」という創成期の精神と、 「データは資産」という現代の現実のせめぎ合いが続くでしょう。
requests + BeautifulSoup が定番Playwright や SeleniumScrapyWebスクレイピングを中心に、 上位概念・並列手法・下位技術の関係を示す。
実線矢印: 上位→下位の包含関係 / 破線: 並列・関連関係
大規模サイトは /sitemap.xml でサイト構造を公開しています。 これを利用すれば:
スクレイピングの「礼儀」として、 まず sitemap.xml と robots.txt を確認するのが標準的なワークフローです。
| サービス | API | 制限 |
|---|---|---|
| e-Stat | 政府統計 API | 無料、 appId 登録 |
| 気象庁 | JSON データ公開 | 無料 |
| X (Twitter) | API v2 | 有料 (Basic $200/月〜) |
| YouTube | Data API v3 | 無料 (1 万単位/日) |
| GitHub | REST + GraphQL | 無料 (5000 req/時) |
| Google Maps | Places API | 従量課金 |
| Amazon PA API | Product Advertising | 無料 (売上連動) |
| 楽天 WebService | 商品検索 API | 無料 (1 万 req/日) |
| Yahoo!ニュース | RSS | 無料 |
| API | 有料化 (2023〜) |
Python 以外、 CLI ツールでもスクレイピングは可能。 簡単なケースなら効率的。
| ツール | 用途 | 例 |
|---|---|---|
| curl | HTTP リクエスト | curl -O https://example.com/data.csv |
| wget | 再帰ダウンロード | wget -r -l 2 https://example.com/ |
| httpie | モダン curl | http GET example.com/api |
| jq | JSON 抽出 | jq '.data[].name' data.json |
| pup | HTML パース (Go) | curl URL | pup 'table tr text{}' |
| htmlq | HTML パース (Rust) | curl URL | htmlq -t '.title' |
| yt-dlp | 動画ダウンロード | yt-dlp https://youtu.be/... |
| 項目 | Selenium | Playwright | Puppeteer |
|---|---|---|---|
| 言語 | 多言語 | Python/JS/Java/.NET | JS のみ |
| ブラウザ | 全て | Chromium/Firefox/WebKit | Chromium のみ |
| 速度 | 遅め | 速い | 速い |
| API 洗練度 | 古い | モダン | モダン |
| 自動待機 | 明示的に書く | 自動 | 自動 |
| スクリーンショット | ○ | ○ | ○ |
| ネットワーク制御 | 限定的 | 強力 | 強力 |
| iframe 対応 | 限定的 | 強力 | 強力 |
| 学習曲線 | 緩い | 緩い | 普通 |
| コミュニティ | 大 | 急成長中 | 大 |
2025 年現在、 新規プロジェクトには Playwright が推奨。 Selenium は既存資産がある場合のみ。
| 分野 | 提供元 | 取得方法 |
|---|---|---|
| 人口・経済 | e-Stat | API + CSV ダウンロード |
| 人口・経済 (教育用) | SSDSE (本ページの例) | CSV 直接 DL |
| 気象 | 気象庁 | JSON API + CSV |
| 地理 | 国土地理院 | 地図 API + シェープファイル |
| 不動産取引 | 国土交通省 | CSV 検索 + DL |
| 医療 | 厚生労働省 | 各種統計データ DL |
| 教育 | 文部科学省 | 学校基本調査 CSV |
| 農業 | 農林水産省 | 農業統計データ DL |
| 環境 | 環境省 | 大気・水質モニタリング |
| 交通 | 国土交通省 | 道路・鉄道統計 |
| 選挙 | 総務省 | 選挙結果 CSV |
| 公開データ全般 | data.go.jp (オープンデータカタログ) | 各省データへのリンク集 |
「データが欲しい」と思ったらまず公的データソースを探す。 多くの場合スクレイピング不要で済みます。
| 分野 | 提供元 | 備考 |
|---|---|---|
| 世界統計 | World Bank Open Data | API + CSV、 200 ヶ国 |
| OECD | OECD.Stat | 先進国比較 |
| 国連 | UN Data | SDGs 関連豊富 |
| 米国 | data.gov | 連邦政府データ |
| EU | Eurostat | EU 統計 |
| 科学論文 | arXiv API, PubMed | 無料アクセス |
| 地理 | OpenStreetMap | 地図データ全部 |
| 機械学習 | Kaggle Datasets | 10 万件以上 |
| 機械学習 | Hugging Face Datasets | NLP 中心 |
| 画像 | ImageNet, COCO | ML ベンチマーク |
.class, #id 等「Web スクレイピング」は 法的・倫理的リスクを伴う最終手段のデータ取得 であり、 robots.txt 確認 → API 探索 → やむを得ず HTML 解析、 という意思決定フローの末端に位置する。 取得後は必ずクリーニング・DB 保存・更新管理と接続することで安定運用が成立する。
スクレイピングは「最終手段」であり、 robots.txt 確認 → API 探索 → スクレイピング → DB 保存 という上流から下流までの法的・技術的フローを設計してから着手する。
「スクレイピング」を実施するかは、 公式 API/オープンデータの有無と利用規約で判断する。
requests + BeautifulSoup または Selenium で取得、 No → 取得を諦めるかサイト管理者に問い合わせSelenium / Playwright で実ブラウザ駆動スクレイピングは最終手段。 e-Stat の SSDSE-B-2026 のように公式 CSV があれば必ずそちらを優先 (法的リスクと再現性の観点で圧倒的に有利)。
本ページ前半はスクレイピングの実装技術を扱った。 この追補では角度を変え、 「そもそもスクレイピングすべきか」という上流の意思決定に絞る。 結論を先に言えば、 目的のデータが公式オープンデータとして配布されているなら、 スクレイピングは技術的に可能でも選ぶべきではない。 手元の data/raw/SSDSE-B-2026.csv がまさにその実例なので、 実測値で確かめる。
スクレイピングの本質は、 提供側が整えてくれなかった構造化・クレンジング・結合を取得側が自力で埋める作業。 つまり「HTML という表示用の器から、 分析用の表を復元する」逆変換であり、 器 (ページ構造) が変わるたびに壊れる宿命を負う。 逆に言えば、 提供側が既に整形済みの表 (CSV/API) を出しているなら、 その逆変換コストはゼロになる。
SSDSE-B-2026 が「どれだけ整形済みか」を実測してみる。
欠損セルがゼロ、 47 行きっちり、 12 年分が縦持ちで揃っている。 これをもし各府省サイトから scraping で組み上げると、 表の HTML 構造・全角半角・脚注記号・年度またぎの定義変更を全部ハンドリングする必要があり、 それでも欠損 0 を保証するのは困難。 「整形済みで配布されている」こと自体が最大の価値だと分かる。
| 落とし穴 | 何が起きるか | 正しい対応 |
|---|---|---|
| 公式データがあるのに scraping する | 法的リスク・再現性低下・整形コスト増を自ら背負う | まず e-Stat / SSDSE 等を検索。 上の実測どおり整形済みなら即採用 |
robots.txt の Disallow を読まない | 禁止領域への自動アクセス=技術的にも倫理的にもアウト | 取得前に必ず can_fetch() 判定 (本文③参照) |
| レート制限を無視した高速収集 | 429/503 を誘発、 最悪 DoS と見なされ IP 遮断や法的責任 | 1 秒以上の間隔+指数バックオフ。 Crawl-delay があれば従う |
| 利用規約(ToS)の自動収集禁止条項を見落とす | robots.txt が許可でも ToS 違反で民事責任が生じ得る | robots.txt と ToS は別物。 両方確認する |
| ページ構造の変更に無防備 | セレクタが一夜で全滅し、 収集が静かに空振りする | 取得件数を毎回検証し、 0 件や急減で失敗検知 (本文の対話デモ参照) |
| エンコーディングの自己申告を鵜呑み | UTF-8 宣言なのに実体は Shift_JIS で文字化け | apparent_encoding 併用。 CSV 側も SSDSE は cp932 指定が必要 |
特に最上段が本追補の主眼。 「取れるから取る」ではなく、 公式配布の有無を先に調べて『取らない』を選べることが、 実務で最も差がつく判断力になる。
どうしても scraping が必要な場合 (公式配布が無い/更新が速いニュース等) は、 全件再取得ではなく差分検知で負荷を最小化する。 段階的に堅牢化できる。
If-Modified-Since / ETag を送り、 304 が返れば本文を受信せず「更新なし」と判定 (本文③のコード)。 帯域を最も節約できる。なお SSDSE のような静的 CSV は「更新されたら丸ごと差し替え」で十分なので、 上記のような差分ロジックは不要。 差分検知の複雑さもまた、 公式配布を選べば回避できるコストだと理解しておくとよい。
📝 補足:本セクションの数値は data/raw/SSDSE-B-2026.csv を pd.read_csv(encoding='cp932', skiprows=[1]) で読み、 df[df['SSDSE-B-2026']==2023] で 2023 年 47 都道府県を抽出して実測した値 (総人口は列 A1101)。 合成・架空の数値は含まない。