python3
BingImageSearch

Bing画像検索API v7で画像を収集する

はじめに

Bingの画像検索APIを使って画像を大量に収集する を参考にBingで画像収集しようとしたところ、APIがv7になっていたので、少し修正することになりました。
また、url取得とダウンロードをわけて行うようしました。

Bingのアカウントの取得や、API Keyの取得などは上記の記事を参考に行ってください。大変お世話になりました。

python素人なので、コードがイマイチなところは目をつぶってください。

コード

画像URL収集用

プログラム中に指定した検索ワードでBing画像検索を行い、結果のURLリストをファイルに保存します。

bing_image_url_collector.py
# coding: utf-8
import requests
import os
import math
import configparser # for Python3
import urllib
import re
import datetime

import bing_util

def get_headers(api_key):
    return {"Ocp-Apim-Subscription-Key" : api_key}

def get_params(search_term, num_imgs_per_transaction, offset):
    return urllib.parse.urlencode({
        "q": search_term,
        "license": "All",
        "imageType": "photo",
        "count":num_imgs_per_transaction,
        "offset": offset * num_imgs_per_transaction,
        "mkt":"ja-JP"})

def get_search_results(search_url, headers, params):
    response = requests.get(search_url, headers=headers, params=params)
    response.raise_for_status()
    search_results = response.json()
    return search_results

def save_urls(results, filepath):    
    with open(filepath, mode='a') as f:
        for values in results:
            if values['encodingFormat'] == 'jpeg':
                print(values['contentUrl'], file=f)

def get_filename(path, fn, ext):
    return os.path.join(path, '%s.%s' % (fn, ext))

def gen_url_save_file(search_term, url_dir_path, total_count):
    ext = 'txt'
    fn = bing_util.search_term2file_name(search_term)
    filename = get_filename(url_dir_path, fn, ext)
    dt = datetime.datetime.now().strftime("%Y%m%d%H%M%S")

    if os.path.isfile(filename):
        fn = '%s_%s' % (fn, dt)
        filename = get_filename(url_dir_path, fn, ext)
    with open(filename, mode='w') as f:
        print("date=%s, search_term=%s, totalEstimatedMatches=%d" % (dt, search_term, total_count), file=f)
    return filename


if __name__ == '__main__':
    config = configparser.ConfigParser()
    config.read('authentication.ini')
    bing_api_key = config['auth']['bing_api_key']

    save_dir_path = './bing'
    bing_util.make_dir(save_dir_path)
    url_dir_path = os.path.join(save_dir_path, 'url')
    bing_util.make_dir(url_dir_path)

    num_imgs_required = 1000 # Number of images you want.
    num_imgs_per_transaction =150 # default 30, Max 150 images
    search_term = "emeperor penguin"

    search_url = "https://api.cognitive.microsoft.com/bing/v7.0/images/search"

    headers = get_headers(bing_api_key)
    params = get_params(search_term, num_imgs_per_transaction, 0)

    first_search_results = get_search_results(search_url, headers, params)
    total_count = first_search_results["totalEstimatedMatches"]
    print("totalEstimatedMatches=%d" % total_count)

    filepath=gen_url_save_file(search_term, url_dir_path, total_count)

    print ("len=%d" % (len(first_search_results["value"])))
    save_urls(first_search_results["value"], filepath)

    if num_imgs_required > total_count :
        num_imgs_required = total_count

    offset_count = math.ceil(num_imgs_required / num_imgs_per_transaction)
    print('offset_count=%d' % offset_count)
    for offset in range(1, offset_count):
        params = get_params(search_term, num_imgs_per_transaction, offset)
        search_results = get_search_results(search_url, headers, params)

        print ("len=%d" % len(search_results["value"]))
        save_urls(search_results["value"], filepath)

URL収集と次の画像ダウンロードで共通する処理だけくくりだしたutil。

bing_util.py
import os
import re

def make_dir(path):
    if not os.path.isdir(path):
        os.mkdir(path)

def search_term2file_name(search_term):
    return re.sub('\s+', '_', search_term)

設定箇所

mainにある以下を編集します。

search_term
検索キーワードをいれます。多分日本語いけるはずですが、このキーワードを使ったファイルでURL取得リストを作るので、ちょっと注意したほうが良いかと思います。
save_dir_path
URL取得リストを保存するディレクトリを指定。ディレクトリがなければ作ります。
num_imgs_required
ほしい画像枚数。取得するURLリストの上限。ものすごくたくさん欲しくても、検索にマッチした画像がなければ、取れないのです。。。
num_imgs_per_transaction
Bingリクエスト一回あたりに取得する画像URL。Image Search APIのcountに渡す値です。countのデフォルトは30。

あと、Bing Search APIのkeyをこのスクリプトと同じディレクトリに、authentication.ini のファイル名で保存します。

authentication.ini
[auth]
bing_api_key = XXXXXXXXXXXXXXXXXXXXX

という感じに。

動きのざっくりした説明

一回のリクエストで取得できる結果がmax 150なので、たくさんほしいときは複数回投げます。複数回投げるときは、APIのパラメータのcountとoffsetを使います。
countとoffsetについては、参考にさせていただいた記事を御覧ください。わかりやすいので。

  1. 一回、検索パラメータを組み立てて、検索を投げる。レスポンスから、検索結果件数を取得。
  2. URL格納ファイルを作成。

    • ファイル名は "検索キーワード.txt"。ただし、検索キーワード中にスペースがある場合は""に変換。あと、出力ディレクトリに同じファイル名のファイルがあったら、"検索キーワードYYYYMMDDhhmmss.txt"と日時をくっつけたファイル名にする。
    • 一行目に以下の情報を出力し、二行目以降に結果のURLリストを出力。

    date=YYYYMMDDhhmmss, search_term=検索キーワード, totalEstimatedMatches=検索結果件数

  3. 検索結果件数 < num_imgs_required の場合は、num_imgs_requiredに検索結果件数をセットし直す。

  4. num_imgs_requiredとnum_imgs_per_transactionから、何回検索を投げるかを決めて、必要回数だけ投げて、結果をファイルに格納。

いくつか補足

  • Image Searchのパラメーターでlicenseを指定できます。サンプルではAllにしているので、licenseを無視してガバガバ集めます。APIドキュメント これをPublicとかにしていると取得結果がすっごく少なくなる。king penguinだと150件以上あるのに、adelie penguinだと8件しかないとか。
  • 取得する画像のタイプをjpegに制限しています。他の画像種類が必要でしたら、save_urlsを直してください。

画像ダウンロード用

前項で取得したURLリストのファイルを使って、画像をダウンロードするスクリプトです。動かし方は、

$ python3 bing_image_downloader.py URL格納ファイル
bing_image_downloader.py
# coding: utf-8
import requests
import os
import sys
import re
import hashlib
import json

import bing_util

def get_target(line):
    matchObj=re.match(r"date=(\d{14}), search_term=([^,]+)", line)
    return [matchObj.group(1), matchObj.group(2)]

def download_image(url, timeout=10):
    response = requests.get(url, allow_redirects=True, timeout=timeout)
    if response.status_code != 200:
        error = Exception("HTTP status: %d" % response.status_code)
        raise error

    content_type = response.headers["content-type"]
    if 'image' not in content_type:
        error = Exception("Content-Type: %s" % content_type)
        raise error

    return response.content

def gen_image_md5(image_data):
    return hashlib.md5(image_data).hexdigest()

def save_image_file(img_save_dir, content):
    filename = "%s.jpg" % (gen_image_md5(content))
    filepath = os.path.join(img_save_dir, filename)
    with open(filepath, "wb") as fout:
        fout.write(content)
    return filename

if __name__ == '__main__':

    filepath = sys.argv[1]
    save_dir_path = './bing'
    bing_util.make_dir(save_dir_path)

    file = open(filepath)
    lines = file.readlines()
    file.close()

    [target_date, search_term] =get_target(lines[0].rstrip())
    print (search_term)
    img_save_dir=os.path.join(save_dir_path, '%s_%s' % (bing_util.search_term2file_name(search_term), target_date))

    bing_util.make_dir(img_save_dir)

    correspondence_table = {}

    for url in lines[1:]:
        try:
            url = url.rstrip()
            content = download_image(url)
            filename = save_image_file(img_save_dir, content)
            correspondence_table[url] = filename
            print ("filename:%s" % filename)

        except KeyboardInterrupt:
            break
        except Exception as err:
            print("%s : %s" % (err, url))

    with open(os.path.join(img_save_dir, 'corr_table.json'), mode='w') as f:
        json.dump(correspondence_table, f)

動きのざっくりした説明

  1. URL格納ファイルを開いて、一行目の情報をとってきます。
  2. 画像をためるディレクトリを作成します。save_dir_path/URLファイル中の検索キーワード_URLファイル中のdate/ です。
  3. URLにリクエストを投げて画像を2.にためていきますが、同じ画像が別URLで置かれている場合があるので、画像をhash値にしてファイル名としています。
  4. 最後にURLと画像ファイル名の対応をcorr_table.jsonというファイル名で、画像保存ディレクトリに書き出します。

おわりに

大量に集めてきた画像を前に途方に暮れている現状。。。この先が大変。