スクレイピングでGoogle検索した画像を持ってくる方法です。
スクレイピングの違法性は他のところでもいろいろ言われているので、実施する際はご自身でご注意ください。
SeleniumでGoogleChromeを動かします。Pythonの環境構築、Anacondaのインストール方法などは、他の方もたくさん記事を書いているのでそちらを参照ください。
環境
Windows10
anaconda=4.13.0
python=3.10.5
GoogleChrome
ディレクトリ構成
root
├ data
├ script
│ └ scraping_from_google.py
└ chromewebdriver.exe
「data」フォルダにスクレイピングした画像を保存します
コード全文
先にコード全文を載せます。
import os
from pathlib import Path
import random
import re
import requests
import string
import sys
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import ElementClickInterceptedException
def collect_images():
QUERY = <検索したいワード>
LIMIT_DL_NUM = <取得したい画像数>
project_dir = Path(__file__).resolve().parent.parent
DRIVER_PATH = project_dr
RETRY_NUM = 3
TIMEOUT = 3
# フルスクリーンにする
options = webdriver.ChromeOptions()
options.add_argument("--start-fullscreen")
options.add_argument('--headless')
options.add_argument('--disable-gpu') # headlessモードで暫定的に必要なフラグ(そのうち不要になる)
options.add_argument('--disable-extensions') # すべての拡張機能を無効にする。ユーザースクリプトも無効にする
options.add_argument('--proxy-server="direct://"') # Proxy経由ではなく直接接続する
options.add_argument('--proxy-bypass-list=*') # すべてのホスト名
options.add_argument('--start-maximized') # 起動時にウィンドウを最大化する
# 指定したURLに移動
url = f'https://www.google.com/search?q={QUERY}&tbm=isch'
driver = webdriver.Chrome(DRIVER_PATH, options=options)
# タイムアウト設定
driver.implicitly_wait(TIMEOUT)
# グーグルの画像検索を行う。
driver.get(url)
# 表示されるサムネイル画像をすべて取得する
thumbnail_elements = driver.find_elements(By.CLASS_NAME, 'Q4LuWd')
# 取得したサムネイル画像数を数える
count = len(thumbnail_elements)
print(count)
# 取得したい枚数以上になるまでスクロールする
while count < LIMIT_DL_NUM:
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
time.sleep(2)
# サムネイル画像の取得
thumbnail_elements = driver.find_elements(By.CLASS_NAME, 'Q4LuWd')
count = len(thumbnail_elements)
print(count)
# HTTPヘッダーの作成
HTTP_HEADERS = {'User-Agent': driver.execute_script('return navigator.userAgent;')}
print(HTTP_HEADERS)
# 画像をダウンロードするためのURLを格納
image_urls = []
# サムネイルをクリックしたときに表示される画像から、画像のURLを取得
# エラーの処理は苦し紛れ
# うまく取得できるまで、最大3回はトライする
for index, thumbnail_element in enumerate(thumbnail_elements):
is_clicked = False
for i in range(RETRY_NUM):
# サムネイル画像をクリックする。場合によっては失敗するのでtry-exceptでエラー処理
try:
if is_clicked == False:
thumbnail_element.click()
time.sleep(2)
is_clicked = True
except NoSuchElementException:
print(f'****NoSuchElementException*****')
continue
except Exception:
print('予期せぬエラーです')
break
try:
# サムネイル画像をクリックしたときに表示される大きい画像のimgタグを取得し、画像のURLをsrcから取得
image_element = driver.find_element(By.CLASS_NAME, 'KAlRDb')
image_url = image_element.get_attribute('src')
# サムネイル画像をクリックした直後は、画像のURLがサムネイルURLのまま。そのURLだった場合、再度画像URLの取得を行う。
if re.match(r'data:image', image_url):
print(f'URLが変わるまで待ちましょう。{i+1}回目')
time.sleep(2)
if i+1 == RETRY_NUM:
print(f'urlは変わりませんでしたね。。。')
continue
else:
print(f'image_url: {image_url}')
extension = get_extension(image_url)
if extension:
image_urls.append(image_url)
print(f'urlの保存に成功')
# jpg jpeg png 以外はダウンロードしない
else:
print('対象の拡張子ではありません')
break
except NoSuchElementException:
print(f'****NoSuchElementException*****')
break
except ElementClickInterceptedException:
print(f'***** click エラー: {i+1}回目')
driver.execute_script('arguments[0].scrollIntoView(true);', thumbnail_element)
time.sleep(1)
else:
break
if index+1 % 20 == 0:
print(f'{index+1}件完了')
time.sleep(1)
# 出力フォルダの作成
project_dir = Path(__file__).resolve().parent.parent
save_dir = os.path.join(project_dir, 'data', QUERY)
os.makedirs(save_dir, exist_ok=True)
for image_url in image_urls:
down_load_image(image_url, save_dir, 3, HTTP_HEADERS)
driver.quit()
def get_extension(url):
url_lower = url.lower()
extension = re.search(r'\.jpg|\.jpeg|\.png', url_lower)
if extension:
return extension.group()
else:
return None
def randomname(n):
randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
return ''.join(randlst)
def down_load_image(url, save_dir, loop, http_header):
result = False
for i in range(loop):
try:
r = requests.get(url, headers=http_header, stream=True, timeout=10)
r.raise_for_status()
extension = get_extension(url)
file_name = randomname(12)
file_path = save_dir + '/' + file_name + extension
with open(file_path, 'wb') as f:
f.write(r.content)
print(f'{url}の保存に成功')
except requests.exceptions.SSLError:
print('*****SSLエラー*****')
break
except requests.exceptions.RequestException as e:
print(f'***** requests エラー ({e}): {i+1} 回目')
time.sleep(1)
else:
result = True
break
return result
if __name__ == '__main__':
collect_images()
スクレイピングの環境構築
スクレイピングを行うには、コマンドプロンプトからGoogleChromeを開けないといけません。
そのためには、ChromeWebDriverをダウンロードする必要があります。
まずはご自身のChromeを開いて、「設定」→「ヘルプ」→「Google Chromeについて」からバージョンを確認します。
バージョンは「103.0.5060.66」ですね。
そして、こちらのページより、ChromeのWebDriverをダウンロードします。
バージョンと完全に合うものはないこともあるみたいです。
僕は「103.0.5060.53」をダウンロードしました。
ダウンロードしたWebDriverはプロジェクトディレクトリ直下に配置します(直下でなくても大丈夫ですが、その場合、DRIVER_PATH
はご自身で変更してください。)
基本的な準備はこちらで終了です。
以下は、コードのそれぞれで何をやっているかを解説していきます。
Seleniumを立ち上げる
# フルスクリーンにする
options = webdriver.ChromeOptions()
options.add_argument("--start-fullscreen")
options.add_argument('--headless')
options.add_argument('--disable-gpu') # headlessモードで暫定的に必要なフラグ(そのうち不要になる)
options.add_argument('--disable-extensions') # すべての拡張機能を無効にする。ユーザースクリプトも無効にする
options.add_argument('--proxy-server="direct://"') # Proxy経由ではなく直接接続する
options.add_argument('--proxy-bypass-list=*') # すべてのホスト名
options.add_argument('--start-maximized') # 起動時にウィンドウを最大化する
# 指定したURLに移動
url = f'https://www.google.com/search?q={QUERY}&tbm=isch'
driver = webdriver.Chrome(DRIVER_PATH, options=options)
# タイムアウト設定
driver.implicitly_wait(TIMEOUT)
# グーグルの画像検索を行う。
driver.get(url)
add_argument
でいろんな設定を追加していきます。今記述しているものすべてを書く必要はなさそうですが、よく分かっていないので僕も確認していません。
url
はGoogleの画像検索のURLです。ここに検索ワードであるQUERY
を渡すことで、画像検索ができます。
driver.implicitly_wait(TIMEOUT)
はタイムアウト設定です。指定したドライバの要素が見つかるまでの待ち時間を設定します。
TIMEOUT=3
で設定をしていますが、適宜値は変更して問題ないと思います。
サムネイル画像を取得したい枚数以上表示する
Googleで画像検索をかけると、画像一覧が表示されます(とりあえず推しのれなひょん。深い意味はない)
下にスクロールしていくとページが読み込まれて、画像の枚数が増えますよね。
以下のコードでSeleniumから同じことをしています。
# 表示されるサムネイル画像をすべて取得する
thumbnail_elements = driver.find_elements(By.CLASS_NAME, 'Q4LuWd')
# 取得したサムネイル画像数を数える
count = len(thumbnail_elements)
print(count)
# 取得したい枚数以上になるまでスクロールする
while count < LIMIT_DL_NUM:
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
time.sleep(2)
# サムネイル画像の取得
thumbnail_elements = driver.find_elements(By.CLASS_NAME, 'Q4LuWd')
count = len(thumbnail_elements)
print(count)
サムネイル画像を数える
→取得したい枚数以下だったら、画面をスクロールして表示数を増やす
→サムネイル画像を数える
→取得したい枚数以下だったら、画面をスクロールして表示数を増やす
ということを繰り返して、目標の枚数以上表示されるまでスクロールします。
サムネイル画像は、サムネイルの画像のクラス名rg_i Q4LuWd
のうち、後者を指定しています。
前者でもうまくいくかもしれませんが、試していないので分かりません。このクラス名は時々で変わるらしいので、Chromeの検証ツールから確認してください。
サムネイル画像をクリックして表示される画像のURLを取得する
サムネイル画像をクリックすると写真のようにその画像のアップが表示されます。
元の画像のURLはこの画像のimgタグの中のsrcに記述されています。
このURLをシンプルに取得できればいいのですが、うまくいかないことがよくあります。具体的にはこのような事象に対処しています。
- サムネイル画像をクリックしたが、読み込みが間に合わず、画像のimgタグを取得できない
- サムネイル画像をクリックしたときに表示される画像はクリック直後は「data:image」で始まるサムネイル画像のURLになっている。しばらく待つと、実際の画像のURLに変わる
- 表示される画像のimgタグのクラス名は
KAlRDb
が含まれているがたまにないものもある(先述したが、このクラス名は可変らしい)
これらに対応するために、ごちゃごちゃとエラー対応をしています。最大3回トライしてみて、ダメだったら諦めて次の画像に行く、という処理をしています。
また、拡張子がjpg
,jpeg
,png
以外のものは取得しない、という処理もいれています。
正直全くきれいにかけていないので、もっとうまく書く方法はたくさんあると思います。
# サムネイルをクリックしたときに表示される画像から、画像のURLを取得
# エラーの処理は苦し紛れ
# うまく取得できるまで、最大3回はトライする
for index, thumbnail_element in enumerate(thumbnail_elements):
is_clicked = False
for i in range(RETRY_NUM):
# サムネイル画像をクリックする。場合によっては失敗するのでtry-exceptでエラー処理
try:
if is_clicked == False:
thumbnail_element.click()
time.sleep(2)
is_clicked = True
except NoSuchElementException:
print(f'****NoSuchElementException*****')
continue
except Exception:
print('予期せぬエラーです')
break
try:
# サムネイル画像をクリックしたときに表示される大きい画像のimgタグを取得し、画像のURLをsrcから取得
image_element = driver.find_element(By.CLASS_NAME, 'KAlRDb')
image_url = image_element.get_attribute('src')
# サムネイル画像をクリックした直後は、画像のURLがサムネイルURLのまま。そのURLだった場合、再度画像URLの取得を行う。
if re.match(r'data:image', image_url):
print(f'URLが変わるまで待ちましょう。{i+1}回目')
time.sleep(2)
if i+1 == RETRY_NUM:
print(f'urlは変わりませんでしたね。。。')
continue
else:
print(f'image_url: {image_url}')
extension = get_extension(image_url)
if extension:
image_urls.append(image_url)
print(f'urlの保存に成功')
# jpg jpeg png 以外はダウンロードしない
else:
print('対象の拡張子ではありません')
break
except NoSuchElementException:
print(f'****NoSuchElementException*****')
break
except ElementClickInterceptedException:
print(f'***** click エラー: {i+1}回目')
driver.execute_script('arguments[0].scrollIntoView(true);', thumbnail_element)
time.sleep(1)
else:
break
画像のURLからダウンロードしてフォルダに保存
スクレイピングして取得した画像URLから、最後実際にダウンロードしていきます。
# 出力フォルダの作成
project_dir = Path(__file__).resolve().parent.parent
save_dir = os.path.join(project_dir, 'data', QUERY)
os.makedirs(save_dir, exist_ok=True)
for image_url in image_urls:
down_load_image(image_url, save_dir, 3, HTTP_HEADERS)
def randomname(n):
randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
return ''.join(randlst)
def down_load_image(url, save_dir, loop, http_header):
result = False
for i in range(loop):
try:
r = requests.get(url, headers=http_header, stream=True, timeout=10)
r.raise_for_status()
extension = get_extension(url)
file_name = randomname(12)
file_path = save_dir + '/' + file_name + extension
with open(file_path, 'wb') as f:
f.write(r.content)
print(f'{url}の保存に成功')
except requests.exceptions.SSLError:
print('*****SSLエラー*****')
break
except requests.exceptions.RequestException as e:
print(f'***** requests エラー ({e}): {i+1} 回目')
time.sleep(1)
else:
result = True
break
return result
保存する画像の名前はランダムに付けました。
このpythonファイルを実行すると、data
内に検索したクエリ名でディレクトリが作られ、その中に画像がダウンロードされます。
これにてスクレイピング終了です!