Flickr APIをPythonでたたいて画像検索・EXIF取得する

#はじめに

[link-1]:https://www.flickr.com/services/api/

Flickrに投稿する人のカメラの設定を分析できるように、[Flickr API][link-1]を使って画像検索、EXIF取得できるPythonプログラムを作ってみた。
[FlickrAPI][link-1]の導入についてはいろいろなサイトで紹介があるので割愛するが、[例えばこちら](https://qiita.com/Saayaman/items/a3066697a108a7e7fc39)を参照。

#pythonプラグラム

##参考文献

以下の情報を参考にして、プログラムを作成した。

+ https://qiita.com/Saayaman/items/a3066697a108a7e7fc39
+ http://bty.sakura.ne.jp/wp/archives/1196
+ https://stackoverflow.com/questions/43437364/converting-json-to-csv-using-python-pandas

上記に対してオリジナルなところはEXIFを取得することで、jsonデータの平滑化をすることでCSVファイルに所望の状態でEXIF情報を書きだすことができた。

##作成ポイント

###ライブラリ

以下のライブラリをimportして設計した。

+ [requests](http://docs.python-requests.org/en/master/)
+ [pandas](https://pandas.pydata.org/)

[Flickr API][link-1]へのアクセスはrequestsを使用する。

###auth key情報

[Flickr API][link-1]を使えるようにすると自分のkeyが発行されるので、それを`auth_flickr.py`に記載する。

```py:auth_flickr.py
flickr_key = "1234567890abcdefghijklmnopqrstuv"
```
メインプログラム側では、上記をimportする。

```py:
from auth_flickr import(
    flickr_key
)
```

###Flickr画像の検索

画像検索用のペイロードをjson形式で作成し、[Flickr API][link-1]の[flickr.photos.search](https://www.flickr.com/services/api/flickr.photos.search.html)を使うリクエストを投げて、画像検索する。


```py:
#画像検索用のjsonデータ
payload = {
    'method': 'flickr.photos.search',
    'api_key': flickr_key,
    'text': 'mountain moon',
    #'extras': 'date_upload',
    #'has_geo': 1,
    #'place_id' : '36i2ieFQU7uqDINu',
    'per_page': '10',
    'format': 'json',
    'nojsoncallback': '1'
    }

#画像検索
r = requests.get(url, params=payload)
resp = r.json()
#print (json.dumps(resp, sort_keys=True, indent=2))
```
**'text'**には、検索したいキーワードを記載する。AND条件は半角スペースで併記すればよいようだが、やや検索結果が所望とは外れてくるようだ。

**'text'**は日本語もOKだが、（ほぼ）日本人の撮影した写真に限定される。。。

**'per_page'**で指定した数だけ検索する。この例では10個検索している。

**'place_id'**を定義すると、写真の撮影された場所を限定できる。**'place_id'**は、[こちら](https://www.flickr.com/services/api/explore/flickr.places.find)で検索できる。これがなかなか見つけられず苦労した。コメントのものは**yokohama**の**'place_id'**。

###画像検索結果の取得とEXIF情報の取得

表示用URLの雛型記述を[Photo Source URLs](https://www.flickr.com/services/api/misc.urls.html)に従って**tpl_url**に準備する。

```py:
#表示用URLの雛型
tpl_url = 'https://farm%s.staticflickr.com/%s/%s_%s.jpg'
```

以下はfor文で一気に記載している。

検索結果はjson形式で**resp**に格納されているので、検索結果の情報が格納されている**resp['photos']['photo']**を順次呼び出して、表示用URLを生成、画像取得する。

次に、EXIF取得用のペイロードをjson形式で作成し、[Flickr API][link-1]の[flickr.photos.getExif](https://www.flickr.com/services/api/flickr.photos.getExif.html)を使うリクエストを投げて、結果を**resp2**に格納する。

EXIFが取得できない画像もあるので、リクエスト結果をチェックして**resp2['stat'] == 'fail'**であれば、EXIFをCSV化する処理をスキップする。

EXIFが取得出来たらpandasでjsonデータを平たん化して、csvに書き出す。これをやらずに**pd.DataFrame(resp2['photo']['exif'])**としてしまうと、EXIF情報が1つのセル情報にまとまって書き出されてしまうので、注意。

```py:
#検索結果画像の情報処理
count = 1
for i in resp['photos']['photo']:
    #表示用URLの生成
    img_url = tpl_url % (i['farm'],i['server'],i['id'],i['secret'])
    print ("#%04d" % count, img_url)
    
    #画像取得
    r = requests.get(img_url)
    fname = "%04d.jpg" % count
    f = open(fname, 'wb')
    f.write(r.content)
    f.close()

    #EXIF取得用のjsonデータ
    payload2 = {
        'method': 'flickr.photos.getExif',
        'api_key': flickr_key,
        'photo_id' : i['id'],
        'format': 'json',
        'nojsoncallback': '1'
    }
    
    #EXIF取得
    r2 = requests.get(url, params=payload2)
    resp2 = r2.json()
    #print (json.dumps(resp2, sort_keys=True, indent=2))
    
    #EXIFがない場合はスキップする
    if resp2['stat'] == 'fail' :
        count += 1
        continue

    #pandasでjsonデータを平たん化してcsvに書き出す
    df = pd.read_json(json.dumps(resp2, sort_keys=True, indent=2))
    #exif = pd.DataFrame(resp2['photo']['exif'])
    exif = pd.io.json.json_normalize(resp2['photo']['exif'])
    exif.to_csv("%04d.csv" % count)

    count += 1

```

##作成結果

メインプログラム全体は以下。関数化すればよい部分もあるが、実験用の初版なのでこれで良しとした。

```py:GetFlickrImagesExif.py
# -*- coding: utf-8 -*-
import json
import requests
import pandas as pd

url = 'https://api.flickr.com/services/rest/'
from auth_flickr import(
    flickr_key
)

#画像検索用のjsonデータ
payload = {
    'method': 'flickr.photos.search',
    'api_key': flickr_key,
    'text': 'mountain moon',
    #'extras': 'date_upload',
    #'has_geo': 1,
    #'place_id' : '36i2ieFQU7uqDINu',
    'per_page': '10',
    'format': 'json',
    'nojsoncallback': '1'
    }

#画像検索
r = requests.get(url, params=payload)
resp = r.json()
#print (json.dumps(resp, sort_keys=True, indent=2))

#表示用URLの雛型
tpl_url = 'https://farm%s.staticflickr.com/%s/%s_%s.jpg'

#検索結果画像の情報処理
count = 1
for i in resp['photos']['photo']:
    #表示用URLの生成
    img_url = tpl_url % (i['farm'],i['server'],i['id'],i['secret'])
    print ("#%04d" % count, img_url)
    
    #画像取得
    r = requests.get(img_url)
    fname = "%04d.jpg" % count
    f = open(fname, 'wb')
    f.write(r.content)
    f.close()

    #EXIF取得用のjsonデータ
    payload2 = {
        'method': 'flickr.photos.getExif',
        'api_key': flickr_key,
        'photo_id' : i['id'],
        'format': 'json',
        'nojsoncallback': '1'
    }
    
    #EXIF取得
    r2 = requests.get(url, params=payload2)
    resp2 = r2.json()
    #print (json.dumps(resp2, sort_keys=True, indent=2))
    
    #EXIFがない場合はスキップする
    if resp2['stat'] == 'fail' :
        count += 1
        continue

    #pandasでjsonデータを平たん化してcsvに書き出す
    df = pd.read_json(json.dumps(resp2, sort_keys=True, indent=2))
    #exif = pd.DataFrame(resp2['photo']['exif'])
    exif = pd.io.json.json_normalize(resp2['photo']['exif'])
    exif.to_csv("%04d.csv" % count)

    count += 1
```

デバッグで、EXIF情報の一部を標準出力に出した時の記述は以下。json形式にまだ慣れていなくて、記述方法はけっこう試行錯誤した。

```py:    
    #標準出力にEXIFの情報を選択して出力する場合
    for j in resp2['photo']['exif']:
        if j['label'] == 'Model' :
            print(j['raw']['_content'])
        elif j['label'] == 'Exposure' :
            print(j['raw']['_content'])
        elif j['label'] == 'Aperture' :
            print(j['raw']['_content'])
        elif j['label'] == 'ISO Speed' :
            print(j['raw']['_content'])
```

##やってみる

以下を実行し、[FlickrAPI][link-1]で所望の画像を検索し、EXIF情報を取得してCSVに書き出すことができた。

```shell-session:
$ python3 GetFlickrImagesExif.py
```

#さいごに

これまで作成してきたpythonのrequestsで、[FlickrAPI][link-1]も比較的早く使いこなせるようになった。

今回のプログラムは、EXIFのCSVファイルが画像ファイルごとにできる仕様だが、分析用にはひとつのCSVファイルにまとまっていると使いやすい。

pandasは初めて使ったが、もっと多くの機能があるようなので、使いこなせるようになるとよいと感じた。