Python + Selenium でメインコンテンツの画像を一括ダウンロードしてみる

2021年9月26日日曜日

Python

t f B! P L

Python と Selenium を使って、ギャラリーサイトなどから画像を一括をダウンロードしてみましょう。

スポンサーリンク

メインの画像のみダウンロードしたい

一般的にスクレイピングで画像を一括ダウンロードする場合は、HTML 中の <img> タグから src 属性を取り出し、その URL の画像を一括でダウンロードする方法が考えられれる。

しかし、基本的に Web ページ上には、メインの画像以外にも宣伝や、その他の画像が掲載されていることが多く、すべての画像をダウンロードしてしまうと、関係のない画像までダウンロードしてしまうことになる。

そこで、極力メイン画像のみをスクレイピングするように、この記事で紹介するサンプルコードは、次のような事を考える。

メインの画像のみダウンロードする方式

通常、ギャラリーサイトに掲載されている画像は、次のイメージのように画像サイズが類似していることが多い。

そこで、今回紹介するサンプルコードは、極力メイン画像のみをスクレイピングするように、画像サイズが誤差 30px で同じ画像の数をカウントして、もっとも数が多いサイズの画像をダウンロードするようにしている。

サンプルコード

それでは、上のことを考慮したサンプルコードを紹介する。少し長いサンプルコードになるが、途中のコメントを見ていけば問題なく理解できるであろう。

from selenium import webdriver
import requests
import os
import datetime

#ダウンロードする画像の最低サイズ
MIN_WIDTH = 200
MIN_HEIGHT = 300

#画像の一括ダウンロードを行うサイト
url = "https://xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# 画像のサイズと画像のurlを管理するクラス
class ImageSet:
  def __init__(self, w, h):
    self.w = w
    self.h = h
    self._url_list = []

  def is_within_range(self, w, h):
    #print("w=%d h=%d rw=%d %d " % (w, h, self.range_w.start, self.range_w.stop))
    return self.range_w.start <= w <= self.range_w.stop \
      and self.range_h.start <= h <= self.range_h.stop

  @property
  def range_w(self):
    return range(self.w - 30, self.w + 30)

  @property
  def range_h(self):
    return range(self.h - 30, self.h + 30)

  def add_url(self, url):
    self._url_list.append(url)

  @property
  def url_list(self):
    return self._url_list

  def display(self):
    #print("w=%d h=%d count=%d" % (self.w, self.h, len(self.url_list)))
    for n in self.url_list:
      print(n)
  
img_set_list = []

# Chrome の Web ドライバーを読み込み(パスを環境に合わせて変更すること)
driver = webdriver.Chrome("/path/to/chromedriver") 
driver.get(url)
driver.implicitly_wait(10)

#imgタグのリストを取得
elements = driver.find_elements_by_tag_name("img")
img_items = [
  { 'w': e.size["width"], 
    'h': e.size["height"], 
    'url': e.get_attribute("src") 
  } 
  for e in elements if e.size["width"] >= MIN_WIDTH and e.size["height"] >= MIN_HEIGHT
]

#画像はカレントディレクトリの「img/yyyymmddhhmmss」に格納するためフォルダを作成しておく
if not os.path.isdir("img"):
  os.makedirs("img")
now = datetime.datetime.now()
path = "{}/{}".format("img", now.strftime("%Y%m%d%H%M%S"))
if not os.path.isdir(path):
  os.makedirs(path)

#画像サイズごとにURLのリストを作成
for i,el in enumerate(img_items, start=1):
  
  #表示上の幅と高さを取得
  w, h = el["w"], el["h"]
  print("w=%d h=%d" % (w, h))

  #似たサイズの画像リスト検索
  find_list = list(filter(lambda x: x.is_within_range(w, h), img_set_list))

  if len(find_list) == 0:
    #似たサイズがなければ画像リスト作成
    img_set = ImageSet(w, h)
    img_set.add_url(el["url"])
    img_set_list.append(img_set)
  else:
    #似たサイズのリストに追加
    for x in find_list:
      x.add_url(el["url"])
  
max_img_set = max(img_set_list, key=lambda x: len(x.url_list))
if max_img_set is not None:
  #max_img_set.display()

  for i, url in enumerate(max_img_set.url_list, 1):
    #ファイルのダウンロード
    print("download start %s" % url)
    responce = requests.get(url)
    content_type = responce.headers['content-type']
    #print("download end content-type=%s" % content_type)
    
    #URLまたは content-type ヘッダからファイルの拡張子を設定
    _, ext = os.path.splitext(url)
    ext = ext if len(ext) > 0 else ".jpg"
    for k, v in {"image/jpeg" : ".jpg", "image/png" : ".png", "image/gif" : ".gif"}.items():
      if k in content_type:
        ext = v

    #ファイルの保存
    with open(path + "/" + "{:04}{}".format(i, ext), "wb") as f:
      f.write(responce.content)

#webdriverを閉じる
driver.quit()

注意

画像を一括でダウンロードする方法を解説してきました。インターネット上の画像は著作権で保護されてい可能性もあるため、ダウンロードした画像を二次利用する時は十分に気をつけましょう。

また、Cloudflare などのスクレイピング対策された Web サイトの場合は、requests モジュールで画像をダウンロードすると、bot 扱いとなりダウンロードができません。cloudscraper を使えばそれも回避できるようだが、今回はここまで。

スポンサーリンク
スポンサーリンク

このブログを検索

Profile

自分の写真
Webアプリエンジニア。 日々新しい技術を追い求めてブログでアウトプットしています。
プロフィール画像は、猫村ゆゆこ様に書いてもらいました。

仕事募集もしていたり、していなかったり。

QooQ