はじめに
とある理由で大量の画像が必要になった。が、以下のような縛りがある
・指定のワードに関連した画像が欲しい
・手動で画像検索して保存しまくるのは面倒
・自動でクロールして保存したいが、クロール禁止のサイトの画像は取得したくない
・SSL通信できないサイトはクロールしたくない
仕様
・WebDriverはchromedriver を使用
・ブラウザはChrome ※ヘッドレスモードで実行
・検索エンジンでの検索結果の最初のページのみがクロール対象
コード内の"MAX_PAGES"で処理するページ数を変更可
・検索エンジンはGoogle
・検索するワードは「トノサマバッタのローキック」
・サイトのrobots.txtの情報に基づいてクロール可能な場合のみ画像を取得
・SSL通信できない(SSLErrorになる)サイトはクロールしない
・画像はサイトごとにフォルダを分けて保存する
・クロールは1秒間隔で行う※robots.txtのCrawl-delayは使用していない
コード
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException
from bs4 import BeautifulSoup
import requests
import os
from urllib.parse import urljoin
import urllib.robotparser
import re
import time
def get_page_links(url):
# サイト内のリンクをリスト化する
html = requests.get(url)
soup = BeautifulSoup(html.content, "html.parser")
title_links = []
for element in soup.find_all("a"):
try:
link_ = element.get("href")
link_url = urljoin(url, link_)
title_links.append(link_url)
except:
print("BeautifulSoupでうまく解析できない")
pass
return title_links
def is_can_fetch(url):
# URLを正規化してルートURLを取得
pattern = r'(https://.*?)\/.*'
re_url = re.match(pattern, url)
if re_url is not None:
root_url = re_url.group(1)
else:
root_url = url
# robots.txtのURLを生成
robots_txt_url = root_url + '/robots.txt'
# robots.txtの情報から調査したいURL、User-Agentでクロール可能かを調べる
rp = urllib.robotparser.RobotFileParser()
rp.set_url(robots_txt_url)
try:
rp.read()
result = rp.can_fetch("*", url)
except UnicodeDecodeError:
# utf-8でないバイト列が含まれるrobots.txtはあやしいのでFalse
print("UnicodeDecodeError at is_can_fetch")
result = False
return result
# 待ち時間の定義
SCREEN_TRANS_TIME = 1 # 画面遷移時の待ち時間
URL_TRANS_TIME = 1 # URLページ遷移時の待ち時間
# Google検索結果の処理対象とする最大ページ数
MAX_PAGES = 1
# Chromeオプション
options = Options()
options.add_argument('--disable-gpu');
options.add_argument('--disable-extensions');
options.add_argument('--proxy-server="direct://"');
options.add_argument('--proxy-bypass-list=*');
options.add_argument('--start-maximized');
options.add_argument('--headless');
# Chromeドライバーの起動
driver = webdriver.Chrome(chrome_options=options)
# 検索するワード
search_word = "トノサマバッタのローキック"
# Googleにアクセスする
url = 'https://google.com/'
driver.get(url)
time.sleep(SCREEN_TRANS_TIME)
# 検索窓にキーワードを入力する
selector = "body > div.L3eUgb > div.o3j99.ikrT4e.om7nvf > form > div:nth-child(1) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
element = driver.find_element_by_css_selector(selector)
element.send_keys(search_word) # キーワード
# enterキーを押す
element.send_keys(Keys.ENTER)
# 画面遷移する時間待つ
time.sleep(SCREEN_TRANS_TIME)
# Googleでキーワードを検索した結果のリンク先の画像を保存する ※トップページのみ
checked_urls = [] # 処理したサイトのURLリスト
saved_img_urls = [] # 保存した画像のURLリスト
for page in range(MAX_PAGES):
# 次の検索ページに遷移するために子ページからの戻り先として現在の検索ページを保存
search_page_url = driver.current_url
for link in get_page_links(search_page_url):
# リンクを開く
try:
driver.get(link)
time.sleep(SCREEN_TRANS_TIME)
check_url = driver.current_url
except WebDriverException as e: # ページから応答が無いとき等は以降の処理を行わない
print("WebDriverException: ")
print(e)
continue
except Exception as e:
print("謎のページ読み込みエラー")
raise Exception(e)
# 一度処理したサイトは処理しない
if check_url in checked_urls:
continue
else:
checked_urls.append(check_url)
# robots.txtの中身を調べてスクレイピングOKなら画像取得して保存する
if is_can_fetch(check_url):
# 現在のページのURLの画像情報を取得
try:
html = requests.get(check_url)
except requests.exceptions.SSLError:
#html = requests.get(check_url, verify=False) #で回避できるが怪しいリスクを考えてやらない
print("SSLError -> " + check_url)
continue
except requests.exceptions.ConnectionError:
print("ConnectionError -> " + check_url)
continue
bs = BeautifulSoup(html.content, "html.parser")
images = bs.find_all("img")
# 画像を保存するフォルダの名前を決める
try:
page_title = bs.find("head").title.text
except Exception as e:
page_title = check_url.split("/")[3]
print("headがないのでURLから名前つける")
# フォルダ名に使えないものは置換
page_title = re.sub(r'[\\|/|:|?|.|"|<|>|\|*+]', "", page_title)
dir_name = re.sub('\n', "", page_title)
# 画像を保存するフォルダを作成
try:
os.makedirs(dir_name)
except FileExistsError:
print(dir_name + " already exists")
except Exception as e:
print("保存フォルダ作成できないエラー")
print(e)
# 取得した画像をループして保存
for i, img in enumerate(images, start=1):
try:
# srcを使用して画像のURLを読み込み
src = img.get("src")
url_joined = urljoin(check_url, src)
responce = requests.get(url_joined)
except Exception as e:
# data-srcを使用して画像のURLを読み込み
print("srcがないのでdata-srcを読む")
src = img.get("data-src")
url_joined = urljoin(check_url, src)
responce = requests.get(url_joined)
img_url = url_joined
# 一度保存した画像は保存しない
if img_url in saved_img_urls:
continue
else:
saved_img_urls.append(img_url)
with open(dir_name + "/" + "{}.jpg".format(i), "wb") as f:
f.write(responce.content)
# 次の処理まで1秒空ける
#time.sleep(URL_TRANS_TIME)
try:
# 次の検索ページに遷移するために一度検索ページに戻る
driver.get(search_page_url)
time.sleep(SCREEN_TRANS_TIME)
# 検索結果下部の「次へ」ボタンをクリックしページ遷移する
next_button = driver.find_element_by_link_text('次へ')
next_button.click()
# 画面遷移する時間待つ
time.sleep(SCREEN_TRANS_TIME)
# 似たページが除外される等してMAX_PAGESまでページ遷移できない場合は終了
except Exception as e:
print("エラーページ:" + str(page+1))
print(e)
driver.quit()
break
# Chromeを閉じる
driver.quit()
画像の整理にはこれを使う