STEP 5:API・Webスクレイピング基礎

🌐 STEP 5: API・Webスクレイピング基礎

WebからデータをAPI経由で取得、またはHTMLを解析してデータ抽出する方法を学びます

📋 このステップで学ぶこと

  • REST APIの基本概念と呼び出し方法
  • requestsライブラリの使い方
  • JSONレスポンスの解析とPandasへの変換
  • ページネーションの実装パターン
  • 認証方式(APIキー、Bearer Token、Basic認証)
  • Beautiful Soupを使ったHTML解析
  • 倫理的なWebスクレイピングのルール

🎯 1. REST APIの基本

1-1. REST APIとは?

REST API(RESTful API)は、Webサービスがデータを外部に公開するための仕組みです。
STEP 2で学んだ「レストランの注文」の例えを覚えていますか?APIはまさにその仕組みを使っています。

🍽️ 復習:レストランの注文フロー

あなた(クライアント)メニュー(API仕様書)を見て注文 →
ウェイター(API)が注文を受け取り →
キッチン(サーバー)で調理 →
料理(データ)が届く

1-2. HTTPリクエストの種類

REST APIでは、目的に応じて異なるHTTPメソッドを使います。

メソッド 目的 ETLでの使用頻度
GET データを取得する ユーザー一覧を取得 ★★★★★(非常に多い)
POST データを作成する 新しいユーザーを登録 ★★☆☆☆(時々)
PUT データを更新する ユーザー情報を更新 ★☆☆☆☆(まれ)
DELETE データを削除する ユーザーを削除 ★☆☆☆☆(まれ)
📝 ETLではGETがメイン

ETLの「E(Extract:抽出)」では、GETメソッドを使ってデータを取得することがほとんどです。
POST、PUT、DELETEは、ETLの「L(Load:ロード)」で使うことがあります。

1-3. requestsライブラリのインストール

Pythonで REST API を呼び出すには、requestsライブラリを使います。
非常にシンプルで使いやすいライブラリです。

# pipでインストール pip install requests # Google Colabの場合(先頭に!をつける) !pip install requests

1-4. 基本的なGETリクエスト

まずは、最もシンプルなAPIコールから始めましょう。

# ===== 基本的なGETリクエスト ===== import requests # GETリクエストを送信(GitHubの公開API) response = requests.get(‘https://api.github.com/users/octocat’) # ステータスコードを確認 print(f”ステータスコード: {response.status_code}”) # JSONデータを取得 data = response.json() # データを表示 print(f”ユーザー名: {data[‘login’]}”) print(f”名前: {data[‘name’]}”) print(f”フォロワー数: {data[‘followers’]}”)
【実行結果】 ステータスコード: 200 ユーザー名: octocat 名前: The Octocat フォロワー数: 9999
📝 コードの説明
  • requests.get(URL):指定したURLにGETリクエストを送信
  • response.status_code:HTTPステータスコード(200=成功)
  • response.json():レスポンスのJSONをPython辞書に変換
  • data['login']:辞書からデータを取り出す

1-5. HTTPステータスコード

APIからのレスポンスには、処理結果を示すステータスコードが含まれます。

コード 意味 説明・対処法
200 OK(成功) 正常にデータが取得できた
201 Created データの作成に成功した(POSTの成功)
400 Bad Request リクエストの形式が間違っている → パラメータを確認
401 Unauthorized 認証エラー → APIキーやトークンを確認
403 Forbidden アクセス権限がない → 権限を確認
404 Not Found リソースが見つからない → URLを確認
429 Too Many Requests レート制限に達した → 時間をおいて再試行
500 Internal Server Error サーバー側のエラー → 時間をおいて再試行

1-6. URLパラメータを指定する

多くのAPIは、パラメータを指定して検索条件や取得件数を制御できます。

# ===== パラメータを指定してリクエスト ===== import requests # パラメータを辞書で定義 params = { ‘q’: ‘python’, # 検索キーワード ‘sort’: ‘stars’, # ソート順(スター数) ‘order’: ‘desc’, # 降順 ‘per_page’: 5 # 1ページあたり5件 } # パラメータ付きでリクエスト response = requests.get( ‘https://api.github.com/search/repositories’, params=params ) data = response.json() # 検索結果を表示 print(f”総件数: {data[‘total_count’]}件”) print() for repo in data[‘items’]: print(f”リポジトリ名: {repo[‘name’]}”) print(f”説明: {repo[‘description’][:50]}…”) print(f”スター数: {repo[‘stargazers_count’]:,}”) print(“—“)
📝 paramsの使い方

paramsに辞書を渡すと、自動的にURLの末尾に?q=python&sort=stars...のように追加されます。
URLに直接書くより、辞書で渡す方が読みやすく、エスケープも自動で行われます。

1-7. エラーハンドリング

本番環境では、ネットワークエラーやサーバーエラーに備えて、エラーハンドリングが必須です。

# ===== 安全なAPIコール ===== import requests def fetch_api_data(url, params=None, timeout=10): “”” 安全にAPIからデータを取得する関数 Parameters: url: APIのURL params: URLパラメータ(辞書) timeout: タイムアウト秒数 Returns: 成功時: JSONデータ(辞書) 失敗時: None “”” try: # リクエストを送信(タイムアウト設定は必須!) response = requests.get(url, params=params, timeout=timeout) # ステータスコードをチェック(200番台以外は例外を発生) response.raise_for_status() # JSONデータを返す return response.json() except requests.exceptions.Timeout: print(f”エラー: タイムアウト({timeout}秒)”) return None except requests.exceptions.HTTPError as e: print(f”HTTPエラー: {e}”) return None except requests.exceptions.ConnectionError: print(“エラー: ネットワーク接続に失敗”) return None except requests.exceptions.RequestException as e: print(f”リクエストエラー: {e}”) return None except ValueError: print(“エラー: JSONのパースに失敗”) return None # 使用例 data = fetch_api_data(‘https://api.github.com/users/octocat’) if data: print(f”ユーザー名: {data[‘login’]}”) else: print(“データの取得に失敗しました”)
⚠️ timeout設定は必須!

timeoutを設定しないと、サーバーが応答しない場合に永遠に待ち続けます
必ずtimeout=10のように秒数を指定しましょう(通常は5〜30秒)。

📄 2. JSONレスポンスの処理

2-1. JSONとは?

JSON(JavaScript Object Notation)は、Web APIで最も一般的なデータ形式です。
人間にも読みやすく、プログラムでも処理しやすい形式です。

【JSONの構造】 { “user_id”: 1, ← 数値 “name”: “山田太郎”, ← 文字列 “email”: “yamada@example.com”, ← 文字列 “is_active”: true, ← 真偽値 “tags”: [“VIP”, “Premium”], ← 配列(リスト) “orders”: [ ← 配列の中にオブジェクト { “order_id”: 101, “product”: “ノートPC”, “price”: 89800 }, { “order_id”: 102, “product”: “マウス”, “price”: 1980 } ] }
📝 JSONの基本ルール
  • オブジェクト{ }で囲む(Pythonの辞書に対応)
  • 配列[ ]で囲む(Pythonのリストに対応)
  • キーは必ず"ダブルクォート"で囲む
  • 文字列"ダブルクォート"で囲む
  • 数値true/falsenullはそのまま

2-2. JSONからデータを取り出す

response.json()で取得したデータは、Pythonの辞書として扱えます。

# ===== JSONデータの解析 ===== import requests response = requests.get(‘https://api.example.com/user/1’) data = response.json() # 単純な値を取得(辞書のキーでアクセス) user_id = data[‘user_id’] # 1 name = data[‘name’] # “山田太郎” email = data[‘email’] # “yamada@example.com” is_active = data[‘is_active’] # True # 配列(リスト)を取得 tags = data[‘tags’] # [“VIP”, “Premium”] print(f”タグ: {tags[0]}”) # “VIP” # ネストしたデータ(配列の中のオブジェクト)を取得 orders = data[‘orders’] for order in orders: order_id = order[‘order_id’] product = order[‘product’] price = order[‘price’] print(f”注文ID: {order_id}, 商品: {product}, 価格: {price}円”)

2-3. 安全にデータを取り出す(.get()メソッド)

存在しないキーにアクセスするとエラーになります。
.get()メソッドを使うと、キーがない場合にデフォルト値を返せます。

# ===== 安全なデータ取得 ===== data = {‘name’: ‘山田太郎’, ‘email’: ‘yamada@example.com’} # 存在しないキーにアクセス → エラー! # phone = data[‘phone’] # KeyError: ‘phone’ # .get()を使えば安全 phone = data.get(‘phone’) # None(キーがない場合) phone = data.get(‘phone’, ‘未設定’) # ‘未設定’(デフォルト値を指定) print(f”名前: {data.get(‘name’, ‘不明’)}”) print(f”電話: {data.get(‘phone’, ‘未設定’)}”)
✅ .get()を使うべき場面
  • APIのレスポンスにオプショナルなフィールドがある場合
  • データの形式が一定でない可能性がある場合
  • エラーで処理を止めたくない場合

2-4. PandasのDataFrameに変換

APIから取得したJSONデータを、Pandas DataFrameに変換して分析できます。

# ===== JSONをDataFrameに変換 ===== import requests import pandas as pd # APIからデータ取得 response = requests.get(‘https://api.github.com/users/octocat/repos’) data = response.json() # JSONリストをDataFrameに変換 df = pd.DataFrame(data) # 必要なカラムだけを選択 df_selected = df[[‘name’, ‘description’, ‘stargazers_count’, ‘language’]] # カラム名を日本語に変更 df_selected.columns = [‘リポジトリ名’, ‘説明’, ‘スター数’, ‘言語’] print(df_selected.head())

2-5. ネストしたJSONを平坦化する

複雑な階層構造のJSONを、平らな表形式に変換するにはjson_normalizeを使います。

# ===== ネストしたJSONの平坦化 ===== import pandas as pd from pandas import json_normalize # 複雑な構造のJSONデータ data = { ‘user_id’: 1, ‘name’: ‘山田太郎’, ‘address’: { ‘city’: ‘東京’, ‘zip’: ‘100-0001’ }, ‘orders’: [ {‘order_id’: 101, ‘product’: ‘ノートPC’, ‘price’: 89800}, {‘order_id’: 102, ‘product’: ‘マウス’, ‘price’: 1980} ] } # 方法1:基本的な平坦化 df1 = json_normalize(data) print(“=== 基本的な平坦化 ===”) print(df1.columns.tolist()) # [‘user_id’, ‘name’, ‘address.city’, ‘address.zip’, ‘orders’] # 方法2:ネストしたリストを展開 df2 = json_normalize( data, record_path=[‘orders’], # 展開するリスト meta=[‘user_id’, ‘name’] # 親の情報を含める ) print(“\n=== ordersを展開 ===”) print(df2)
【実行結果】 === ordersを展開 === order_id product price user_id name 0 101 ノートPC 89800 1 山田太郎 1 102 マウス 1980 1 山田太郎
📝 json_normalizeのポイント
  • record_path:展開するネストしたリストのパス
  • meta:親レベルから含めるフィールド
  • ネストした辞書はaddress.cityのように「.」区切りで展開される

📑 3. ページネーション

3-1. ページネーションとは?

ページネーション(Pagination)とは、大量のデータを複数のページに分けて返す仕組みです。
1回のリクエストで全データを返すと、サーバーに負荷がかかるため、この仕組みが使われます。

📚 例え話:図書館の本棚

図書館に1万冊の本があるとします。

ページネーションなし:「全部の本を見せて!」と言われて、1万冊を一度に運ぶ → 大変!

ページネーションあり:「1ページ目(1〜100冊)を見せて」「次のページを見せて」と少しずつ → 効率的!

3-2. ページネーションの3つの方式

方式 仕組み 使用例
オフセット方式 offset(開始位置)とlimit(件数)を指定 多くのREST API
ページ番号方式 page(ページ番号)とper_page(1ページの件数)を指定 GitHub API、多くのWeb API
カーソル方式 次のデータを指すカーソル(トークン)を使用 Twitter API、Facebook API

3-3. オフセット方式の実装

# ===== オフセット方式のページネーション ===== import requests import time def fetch_all_data_offset(base_url, page_size=100): “”” オフセット方式で全データを取得する Parameters: base_url: APIのベースURL page_size: 1ページあたりの件数 Returns: 全データのリスト “”” all_data = [] offset = 0 while True: # パラメータを指定 params = { ‘limit’: page_size, # 1ページあたりの件数 ‘offset’: offset # 開始位置 } response = requests.get(base_url, params=params, timeout=10) response.raise_for_status() data = response.json() # データがなければ終了 if not data.get(‘items’, []): break # データを追加 all_data.extend(data[‘items’]) # 次のページへ offset += page_size print(f”取得済み: {len(all_data)}件”) # レート制限対策(1秒待機) time.sleep(1) return all_data # 使用例 # all_users = fetch_all_data_offset(‘https://api.example.com/users’) # print(f”合計: {len(all_users)}件”)

3-4. ページ番号方式の実装

# ===== ページ番号方式のページネーション ===== import requests import time def fetch_all_data_page(base_url, per_page=100): “”” ページ番号方式で全データを取得する Parameters: base_url: APIのベースURL per_page: 1ページあたりの件数 Returns: 全データのリスト “”” all_data = [] page = 1 while True: # パラメータを指定 params = { ‘page’: page, # ページ番号 ‘per_page’: per_page # 1ページあたりの件数 } response = requests.get(base_url, params=params, timeout=10) response.raise_for_status() data = response.json() # データがなければ終了 if not data: break # データを追加 all_data.extend(data) print(f”ページ {page} 取得完了(累計: {len(all_data)}件)”) # 次のページへ page += 1 # レート制限対策 time.sleep(1) return all_data # 使用例(GitHub APIの実例) # repos = fetch_all_data_page(‘https://api.github.com/users/octocat/repos’, per_page=30)

3-5. カーソル方式の実装

# ===== カーソル方式のページネーション ===== import requests import time def fetch_all_data_cursor(base_url, per_page=100): “”” カーソル方式で全データを取得する Parameters: base_url: APIのベースURL per_page: 1ページあたりの件数 Returns: 全データのリスト “”” all_data = [] next_cursor = None while True: # パラメータを指定 params = {‘count’: per_page} # カーソルがあれば追加 if next_cursor: params[‘cursor’] = next_cursor response = requests.get(base_url, params=params, timeout=10) response.raise_for_status() data = response.json() # データを追加 items = data.get(‘items’, []) all_data.extend(items) print(f”取得済み: {len(all_data)}件”) # 次のカーソルを取得 next_cursor = data.get(‘next_cursor’) # 次のカーソルがなければ終了 if not next_cursor: break # レート制限対策 time.sleep(1) return all_data
⚠️ レート制限に注意!

多くのAPIにはレート制限(Rate Limit)があります。
例:「1時間に100リクエストまで」「1分間に60リクエストまで」

対策:

  • time.sleep(1)でリクエスト間に待機時間を入れる
  • レスポンスヘッダーのX-RateLimit-Remainingを確認
  • 429エラー(Too Many Requests)が出たら待機して再試行

🔐 4. 認証(APIキー、Bearer Token)

4-1. なぜ認証が必要なのか?

多くのAPIは、誰がアクセスしているかを確認するため、認証を要求します。

  • 不正利用の防止:誰でもアクセスできると悪用される
  • 利用量の制限:ユーザーごとにリクエスト数を制限
  • 課金:有料プランの利用者を識別

4-2. APIキー認証

APIキーは、サービスから発行される文字列です。
ヘッダーまたはURLパラメータに含めて送信します。

方法1:ヘッダーに含める(推奨)

# ===== APIキーをヘッダーに含める ===== import requests # APIキー(実際は環境変数から取得) api_key = ‘YOUR_API_KEY_HERE’ # ヘッダーにAPIキーを含める headers = { ‘X-API-Key’: api_key } response = requests.get( ‘https://api.example.com/data’, headers=headers, timeout=10 ) data = response.json() print(data)

方法2:URLパラメータに含める

# ===== APIキーをURLパラメータに含める ===== import requests api_key = ‘YOUR_API_KEY_HERE’ # パラメータにAPIキーを含める params = { ‘api_key’: api_key, ‘query’: ‘python’ } response = requests.get( ‘https://api.example.com/search’, params=params, timeout=10 ) data = response.json() print(data)
📝 ヘッダー vs パラメータ
  • ヘッダーが推奨される理由:URLに機密情報が残らない、ログに記録されにくい
  • どちらを使うかはAPIの仕様書を確認してください

4-3. Bearer Token認証

Bearer Tokenは、OAuth2.0などで使用される認証方式です。
AuthorizationヘッダーにBearer {トークン}の形式で含めます。

# ===== Bearer Token認証 ===== import requests # アクセストークン(実際は環境変数から取得) access_token = ‘YOUR_ACCESS_TOKEN_HERE’ # Authorizationヘッダーに含める headers = { ‘Authorization’: f’Bearer {access_token}’ } response = requests.get( ‘https://api.example.com/me’, headers=headers, timeout=10 ) data = response.json() print(data)

4-4. Basic認証

Basic認証は、ユーザー名とパスワードを使った認証方式です。

# ===== Basic認証 ===== import requests from requests.auth import HTTPBasicAuth # 方法1:HTTPBasicAuthを使う response = requests.get( ‘https://api.example.com/data’, auth=HTTPBasicAuth(‘username’, ‘password’), timeout=10 ) # 方法2:タプルで指定(簡潔) response = requests.get( ‘https://api.example.com/data’, auth=(‘username’, ‘password’), timeout=10 ) data = response.json() print(data)

4-5. APIキーの安全な管理

APIキーやトークンは秘密情報です。
コードに直接書かず、環境変数や設定ファイルで管理しましょう。

# ===== 環境変数から取得(推奨)===== import os # 環境変数から取得 api_key = os.environ.get(‘MY_API_KEY’) if not api_key: raise ValueError(“環境変数 MY_API_KEY が設定されていません”) # 使用 headers = {‘X-API-Key’: api_key}
# ===== .envファイルを使う(python-dotenv)===== # まず .env ファイルを作成 # MY_API_KEY=your_actual_api_key_here # MY_SECRET=another_secret_value from dotenv import load_dotenv import os # .envファイルを読み込む load_dotenv() # 環境変数として取得 api_key = os.getenv(‘MY_API_KEY’) secret = os.getenv(‘MY_SECRET’)
⚠️ APIキー管理の重要なルール
  • コードに直接書かない
  • .envファイル.gitignoreに追加してGitにコミットしない
  • 漏洩した場合はすぐに無効化して再発行
  • チームで共有する場合はパスワードマネージャーを使う

🕷️ 5. Beautiful Soupの基礎

5-1. Webスクレイピングとは?

Webスクレイピングとは、Webページから必要な情報を自動で抽出する技術です。
HTMLを解析して、テキストやリンク、画像URLなどを取り出します。

✅ APIを使う場合
  • サービスが公式に提供
  • データ形式が明確(JSON)
  • 安定している
  • 合法的で推奨
⚠️ スクレイピングを使う場合
  • APIがない場合の代替手段
  • HTMLの構造が変わると動かない
  • 利用規約の確認が必要
  • サーバーへの負荷に注意
💡 基本方針

APIがあればAPIを使うのが基本です。
APIがない場合にのみ、スクレイピングを検討しましょう。

5-2. Beautiful Soupのインストール

# Beautiful Soupのインストール pip install beautifulsoup4 # HTMLパーサー(lxml推奨) pip install lxml

5-3. 基本的な使い方

# ===== Beautiful Soupの基本 ===== import requests from bs4 import BeautifulSoup # WebページのHTMLを取得 url = ‘https://example.com’ response = requests.get(url, timeout=10) # BeautifulSoupでHTMLを解析 soup = BeautifulSoup(response.content, ‘lxml’) # タイトルを取得 title = soup.title.string print(f”タイトル: {title}”) # すべてのリンクを取得 links = soup.find_all(‘a’) for link in links: href = link.get(‘href’) # href属性を取得 text = link.string # リンクテキストを取得 print(f”{text}: {href}”)
📝 コードの説明
  • BeautifulSoup(HTML, 'lxml'):HTMLを解析してオブジェクトを作成
  • soup.title<title>タグを取得
  • .string:タグ内のテキストを取得
  • soup.find_all('a'):すべての<a>タグをリストで取得
  • link.get('href'):href属性の値を取得

5-4. タグを検索する方法

# ===== タグの検索方法 ===== from bs4 import BeautifulSoup html = ”’ <html> <body> <div class=”product” id=”item1″> <h2>ノートPC</h2> <p class=”price”>89,800円</p> <a href=”/detail/1″>詳細を見る</a> </div> <div class=”product” id=”item2″> <h2>マウス</h2> <p class=”price”>1,980円</p> <a href=”/detail/2″>詳細を見る</a> </div> </body> </html> ”’ soup = BeautifulSoup(html, ‘lxml’) # 1. find() – 最初の1つだけ取得 first_product = soup.find(‘div’, class_=’product’) print(f”最初の商品: {first_product.h2.string}”) # ノートPC # 2. find_all() – すべて取得 all_products = soup.find_all(‘div’, class_=’product’) for product in all_products: name = product.h2.string price = product.find(‘p’, class_=’price’).string print(f”{name}: {price}”) # 3. IDで検索 item2 = soup.find(‘div’, id=’item2′) print(f”ID=item2: {item2.h2.string}”) # マウス # 4. select() – CSSセレクタで検索 prices = soup.select(‘.product .price’) for price in prices: print(price.string)

5-5. CSSセレクタの使い方

セレクタ 意味
.class クラス名で検索 .product → class=”product”
#id IDで検索 #header → id=”header”
tag タグ名で検索 h2 → <h2>タグ
親 子 子孫要素を検索 .product .price
親 > 子 直下の子要素のみ .product > h2
[属性] 属性で検索 a[href^="https"] → httpsで始まるリンク

5-6. テキストの取得と整形

# ===== テキストの取得方法 ===== from bs4 import BeautifulSoup html = ‘<div> 価格: <span>1,980円</span> </div>’ soup = BeautifulSoup(html, ‘lxml’) div = soup.find(‘div’) # .string – 直接の子テキストのみ(子要素があるとNone) print(f”string: {div.string}”) # None # .get_text() – すべてのテキストを取得 print(f”get_text: ‘{div.get_text()}'”) # ” 価格: 1,980円 ” # .get_text(strip=True) – 前後の空白を削除 print(f”strip: ‘{div.get_text(strip=True)}'”) # “価格: 1,980円” # 数値を抽出する例 price_text = soup.find(‘span’).get_text(strip=True) # “1,980円” price = int(price_text.replace(‘円’, ”).replace(‘,’, ”)) # 1980 print(f”数値: {price}”)

5-7. 実践例:商品リストのスクレイピング

# ===== 実践的なスクレイピング ===== import requests from bs4 import BeautifulSoup import pandas as pd import time def scrape_products(url): “”” 商品リストページから情報を抽出する Parameters: url: スクレイピング対象のURL Returns: 商品情報のDataFrame “”” # ヘッダーを設定(User-Agent) headers = { ‘User-Agent’: ‘MyBot/1.0 (your-email@example.com)’ } response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.content, ‘lxml’) # 商品データを格納するリスト products = [] # 各商品を取得 for item in soup.select(‘.product-item’): # 商品名 name_elem = item.select_one(‘.product-name’) name = name_elem.get_text(strip=True) if name_elem else ‘不明’ # 価格 price_elem = item.select_one(‘.price’) if price_elem: price_text = price_elem.get_text(strip=True) price = int(price_text.replace(‘円’, ”).replace(‘,’, ”)) else: price = 0 # リンク link_elem = item.select_one(‘a’) link = link_elem.get(‘href’) if link_elem else None products.append({ ‘name’: name, ‘price’: price, ‘link’: link }) # DataFrameに変換 return pd.DataFrame(products) # 使用例 # df = scrape_products(‘https://example-shop.com/products’) # print(df)

⚖️ 6. 倫理的なスクレイピング

6-1. スクレイピングのルール

Webスクレイピングは便利な技術ですが、ルールを守って使う必要があります。

⚠️ スクレイピングの注意点
  • robots.txtを確認する
  • サイトの利用規約を確認する
  • サーバーに負荷をかけない(短時間に大量リクエスト禁止)
  • 個人情報を取得しない
  • 取得したデータを無断で公開・販売しない
  • 著作権に注意する

6-2. robots.txtの確認

robots.txtは、サイトがクローラー(ボット)に対して「ここはアクセスしないで」と指示するファイルです。

# robots.txtの確認方法 # ブラウザで以下のURLを開く
Example Domain
# 例: User-agent: * # すべてのボットに対して Disallow: /admin/ # /admin/以下はアクセス禁止 Disallow: /api/ # /api/以下はアクセス禁止 Allow: / # その他は許可 Crawl-delay: 10 # 10秒間隔を空けてアクセス
# Pythonでrobots.txtを確認 from urllib.robotparser import RobotFileParser rp = RobotFileParser() rp.set_url(‘https://example.com/robots.txt’) rp.read() # 特定のURLにアクセスしてよいか確認 url = ‘https://example.com/products’ can_fetch = rp.can_fetch(‘*’, url) print(f”{url}: {‘許可’ if can_fetch else ‘禁止’}”)

6-3. サーバーに優しいスクレイピング

# ===== サーバーに優しいスクレイピング ===== import requests from bs4 import BeautifulSoup import time def polite_scrape(urls, delay=2): “”” 礼儀正しいスクレイピング Parameters: urls: スクレイピングするURLのリスト delay: リクエスト間の待機秒数 “”” # 自分を識別できるUser-Agentを設定 headers = { ‘User-Agent’: ‘MyResearchBot/1.0 (contact: your-email@example.com)’ } results = [] for i, url in enumerate(urls): try: print(f”処理中 ({i+1}/{len(urls)}): {url}”) response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.content, ‘lxml’) # データ処理… title = soup.title.string if soup.title else ‘No Title’ results.append({‘url’: url, ‘title’: title}) except requests.exceptions.RequestException as e: print(f”エラー: {e}”) results.append({‘url’: url, ‘title’: ‘Error’}) # 待機(サーバーへの負荷を軽減) if i < len(urls) - 1: # 最後のURLの後は待たない time.sleep(delay) return results # 使用例 # urls = ['https://example.com/page1', 'https://example.com/page2'] # results = polite_scrape(urls, delay=2)
✅ ベストプラクティス
  • APIがあればAPIを使う(公式の方法)
  • スクレイピング前にrobots.txtと利用規約を確認
  • 2秒以上の待機時間を設ける
  • User-Agentに連絡先を含める
  • エラーが出たらすぐに停止する
  • 取得したデータは私的利用のみ

📝 STEP 5 のまとめ

✅ このステップで学んだこと
  • requests:PythonでREST APIを呼び出すライブラリ
  • HTTPステータスコード:200=成功、4xx=クライアントエラー、5xx=サーバーエラー
  • JSONresponse.json()でPython辞書に変換
  • ページネーション:オフセット、ページ番号、カーソルの3方式
  • 認証:APIキー、Bearer Token、Basic認証
  • Beautiful Soup:HTMLを解析してデータ抽出
  • CSSセレクタ.class#idtagで検索
  • 倫理的スクレイピング:robots.txt確認、待機時間、User-Agent設定
💡 実務でのポイント

1. APIがあればAPIを使う
スクレイピングは最後の手段。公式APIがあれば必ずそちらを使いましょう。

2. エラーハンドリングとtimeoutは必須
本番環境では、ネットワークエラーやサーバーエラーに備えましょう。

3. APIキーは環境変数で管理
コードに直接書かず、.envファイルや環境変数を使いましょう。

🎯 次のステップの予告

次のSTEP 6では、「大容量ファイル処理」を学びます。

  • チャンク処理(chunksize)
  • dtype指定によるメモリ削減
  • 並列読み込み
  • ストリーミング処理

メモリに収まらない巨大なファイルを効率的に処理する方法を習得しましょう!

📝 練習問題

問題 1 基礎

requestsを使って、GitHub APIからユーザー情報を取得し、名前とフォロワー数を表示してください。

URL: https://api.github.com/users/torvalds

【解答例】
import requests response = requests.get(‘https://api.github.com/users/torvalds’, timeout=10) data = response.json() print(f”ユーザー名: {data[‘login’]}”) print(f”名前: {data[‘name’]}”) print(f”フォロワー数: {data[‘followers’]:,}”)
問題 2 基礎

JSONデータから安全にデータを取り出す方法を書いてください。キーが存在しない場合は「不明」と表示すること。

【解答例】
data = {‘name’: ‘山田太郎’, ‘email’: ‘yamada@example.com’} # .get()を使って安全に取り出す name = data.get(‘name’, ‘不明’) phone = data.get(‘phone’, ‘不明’) print(f”名前: {name}”) print(f”電話: {phone}”)
問題 3 基礎

Beautiful Soupを使って、HTMLからすべてのh2タグのテキストを取得してください。

【解答例】
from bs4 import BeautifulSoup html = ”’ <html> <body> <h2>見出し1</h2> <p>本文1</p> <h2>見出し2</h2> <p>本文2</p> </body> </html> ”’ soup = BeautifulSoup(html, ‘lxml’) h2_tags = soup.find_all(‘h2’) for h2 in h2_tags: print(h2.get_text(strip=True))
問題 4 基礎

HTTPステータスコード200、401、404、429はそれぞれ何を意味しますか?

【解答】
  • 200:OK(成功)
  • 401:Unauthorized(認証エラー)
  • 404:Not Found(リソースが見つからない)
  • 429:Too Many Requests(レート制限に達した)
問題 5 応用

エラーハンドリング付きのAPI呼び出し関数を作成してください。タイムアウトとHTTPエラーに対応すること。

【解答例】
import requests def safe_api_call(url, timeout=10): “””安全にAPIを呼び出す関数””” try: response = requests.get(url, timeout=timeout) response.raise_for_status() return response.json() except requests.exceptions.Timeout: print(“タイムアウトしました”) return None except requests.exceptions.HTTPError as e: print(f”HTTPエラー: {e}”) return None except requests.exceptions.RequestException as e: print(f”リクエストエラー: {e}”) return None # 使用例 data = safe_api_call(‘https://api.github.com/users/octocat’) if data: print(data[‘login’])
問題 6 応用

ページ番号方式のページネーションを実装して、全ページのデータを取得する関数を作成してください。

【解答例】
import requests import time def fetch_all_pages(base_url, per_page=30): “””全ページのデータを取得する””” all_data = [] page = 1 while True: params = {‘page’: page, ‘per_page’: per_page} response = requests.get(base_url, params=params, timeout=10) data = response.json() if not data: break all_data.extend(data) print(f”ページ {page} 取得完了”) page += 1 time.sleep(1) # レート制限対策 return all_data # 使用例 # repos = fetch_all_pages(‘https://api.github.com/users/octocat/repos’)
問題 7 応用

Bearer Token認証を使ってAPIにアクセスするコードを書いてください。トークンは環境変数から取得すること。

【解答例】
import requests import os # 環境変数からトークンを取得 access_token = os.environ.get(‘API_TOKEN’) if not access_token: raise ValueError(“環境変数 API_TOKEN が設定されていません”) # Bearer Token認証 headers = { ‘Authorization’: f’Bearer {access_token}’ } response = requests.get( ‘https://api.example.com/me’, headers=headers, timeout=10 ) data = response.json() print(data)
問題 8 応用

Beautiful Soupを使って、商品名と価格を抽出するコードを書いてください。価格は数値に変換すること。

【解答例】
from bs4 import BeautifulSoup html = ”’ <div class=”product”> <h3 class=”name”>ノートPC</h3> <span class=”price”>89,800円</span> </div> <div class=”product”> <h3 class=”name”>マウス</h3> <span class=”price”>1,980円</span> </div> ”’ soup = BeautifulSoup(html, ‘lxml’) products = [] for item in soup.select(‘.product’): name = item.select_one(‘.name’).get_text(strip=True) price_text = item.select_one(‘.price’).get_text(strip=True) price = int(price_text.replace(‘円’, ”).replace(‘,’, ”)) products.append({‘name’: name, ‘price’: price}) for p in products: print(f”{p[‘name’]}: {p[‘price’]}円”)
問題 9 発展

APIから取得したネストしたJSONを、pandas.json_normalizeを使って平坦化してください。

【解答例】
import pandas as pd from pandas import json_normalize data = { ‘user_id’: 1, ‘name’: ‘山田太郎’, ‘orders’: [ {‘order_id’: 101, ‘product’: ‘ノートPC’, ‘price’: 89800}, {‘order_id’: 102, ‘product’: ‘マウス’, ‘price’: 1980} ] } # ordersを展開し、親の情報も含める df = json_normalize( data, record_path=[‘orders’], meta=[‘user_id’, ‘name’] ) print(df)
問題 10 発展

礼儀正しいスクレイピングを行う関数を作成してください。User-Agent設定、待機時間、エラーハンドリングを含めること。

【解答例】
import requests from bs4 import BeautifulSoup import time def polite_scrape(urls, delay=2): “””礼儀正しいスクレイピング””” headers = { ‘User-Agent’: ‘MyBot/1.0 (contact@example.com)’ } results = [] for i, url in enumerate(urls): try: print(f”処理中 ({i+1}/{len(urls)}): {url}”) response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.content, ‘lxml’) title = soup.title.string if soup.title else ‘No Title’ results.append({‘url’: url, ‘title’: title, ‘status’: ‘OK’}) except Exception as e: print(f”エラー: {e}”) results.append({‘url’: url, ‘title’: None, ‘status’: str(e)}) if i < len(urls) - 1: time.sleep(delay) return results # 使用例 # results = polite_scrape(['https://example.com', 'https://example.org'])
📝

学習メモ

ETL・データパイプライン構築 - Step 5

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE