ChatGPTで雑にスクレイピングスクリプトを作る Ver.2

こんにちは。

このところおもしろい話題が続いている[静岡アドベントカレンダー](https://qiita.com/advent-calendar/2023/shizuoka)です。
みなさんもう読みましたか？

- [浜松市の行政区の再編によるエンジニアの苦悩](https://qiita.com/kazuomatz/items/8b31712a6277200c0cc7)
    - レアそうでレアでない、あなたの身の回りにもいつ起こるかわからないピンチに役立つ知見を共有してくださっています。
- [Shopifyは予想以上にお金がかかるかもという話](https://hamnaly.com/blog/etc/entry-151.html)
    - みんなが言いづらいことを、よくぞ言ってくださいました。お客さんの様子を見て、「用法・用量を守って、適切に」って本当にその通り。
- [4年間の振り返り](https://sizu.me/hota/posts/iiwstrove953)
    - 登場してくれた @hota1024 さま。本当にありがとう。そしてすごい。世界に羽ばたきつつときどき静岡で癒されてほしいです。

さて、本日はまたまた、アドベントカレンダー前の最後の投稿が５年前だったはるまきです。
Qiitaの最近の雰囲気をやっと理解できてきました。

そんな訳で今日のタイトル。
もちろん、inspired by @toniokatanuki [ChatGPTで雑にスクレイピングスクリプトを作る](https://qiita.com/toniokatanuki/items/1165bb14602ceec0831c) です。


# 事例：構築中のウェブサイトをスクレイピングして、コンソールエラーを書き出す

たとえば、あるカタログ（紙）のデータをごそっと持ってきて、WEBサイトに作り直すという仕事があるとします。
この場合、発生しがちな問題として

- 画像が404になってしまう（ファイル名の間違い、データの移行ミス、CSV書き出しで文字列が変わってしまう）
- 意図したURLが生成されて動いているか確認したい
- とにかくページ数が多い

というようなことが挙げられます。
そこで、全ページをスクレイピングして、コンソールエラーを書き出すスクリプトを作ってみます。

# まずはアーキテクチャについて聞いてみた

#### プロンプト

```
サイトを全ページスクレイピングして、コンソールエラーを洗い出したいのですが、
無料で実行可能なアーキテクチャの案を出してもらえませんか？
```

#### ChatGPTの回答

```
Webサイト全体をスクレイピングしてコンソールエラーを洗い出すためには、いくつかのステップが必要です。ただし、無料で利用できるサービスやアーキテクチャは制限があるため、適切なアプローチを選択する必要があります。

以下は、無料で実行可能なアーキテクチャの一般的なアイデアです：

スクレイピングツールの選定： PythonのBeautifulSoupやScrapy、Node.jsのPuppeteerなど、スクレイピングに適したツールを選びます。

スクレイピングの制限： 一部のウェブサイトはrobots.txtでクローラーのアクセスを制限している場合があります。サイトのポリシーを尊重し、必要であればクロール間隔を調整してください。

コンソールエラーの取得： スクレイピングの過程でJavaScriptを実行してコンソールエラーを取得するには、Headlessブラウザ（無表示ブラウザ）を使用することが一般的です。PuppeteerやSeleniumを使用して、ページをブラウザで開き、コンソールエラーを取得します。

結果の収集： 取得したエラーをローカルファイルやデータベースに保存します。

反復処理： サイト全体に対してスクレイピングとエラー取得を行います。ただし、クローリングの頻度や速度には注意が必要です。

クラウドサービスの利用： Google ColabやAWS Lambda、Herokuなど、無料で一定の利用制限内で実行できるクラウドサービスを活用することができます。ただし、注意事項や利用制限を確認してください。

なお、スクレイピングには法的な制約があるため、対象のウェブサイトの利用規約を確認し、違反しないように注意してください。また、スクレイピングの実行によりサーバーに負荷がかかる場合があるため、適切な頻度で実行することが重要です。
```

#### アーキテクチャは以下に決定

ということで、アーキテクチャは以下に決定しました。

Python 3.9.6
selenium 4.16.0
BeautifulSoup 4.12.2

また、ファイル構成は下記の通りです。

project/
 ┗ main.py
 ┗ urldata.csv （生成されているはずのURL一覧が入ったCSVファイル）
 ┗ console_errors.log　（コンソールエラーを書き込むファイル）


# 作成したコード

さて、結果、こんなコードでやりたいことが実現できました :beers: 

```python
import time
import csv
from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.chrome.service import Service as ChromeService
from bs4 import BeautifulSoup

# ターゲットのURL
base_url = '{https://公開予定のURLとか}'
basic_url = '{http://テスト環境のURLとか}'  # スクレイピングしたいサイトのURLに置き換えてください

# ChromeDriverのパス（自分の環境に合わせて変更）
chromedriver_path = '/path/to/chromedriver'

# ChromeDriverの設定
options = ChromeOptions()
options.add_argument('--headless')  # ヘッドレスモードで起動（画面表示なし）
options.add_argument('--disable-gpu')

# ログファイル名（適宜変更）
log_file_path = 'console_errors.log'

# CSVファイルからURLを読み込み
csv_file_path = 'urldata.csv'  # CSVファイルのパスに適切なものに変更してください

with open(csv_file_path, 'r') as csv_file:
    csv_reader = csv.reader(csv_file)
    next(csv_reader)  # ヘッダー行をスキップする場合

    for row in csv_reader:
        # URLを取得
        url = row[5].replace(base_url, basic_url) #必要に応じて
        print('URL:', url)

        # ウェブドライバーの初期化
        service = ChromeService(executable_path=chromedriver_path)
        driver = webdriver.Chrome(service=service, options=options)

        try:
            # ウェブページを取得
            driver.get(url)
            time.sleep(2)  # ページが完全に読み込まれるのを待つ（適切な時間を設定）

            # ウェブページのHTMLをBeautifulSoupに渡す
            soup = BeautifulSoup(driver.page_source, 'html.parser')

            # タイトルの取得
            title_element = soup.title
            print('title:', title_element.text)

            # コンソールエラーの取得（Selenium）
            logs = driver.get_log('browser')


            with open(log_file_path, 'a') as file:

                if title_element is not None:
                    title = title_element.text
                    file.write(f'\n\nURL: {url}, タイトル: {title}\n')
                else:
                    file.write(f'URL: {url}, タイトルが見つかりませんでした。')
                
                for log in logs:
                    if log['level'] == 'SEVERE':
                        error_message = f"エラー: {log['message']}\n"
                        print(error_message)
                        file.write(error_message)

        except Exception as e:
            print(f'URL: {url}, エラーが発生しました: {e}')

        finally:
            # ドライバーを閉じる
            driver.quit()
```


無事できました。

このコードで、750ページくらいスクレイピングするのに3時間くらいかかったので、作って良かったです。