Python Requests モジュールによる Web スクレイピング入門

Python Requests モジュールによる Web スクレイピング入門

Python において HTTP リクエストを送信するための標準的なライブラリとして、requests モジュールが広く利用されています。本記事では、requests の基本的な使い方から、実践的なデータ収集の手法、およびよくある問題の解決策について解説します。

環境構築とインストール

開発環境として Anaconda を利用する場合、Jupyter Notebook などのツールを通じてインタラクティブにコードを実行できます。Cell モードにはコード実行用の「Code」と記述用の「Markdown」があり、ショートカットキーを活用することで効率的に開発を進められます。

requests モジュールは標準ライブラリではないため、以下のコマンドでインストールが必要です。

pip install requests

基本的な GET リクエストとレスポンス

Web ページのソースコードを取得する最も基本的な操作は、URL を指定して GET リクエストを送信することです。

コード例:ページソースの取得

import requests

def fetch_html_content(target_url):
    # HTTP GET リクエストの送信
    resp = requests.get(url=target_url)
    
    # レスポンステキストの取得
    html_content = resp.text
    
    # ファイルへの保存
    output_file = 'output_page.html'
    with open(output_file, 'w', encoding='utf-8') as file_obj:
        file_obj.write(html_content)
    
    return html_content

if __name__ == '__main__':
    site_url = 'https://www.sogou.com/'
    fetch_html_content(site_url)

Response オブジェクトの主要属性

リクエスト送信後、戻り値として得られる Response オブジェクトには、サーバーからの応答に関する重要な情報が格納されています。

  • status_code: HTTP ステータスコード(200 は成功、404 はNotFound など)
  • text: デコードされた文字列形式のレスポンス本体
  • content: バイト形式のレスポンス本体(画像やファイル保存に使用)
  • json(): JSON 形式のデータを Python の辞書やリストに変換
  • encoding: レスポンスのエンコーディング設定
  • apparent_encoding: コンテンツ内容から推測されたエンコーディング

文字化け対策とエンコーディング

取得したデータが文字化けする場合、レスポンスのエンコーディング設定が適切でない可能性があります。その際は response.encoding を明示的に設定することで解消できます。

import requests

def search_and_save(keyword):
    base_url = 'https://www.sogou.com/web'
    query_params = {
        'query': keyword
    }
    
    # リクエスト送信
    response = requests.get(url=base_url, params=query_params)
    
    # エンコーディングの強制設定
    response.encoding = 'utf-8'
    
    # ファイル名を動的に生成
    file_name = f'{keyword}_result.html'
    with open(file_name, 'w', encoding='utf-8') as f:
        f.write(response.text)

# 使用例
# search_and_save('python')

UA 検知への対策(ヘッダー偽装)

多くの Web サイトでは、アクセス元のユーザーエージェント(User-Agent)を検査し、プログラムからのアクセスを制限する仕組み(アンチスクレイピング)を持っています。これを回避するため、ブラウザの UA をヘッダーに設定してリクエストを送信します。

import requests

def access_with_ua(url, search_term):
    # ブラウザの User-Agent を模倣
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    
    params = {
        'query': search_term
    }
    
    # headers を含めてリクエスト
    resp = requests.get(url=url, params=params, headers=headers)
    resp.encoding = 'utf-8'
    
    return resp.text

動的コンテンツと API リクエスト

近年の Web サイトでは、ページ遷移なしでデータを読み込む AJAX 技術が多用されています。このような場合、ブラウザの開発者ツールネットワークタブを分析し、実際にデータを取得している API エンドポイントを特定する必要があります。

事例:映画ランキングデータの取得

特定の URL にパラメータを送ることで JSON 形式のデータが返ってくる場合、response.json() メソッドを利用して簡単にデータを処理できます。

import requests

def fetch_movie_rankings(start_index, count):
    api_endpoint = 'https://movie.douban.com/j/chart/top_list'
    
    payload = {
        'type': '13',
        'interval_id': '100:90',
        'action': '',
        'start': start_index,
        'limit': count,
    }
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    resp = requests.get(url=api_endpoint, params=payload, headers=headers)
    data_list = resp.json()
    
    for item in data_list:
        title = item.get('title', 'Unknown')
        score = item.get('score', 0)
        print(f"{title}: {score}")

# fetch_movie_rankings(0, 5)

事例:POST リクエストによる店舗検索

フォームデータを送信する場合は POST メソッドを使用します。データは data 引数に辞書形式で渡します。

import requests

def search_store_locations(keyword, page_num):
    api_url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
    
    form_data = {
        'cname': '',
        'pid': '',
        'keyword': keyword,
        'pageIndex': str(page_num),
        'pageSize': '10',
    }
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    response = requests.post(url=api_url, headers=headers, data=form_data)
    result_json = response.json()
    
    # 結果の処理ロジックは API の仕様に応じて変更
    return result_json

複雑なデータ収集フロー

企業の詳細情報を収集する場合、まず一覧ページから ID を取得し、その後 ID を利用して詳細 API を叩くという 2 段階のプロセスが必要なことがあります。

import requests

def collect_company_details():
    base_headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }
    
    # 1. 企業一覧を取得する API
    list_api = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
    
    # 2. 詳細情報を取得する API
    detail_api = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
    
    for page_idx in range(1, 3):
        print(f'Processing page {page_idx}...')
        
        list_params = {
            'on': 'true',
            'page': str(page_idx),
            'pageSize': '15',
            'productName': '',
            'conditionType': '1',
        }
        
        # 一覧データの取得
        list_resp = requests.post(list_api, headers=base_headers, data=list_params)
        companies = list_resp.json().get('list', [])
        
        for company in companies:
            company_id = company.get('ID')
            
            # 詳細データの取得
            detail_params = {'id': company_id}
            detail_resp = requests.post(detail_api, headers=base_headers, data=detail_params)
            info = detail_resp.json()
            
            name = info.get('epsName', 'N/A')
            legal_rep = info.get('legalPerson', 'N/A')
            print(f"{name} - 代表者:{legal_rep}")

# collect_company_details()

バイナリデータの保存(画像ダウンロード)

画像や動画などのファイルを保存する際は、response.content を使用し、ファイルモードをバイナリ書き込み(wb)に設定します。

import requests

def download_image(image_url, save_path):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    resp = requests.get(url=image_url, headers=headers)
    
    # バイナリデータとして保存
    with open(save_path, 'wb') as img_file:
        img_file.write(resp.content)

# 使用例
# download_image('http://example.com/image.jpg', 'downloaded.jpg')

なお、urllib.request.urlretrieve を利用する方法もありますが、ヘッダー制御が難しいため、柔軟性のある requests

データ収集の一般的な分析フロー

未知のサイトからデータを収集する際は、以下の手順で分析を行うことが有効です。

  1. 動的加载の判定: ブラウザの開発者ツール(Network タブ)を使用し、ページ内の特定テキストが初期 HTML に含まれているか、別途リクエストでロードされているかを確認します。
  2. パケットの特定: 検索機能を用いて、目的のデータを含む API リクエストを特定します。
  3. パラメータの解析: 特定されたリクエストの URL、メソッド(GET/POST)、ヘッダー、パラメータを分析し、スクリプトで再現可能な形式に変換します。
  4. 実装と検証: 分析結果に基づいてコードを作成し、実際にデータが取得できるか検証します。

requests モジュールの処理フローまとめ

効果的なデータ収集スクリプトを作成するための標準的な手順は以下の通りです。

  • URL の特定: 目標とするデータのエンドポイントを決定します。
  • リクエストの構築:
    • GET: requests.get(url, params, headers)
    • POST: requests.post(url, data, headers)
  • レスポンスの取得: text, json, content など用途に合わせてデータを抽出します。
  • データの永続化: ファイル保存やデータベースへの登録を行います。

パラメータの動的変更が必要な場合は辞書オブジェクトを活用し、アクセス制限対策として headers に適切な User-Agent を設定することを忘れないようにしてください。また、リダイレクトが発生するケースではヘッダー設定が影響を与える可能性があるため注意が必要です。

タグ: Python requests web-scraping HTTP API

6月7日 16:34 投稿