Google画像検索の画像をオリジナルサイズで取得する

画像検索の画像をスクレイピングする方法についての記事は既に複数あるが、ちょっとした仕様変更が頻繁なのか、そのまま使えるものはほぼなかったので、どこを変更する可能性があるかも含めて記事を書いておきます。
多分動かないときにやることは、ブラウザのディベロッパーツールを使って必要なclass名やid名を調べればよい、というだけですが、わかる人には自明だけど、知らないと何が間違っている調べるのに結構時間かかる。

## 実行環境
macOS Mojave 10.14.5
Python 3.7.2
Chrome 80.0
date 2020-04-01

## スクレイピングに必要なモジュールの準備
* Seleniumのインストール

```
pip install selenium
```
* webdriverのインストール
    * webdriverとはwebブラウザを外部のソフトから実行するためのもので、自動テストなどでよく使われる. 今回はchromeのwebdriverを使う
    * chromeのversionをブラウザのヘルプから確認(同じバージョンにしたい場合)
    * [Downloads - ChromeDriver - WebDriver for Chrome](https://chromedriver.chromium.org/downloads)から該当の環境、versionのものをダウンロードする
    * [追記]pipでもinstallできる [Python/ChromeDriverインストールとパスの通し方](https://watlab-blog.com/2019/08/10/chromedriver-path/)

## webdriverが使えるかチェック
起動 → google検索 → 終了 をしてみる

```python
from selenium import webdriver

# ダウンロードしたwebdriverのpathを指定
DRIVER_PATH = '/User/hoge/1bin/chromedriver'
# webブラウザを起動
wd = webdriver.Chrome(executable_path=DRIVER_PATH)
# googleにアクセス
wd.get('https://google.com')
# 検索boxを選択する. 選択する方法は複数ある
search_box = wd.find_element_by_name('q') 
# search_box = wd.find_element_by_css_selector('input.gLFyf')
# search_box = wd.find_element_by_class_name('gLFyf')

# Qiitaを検索する
search_box.send_keys('Qiita')
search_box.submit() 
time.sleep(5) 
# webブラウザを終了する
wd.quit()
```
## 画像検索をして画像のURLを取得する
### サムネイル画像のURLを取得
最初に検索結果のサムネイルのURLを取得する。

```python
# クリックなど動作後に待つ時間(秒)
sleep_between_interactions = 2
# ダウンロードする枚数
download_num = 3
# 検索ワード
query = "cat"
# 画像検索用のurl
search_url = "https://www.google.com/search?safe=off&site=&tbm=isch&source=hp&q={q}&oq={q}&gs_l=img"

wd = webdriver.Chrome(executable_path=DRIVER_PATH)
wd.get(search_url.format(q=query))
# サムネイル画像のリンクを取得(ここでコケる場合はセレクタを実際に確認して変更する)
thumbnail_results = wd.find_elements_by_css_selector("img.rg_i")
```
### サムネイル画像をクリックして元画像のURLを取得する
取得したサムネイルのURLをwebdriverでクリックし、画像を表示してオリジナル画像のURLを取得する。
また、オリジナル画像URLの取得の際に指定するclass nameは変更されることあるので、適宜ディベロッパーツールを使って調べて変更する。

```python
image_urls = set()
for img in thumbnail_results[:download_num]:
    try:
        img.click()
        time.sleep(sleep_between_interactions)
    except Exception:
        continue
    # 一発でurlを取得できないので、候補を出してから絞り込む(やり方あれば教えて下さい)
    # 'n3VNCb'は変更されることあるので、クリックした画像のエレメントをみて適宜変更する
    url_candidates = wd.find_elements_by_class_name('n3VNCb')
    for candidate in url_candidates:
        url = candidate.get_attribute('src')
        if url and 'https' in url:
            image_urls.add(url)
# 少し待たないと正常終了しなかったので3秒追加
time.sleep(sleep_between_interactions+3)
wd.quit()
```
## 画像URLから画像を取得する
あとはURLから画像をダウンロードするだけで、webdriverは必要ない。
* PILは画像ライブラリ

```python
import os
from PIL import Image
import hashlib

image_save_folder_path = 'data'
for url in image_urls:
    try:
        image_content = requests.get(url).content
    except Exception as e:
        print(f"ERROR - Could not download {url} - {e}")

    try:
        image_file = io.BytesIO(image_content)
        image = Image.open(image_file).convert('RGB')
        file_path = os.path.join(image_save_folder_path,hashlib.sha1(image_content).hexdigest()[:10] + '.jpg')
        with open(file_path, 'wb') as f:
            image.save(f, "JPEG", quality=90)
        print(f"SUCCESS - saved {url} - as {file_path}")
    except Exception as e:
        print(f"ERROR - Could not save {url} - {e}")
```

## 画像リンクを取得するときのelementの探し方
googleの検索窓なら、```search_box = wd.find_element_by_name('q')```
画像検索結果のオリジナル画像なら、```wd.find_elements_by_class_name('n3VNCb')```
などで指定するが、これらはブラウザのディベロッパーツールを使って確認できる。

ブラウザの「…」→その他のツール→デベロッパーツール
ショートカットは、 ```Ctrl Shift J (on Windows)``` or ```cmd + Option + J (on Mac)```

指定したい画像があれば、画像を右クリックして"検証"を選択すると、画像に対応するエレメントを表示してくれる。
画像検索のサムネイルなら、以下のようなエレメントになる。
エレメントは選択状態で出てくる'...'をクリックしてコピーできる。

```
<img alt="Dogs | The Humane Society of the United States" class="n3VNCb" src="https://www.humanesociety.org/sites/default/files/styles/1240x698/public/2019/02/dog-451643.jpg?h=bf654dbc&amp;itok=MQGvBmuo" data-noaft="1" jsname="HiaYvf" jsaction="load:XAeZkd;" style="width: 450px; height: 253.306px; margin: 0px;">
```
class="n3VNCb"とあるので、
wd.find_elements_by_class_name('n3VNCb')としたら選択できる。

## コードまとめ
コピペ用

```python
import os
import time
from selenium import webdriver
from PIL import Image
import io
import requests
import hashlib

# クリックなど動作後に待つ時間(秒)
sleep_between_interactions = 2
# ダウンロードする枚数
download_num = 3
# 検索ワード
query = "cat"
# 画像検索用のurl
search_url = "https://www.google.com/search?safe=off&site=&tbm=isch&source=hp&q={q}&oq={q}&gs_l=img"

# サムネイル画像のURL取得
wd = webdriver.Chrome(executable_path=DRIVER_PATH)
wd.get(search_url.format(q=query))
# サムネイル画像のリンクを取得(ここでコケる場合はセレクタを実際に確認して変更する)
thumbnail_results = wd.find_elements_by_css_selector("img.rg_i")

# サムネイルをクリックして、各画像URLを取得
image_urls = set()
for img in thumbnail_results[:download_num]:
    try:
        img.click()
        time.sleep(sleep_between_interactions)
    except Exception:
        continue
    # 一発でurlを取得できないので、候補を出してから絞り込む(やり方あれば教えて下さい)
    # 'n3VNCb'は変更されることあるので、クリックした画像のエレメントをみて適宜変更する
    url_candidates = wd.find_elements_by_class_name('n3VNCb')
    for candidate in url_candidates:
        url = candidate.get_attribute('src')
        if url and 'https' in url:
            image_urls.add(url)
# 少し待たないと正常終了しなかったので3秒追加
time.sleep(sleep_between_interactions+3)
wd.quit()

# 画像のダウンロード
image_save_folder_path = 'data'
for url in image_urls:
    try:
        image_content = requests.get(url).content
    except Exception as e:
        print(f"ERROR - Could not download {url} - {e}")

    try:
        image_file = io.BytesIO(image_content)
        image = Image.open(image_file).convert('RGB')
        file_path = os.path.join(image_save_folder_path,hashlib.sha1(image_content).hexdigest()[:10] + '.jpg')
        with open(file_path, 'wb') as f:
            image.save(f, "JPEG", quality=90)
        print(f"SUCCESS - saved {url} - as {file_path}")
    except Exception as e:
        print(f"ERROR - Could not save {url} - {e}")

```

## 参考にした記事
* [Python/SeleniumでChrome自動Google検索](https://watlab-blog.com/2019/08/11/selenium-google-search/)
* [Image Scraping with Python - Towards Data Science](https://towardsdatascience.com/image-scraping-with-python-a96feda8af2d)
* [WebDriver について私が知っていること (2017 年版) - ひだまりソケットは壊れない](https://vividcode.hatenablog.com/entry/web-driver-introduction-2017)