Closed4

スクレイピング

うすいうすい

やんどころなき理由により、Webサイトのスクレイピングを学習してみる。
近年は、フロントエンド技術でSPAなど動的な生成のサイトも増えており、スクレイピングしづらい環境は多い。どうやってスクレイピングしようか……。

こんな時も基本は、AIに質問だ。
「 このURLのテキストのスクレイピングをするpythonプログラミングを書いてほしいです
 以下、URL 」

うすいうすい

相談相手:perplexity (内部ではClaude3.5 Sonnet)
Webサイトを調べに行く必要がある場合には、perplexity を使うことが多い。
が、純粋にプログラムを作ってもらう場合、直接Claude3.5のサイトを使う方が良い答えが返ってくる気がする。

想定通り、ちゃんと作ってくれた。
こちらで指定したサイトは普通にスクレイピングできるサイトだったらしい。

以下のPythonプログラムで、指定したURLのテキストをスクレイピングし、次のエピソードのリンクをクリックしてテキストを順次スクレイピングできます。

~~~
プログラミングは中略
~~~

このプログラムの流れは以下の通りです。
requestsを使って指定したURLからHTMLを取得します。
BeautifulSoupを使ってHTMLを解析します。
findメソッドで本文のテキストが含まれる要素を見つけ、get_textメソッドでテキストを抽出して表示します。
次のエピソードへのリンクをfindメソッドで探します。
リンクが見つかった場合は、そのURLを次の対象として1秒待機してからループを継続します。
リンクが見つからなかった場合は、最後のエピソードと判断してループを終了します。
全てのエピソードのスクレイピングが完了したら、メッセージを表示して終了します。
このプログラムを実行すると、指定した小説の各エピソードの本文テキストを順番にスクレイピングして表示します。
次のエピソードがなくなるまで自動的に処理が進みます。
ただし、Webサイトの利用規約に反しないよう注意し、過度なアクセスは控えるようにしてください。
適度な間隔を空けてアクセスするのが望ましいでしょう。

うすいうすい

実行したらエラーが出た。

    text = soup.find("div", class_="widget-episodeBody js-episode-body").get_text(strip=True)
AttributeError: 'NoneType' object has no attribute 'get_text'

どうやら読み取りたいページが存在しないデータもあるようだ。
そういう場合はスキップする。

うすいうすい

最終的なスクリプト。
引数で csvファイルを渡す。
csvファイルを読み込んで、一行ずつのURLを渡し、そのURLのWebサイトをスクレイピングする仕組み。
読み取るテキストがなかった場合は次の処理にスキップし、結果はファイルに書き込む。

本当に雑で、作成は二時間くらい。

import requests
from bs4 import BeautifulSoup
import time
import csv
import sys
import re
import os

# CSVファイルからURLを読み込む関数
def read_urls_from_csv(csv_file):
    with open(csv_file, 'r') as file:
        reader = csv.reader(file)
        return [row[0] for row in reader]

# URLからテキストをスクレイピングする関数
def scrape_text_from_url(url):
    # 前回取得したテキストを保存する変数
    prev_text = ""

    while True:
        # 2秒待機
        time.sleep(2)
        
        try:
            # ページのHTMLを取得
            response = requests.get(url)
            soup = BeautifulSoup(response.content, "html.parser")
            
            # 本文のテキストを抽出
            text_element = soup.find("div", class_="widget-episodeBody js-episode-body")
            if text_element is None:
                print(f"指定のクラスが見つかりませんでした。URL: {url}")
                break
            
            text = text_element.get_text(strip=True)
            
            # 取得したテキストが前回と同じ場合はループを終了
            if text == prev_text:
                print(f"テキストが前回と同じなのでスクレイピングを終了します。URL: {url}")
                break
            
            # 取得したテキストを保存
            prev_text = text
            
            # URLからwork_idとepisode_idを抽出
            work_id = re.findall(r'/works/(\d+)', url)[0]
            episode_id = re.findall(r'/episodes/(\d+)', url)[0]
            
            # 作品IDのフォルダを作成
            work_folder = work_id
            os.makedirs(work_folder, exist_ok=True)
            
            # 出力ファイル名を作成
            output_file = f"{work_folder}/{episode_id}.txt"
            
            # テキストをファイルに書き込み
            with open(output_file, "w", encoding="utf-8") as file:
                file.write(text)
            
            # 次のエピソードのリンクを取得
            next_link = soup.find("link", rel="next")
            
            if next_link:
                # 次のエピソードのURLを取得
                url = next_link["href"]
            else:
                print(f"次のエピソードのリンクが見つかりませんでした。現在のURL: {url}")
                break
        
        except requests.exceptions.RequestException as e:
            print(f"リクエストエラー: {e}")
            break

if __name__ == "__main__":
    # コマンドライン引数からCSVファイルを取得
    csv_file = sys.argv[1]
    
    # CSVファイルからURLを読み込む
    urls = read_urls_from_csv(csv_file)
    
    # 各URLに対してスクレイピングを実行
    for url in urls:
        print(f"スクレイピング開始: {url}")
        scrape_text_from_url(url)

    print("すべてのURLのスクレイピングが完了しました。")
このスクラップは5ヶ月前にクローズされました