【初心者🔰】Copilotを使ってPythonでスクレイピングしてみました

# **はじめに**

買って寝かしてしまったIT本をようやく開いてさらっと読みしつつやってみました。 
・・・結果、めちゃくちゃ楽しくって、こんなに楽しいのに何故やらなかった私 (笑) 
きっとCopilotが手助け(いや,全部書いてくれた:sweat_drops:)し、私は検証しただけだからに違いない！
というわけで、プロンプトで作成・質問したこと、こちら投稿文章もCopilotにまとめてもらいました。
<br>
実は最初のコマンドプロンプトが消えてしまい:scream:ありません！
次回投稿時はその部分もしっかり意識していきたいです。

<br>
<br>
本は参考にさせてもらい、本の手順・設定すべてを行っているわけではありません。
<br>
■参照
<br>
著者: いまにゅ
<br>
書籍名: 今日からできる！ Python業務効率化スキルが身につく本
<br>
出版社: 株式会社 KADOKAWA
<br>
<br>
■使用したもの
<br>
Microsoft VSCode　1.96.1
<br>
Microsoft Copilot
<br>
Python　2024.22.0
<br>
<br>
■気を付けなければならないところ
<br>
対象URL先がスクレイピングOKか確認
<br>
アクセス時間と回数の制限に注意
<br>
<br>

# コード処理（架空のURL使用）
:::note warn
**注意：このコードは動作保証を行いません。
環境や設定によっては正常に動作しない場合がありますので、
ご自身の責任においてご使用ください。**
:::


```python
import re
import requests
from bs4 import BeautifulSoup
import csv
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from requests.adapters import HTTPAdapter, Retry

# スクレイピングしたい架空のECサイトの基本URLを指定
base_url = 'https://www.example.com/fake-path?p='

# リトライ戦略を設定
session = requests.Session()
retries = Retry(
    total=5,  # リトライ回数の上限を設定
    backoff_factor=1,
    status_forcelist=[500, 502, 503, 504]
)
session.mount('http://', HTTPAdapter(max_retries=retries))
session.mount('https://', HTTPAdapter(max_retries=retries))

# Robots.txtの解析
robots_url = 'https://www.example.com/robots.txt'
robots_response = session.get(robots_url)
robots_response.raise_for_status()
robots_text = robots_response.text

# スクレイピングしてはいけないページをリストアップ
disallowed_paths = []
for line in robots_text.split('\n'):
    if line.startswith('Disallow: '):
        disallowed_path = line.split(': ')[1].strip()
        disallowed_paths.append(disallowed_path)

# データを取得する関数
def get_page(url):
    if not url:
        print("エラー: URLが無効です。")
        return None
    try:
        response = session.get(url, timeout=10)
        response.raise_for_status()
        return response.text
    except requests.RequestException as e:
        print(f"Error fetching {url}: {e}")
        return None

# 書籍総数を取得する関数
def get_total_books(url):
    html = get_page(url)
    if html is None:
        return 0
    soup = BeautifulSoup(html, 'html.parser')
    counter_text = soup.find('div', class_='counter').text.strip()
    total_books_text = counter_text.split('／全')[-1].replace('件', '').strip()
    total_books = int(total_books_text.split()[0])  # 改行や余分な文字を除去
    return total_books

# 本の情報をスクレイピングする関数
def scrape_books(page_url):
    html = get_page(page_url)
    if html is None:
        return []

    soup = BeautifulSoup(html, 'html.parser')

    # 書籍情報のHTML構造に基づいてクラス名やタグを更新
    book_items = soup.find_all('div', class_='details ml10')
    book_data = []
    for book in book_items:
        try:
            title_tag = book.find_previous('h3', class_='heightLine-2').find('a')
            title = title_tag.text.strip() if title_tag else 'No title found'

            author_tag = book.find('p', class_='clearfix')
            author = author_tag.text.strip() if author_tag else 'No author found'

            price_tag = book.find_next('span', class_='sale_price')
            price = price_tag.text.strip() if price_tag else 'No price found'

            # 出版社と発売日を取得
            details2_tag = book.find_next('div', class_='details2 select_section1 ml10')
            publisher_release_info = details2_tag.find('li').text.strip()
            match = re.match(r'^(.*)（(\d{4}/\d{2})発売）$', publisher_release_info)
            if match:
                publisher = match.group(1)
                release_date = match.group(2)
            else:
                publisher = 'No publisher found'
                release_date = 'No release date found'

            book_data.append([title, author, price, publisher, release_date])
        except AttributeError as e:
            print(f"Error parsing book data from {page_url}: {e}")
    return book_data

# ページURLリストを生成
def generate_page_urls(start_url, max_pages):
    if not start_url:
        print("エラー: 基本URLが無効です。")
        return []
    return [start_url + str(page) for page in range(1, max_pages + 1)]

# メインロジック
start_url = base_url
total_books = get_total_books(start_url + '1')
items_per_page = 20
max_pages = min((total_books + items_per_page - 1) // items_per_page, 10)  # ページ数の計算と10ページまでの制限

all_book_data = []
data_found = False
page_urls = generate_page_urls(start_url, max_pages)

# 並行処理でページをスクレイピング
with ThreadPoolExecutor(max_workers=5) as executor:
    future_to_url = {executor.submit(scrape_books, url): url for url in page_urls if url}
    for future in as_completed(future_to_url):
        url = future_to_url[future]
        try:
            book_data = future.result()
            if book_data:
                all_book_data.extend(book_data)
                data_found = True
                print(f"{url} の書籍データが追加されました: {len(book_data)} 件")
            else:
                print(f"{url} からデータが見つかりませんでした。")
        except Exception as exc:
            print(f"{url} で例外が発生しました: {exc}")

# 収集したデータが存在する場合のみCSVファイルに保存
if data_found:
    with open('books.csv', mode='w', newline='', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerow(['Title', 'Author', 'Price', 'Publisher', 'Release Date'])
        writer.writerows(all_book_data)
    print("全ての書籍データがCSVファイルに保存されました。")
else:
    print("有効なデータが見つかりませんでした。CSVファイルは作成されませんでした。")
```
<br>

# スクレイピング処理の概要と詳細

| No  | 関数名                      | 役割                                                                                      | ポイントと注意点                                    |
| --- | --------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------- |
| 1   | `get_page`                  | 指定されたURLからHTMLを取得し、リクエストが失敗した場合にエラーメッセージを表示。              | 無効なURLの検出とエラーハンドリングを強化。          |
| 2   | `get_total_books`           | サイト内の書籍の総数を取得。                                                               | データ取得のための初期設定に使用。                  |
| 3   | `scrape_books`              | ページURLから書籍情報（タイトル、著者、価格、出版社、発売日）をスクレイピング。               | HTML構造に依存しているため、HTMLの変更に注意。       |
| 4   | `generate_page_urls`        | 基本URLと最大ページ数を基に、スクレイピングするページのURLリストを生成。無効なURLも検出。     | 正しいURLリストの生成と無効なURLの検出。             |
| 5   | `Robots.txt`の解析          | `robots.txt` ファイルを取得し、スクレイピングが禁止されているパスをリストアップ。               | サイトポリシーに従ったスクレイピングを実現。          |
| 6   | リトライ戦略の設定           | リクエストが失敗した場合に再試行する回数を設定（最大5回）。                                   | 適切なリトライ戦略を設定し、サーバーの負荷を軽減。     |
| 7   | 並行処理の設定              | 複数のページを並行してスクレイピングし、結果を収集。エラーハンドリングも実施。               | スクレイピングの効率を向上し、信頼性を確保。          |
| 8   | CSVファイル保存処理         | 収集したデータをCSVファイルに保存。有効なデータが無い場合はファイルを作成せずにエラーメッセージを表示。 | データの信頼性と保存処理の安定性を確保。             |
| 9   | 実行ファイルの作成          | `pyinstaller`を使用してPythonスクリプトを実行ファイルに変換。                              | 実行環境に依存しないスクリプトの提供。               |