Pythonでエゴサ結果をzipファイルにまとめる

# 目的
- PythonのSeleniumで、Google画像検索（エゴサーチ）した結果のうち、サムネイル画像上位20ファイルを、zipファイルにまとめる

# 背景
誰しも？自己紹介の資料を作るとき、一度はエゴサしたことと思うw
エゴサした結果を1つのファイルにまとめたら、何か面白い使い道があるのではないかと思い、作ってみた。

# 環境
- Windows11 バージョン22H2(OSビルド22621.963)
 - Python3.9.1
 - chrome 108.0.5359.125（Official Build） （64 ビット）
 - chrome driver 108.0.5359.71

# セットアップ

## Pythonライブラリ
- ライブラリをインストールする
```winbatch
pip install urllib3
pip install requests
pip install pyperclip
pip install python-datauri
```

## PythonのSelenium環境
- Selenium環境構築の説明は他に譲るとして、chromedriver.exeのパスは「C:\Git\traning\python\Web_scraping\driver\chromedriver.exe」とする
- たぶん以下のライブラリをインストールすればよいと思う
```winbatch
pip install selenium
pip install chromedriver-binary
pip install webdriver-manager
pip install webdrivermanager
```

# 使い方
- [Git](https://github.com/igapon50/training/releases/tag/1.9.0)からSource code(zip)をダウンロードして、展開する
- 展開したパスが「c:\Git\traning\」とすると、「C:\Git\traning\python\Web_scraping\egoser_zipper.py」にツールがある
- egoser_zipper.pyは、以下のファイルに依存関係があるので、フォルダを移動する場合は一緒に移動する（実行するとegoser_zipper.txtファイルができる）
    - crawling.py　クローリングに使う
    - chromeDriverHelper.py　ChromeDriverを操作するヘルパーで、スクレイプやファイル保存に使う
    - webFileListHelper.py　複数のwebFileHelperを操作するヘルパーで、ファイルの存在チェックやzip作成に使う
    - webFileHelper.py　URLを操作するヘルパーで、パスの作成やファイルの存在チェックに使う
    - irvineHelper.py　irvineを操作するヘルパーで、今回のツールでは使っていない、importだけされる
    - uriHelper.py　uriを操作するヘルパーで、パスの作成やDataURIの操作に使う
- 以下のコマンドラインを実行して、chromedriverを起動する
```winbatch:
chrome.bat
```
- 以下のコマンドラインを実行して、エゴサzipperする
```winbatch:
python egoser_zipper.py igapon
```
- 成功すると、archiveフォルダに「igapon.zip」が、workフォルダに「igapon：[ファイル名の禁則文字だけ全角にしたURL].html」ができる
- 漫画ビュアーなどでzipファイルを開いて、内容を確認する

# コードの説明
- サイトや、ページのアドレスや、セレクタを定義する
```python:egoser_ziper.py 25～47行目
    site_url = f'https://www.google.com/search?q={my_name}'
    site_selectors = {
        'page_urls': [
            (By.XPATH,
             '//*[@id="hdtb-msb"]/div[1]/div/div[2]/a',
             lambda el: el.get_attribute("href")
             ),
        ]
    }
    page_selectors = {
        'title_jp': [(By.XPATH,
                      '//*[@id="REsRA"]',
                      lambda el: el.get_attribute("value")),
                     ],
        'title_en': [(By.XPATH,
                      '//*[@id="REsRA"]',
                      lambda el: el.get_attribute("value")),
                     ],
        'image_urls': [(By.XPATH,
                       '//*[@id="islrg"]/div[1]/div/a[1]/div[1]/img',
                        lambda el: el.get_attribute("src")),
                       ],
    }
```
- Crawlingクラスのインスタンスを作り、スクレイピングする
```python:egoser_ziper.py 48～49行目
    crawling = Crawling(site_url, site_selectors, 'egoser_ziper.txt')
    crawling_items = crawling.get_crawling_items()
```
- スクレイピング結果からpage_urlsを取り出し、forループ処理を行う
- page_urlが除外リストに含まれていたら、以降の処理をスキップする
```python:egoser_ziper.py 51～58行目
    if 'page_urls' in crawling_items:
        page_urls = crawling_items['page_urls']
    for page_url in page_urls:
        print(page_url)
        if crawling.is_url_included_exclusion_list(page_url):
            crawling.move_url_from_page_urls_to_exclusion_urls(page_url)
            crawling.save_text()
            continue
```
- ページのセレクタでスクレイピングする
- itemsから、image_urlsのスクレイピング結果を先頭から20個分取り出して、image_urlsに入れる
- titleを取り出し、urlを全角文字列url_titleに変換して、チェック用ファイル名を作る
```python:egoser_ziper.py 59～65行目
        items = crawling.scraping(page_url, page_selectors)
        image_urls = crawling.take_out(items, 'image_urls')[0:20]  # 先頭の20個は、DataURIで表示される
        title = Crawling.validate_title(items, 'title_jp', 'title_en')
        url_title = ChromeDriverHelper.fixed_file_name(page_url)
        # フォルダがなかったらフォルダを作る
        os.makedirs(WebFileListHelper.work_path, exist_ok=True)
        target_file_name = os.path.join(WebFileListHelper.work_path, f'{title}：{url_title}.html')
```
- チェック用ファイルが既に存在していたり、image_urlsが空の時、以降の処理をスキップする(ただし、Google画像検索では、URLのクエリーが毎回違うようなので、毎回チェックをパスしてしまう)
- image_urlsを引数に、WebFileListHelperクラスのインスタンスを生成する
- chromedriverでローカルに保存する
- ローカルのファイルをzipファイルにまとめる
- zipファイル名をtitleに変更する
- ローカルのファイルを削除する
- チェック用ファイルを作る
- 除外リストにpage_urlを移動して、その情報を保存する
```python:egoser_ziper.py 67～82行目
        if not os.path.exists(target_file_name):
            if image_urls:
                print(image_urls)
                web_file_list = WebFileListHelper(image_urls)
                crawling.download_chrome_driver(web_file_list)
                if not web_file_list.make_zip_file():
                    sys.exit()
                if not web_file_list.rename_zip_file(title):
                    if not web_file_list.rename_zip_file(f'{title}：{url_title}'):
                        sys.exit()
                web_file_list.delete_local_files()
                # 成功したらチェック用ファイルを残す
                ChromeDriverHelper().save_source(target_file_name)
            # page_urlsからexclusion_urlsにURLを移して保存する
        crawling.move_url_from_page_urls_to_exclusion_urls(page_url)
        crawling.save_text()
```

# おわりに
Google画像検索の上位20は、DataURIで返ってくることを知った。
21位以降の画像は、スクロールするとAjaxでロードされるらしい。
今回のツール作成は、勉強にはなったが、使い道が全く分からない。

# 関連リンク
## 参考サイト
- [note.nkmk.me](https://note.nkmk.me/python/)
- [まくまくPythonノート](https://maku77.github.io/python/)
- [dataclassを使って、Pythonで値オブジェクトを実装する](https://nikkie-ftnext.hatenablog.com/entry/value-object-python-dataclass)
- [selenium+pythonでログイン認証後に画像をダウンロード](https://blog.wotakky.net/2018/08/12/post-4829/)
- [Selenium & Python で待機条件をカスタマイズするやり方](https://www.zacoding.com/post/selenium-custom-wait/)
- [[Python]Base64でエンコードされた画像データをデコードする。](https://qiita.com/TsubasaSato/items/908d4f5c241091ecbf9b)
ツール作った後に、以下の記事を発見した。記事では元画像を取得するそうだが、今回私が作ったツールは、サムネイル画像を取得している。
- [PythonスクレイピングでGoogle画像検索ページから画像を取得](https://senmyou.xyz/python%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0%E3%81%A7google%E7%94%BB%E5%83%8F%E6%A4%9C%E7%B4%A2%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%8B%E3%82%89%E7%94%BB%E5%83%8F%E3%82%92/)
## ドキュメント
- [Selenium with Python](https://selenium-python.readthedocs.io/)
- [Seleniumクリックリファレンス](https://www.seleniumqref.com/api/webdriver_gyaku.html)
- [Seleniumドキュメント](https://www.selenium.dev/ja/documentation/webdriver/getting_started/)
- [PythonでSeleniumを操作する](https://kurozumi.github.io/selenium-python/index.html)