PythonからFlickr APIを使う

# はじめに
## 概要
[Photos | PyCon JP 2014 in TOKYO](https://pycon.jp/2014/reports/photos/)にあるFlickrの写真をごにょごにょするPythonスクリプトを作ったときのメモ。
以下の表に従い、

|したいこと|Flickr APIのURI|Flickr APIの引数|
|:--------|:--------------|:------------|
|アルバムの一覧を取得|flickr.photosets.getList|user_id|
|アルバム内の写真を取得|flickr.photosets.getPhotos|photoset_id|
|写真のURLを取得|flickr.photos.getSizes|photo_id|

1. requestsでHTTPリクエスト投げる
2. 返ってきたXMLをxml.dom.minidomでパースする

の二つの作業を行っているだけです。
flickr.photos.getSizesの中に、サイズごとの写真のURLが入っているのがちょっと意外だったかな。

同じようなことをやっている記事が既にいくつかありました。
結果をJSONで取得したり、flickr.photos.getSizesではなくURLを直接構成している点がこの記事と異なり参考になります。
URL構成規則が公開されているならAPI叩くより自分で構成したほうが賢いですね。

* [Qiita - requestsを使ってWebAPIをたたく 例題：Flickr](http://qiita.com/kosystem/items/4879931c48d29eb37714)
* [BTY備忘録 - PythonでFlickr APIを使って写真を検索する](http://bty.sakura.ne.jp/wp/archives/1196)

## 前提
前提条件は以下の通りです。

* [requests](http://docs.python-requests.org/en/latest/)がインストールされていること
* [Flickr](https://www.flickr.com/)のアカウントとAPIキーを持っていること


なお、requestsの導入及び使い方については次の記事が参考になります。

* [Requests の使い方 (Python Library)](http://qiita.com/sqrtxx/items/49beaa3795925e7de666)

FlickrのAPIキー取得については次の記事が参考になります。

* [Flickr APIを使ってみよう(1.APIキーの取得)](http://techbooster.org/android/mashup/14828/)


# コード

最初Python2でやったら動かなかったのでちょっと応急処置中です。ひとまずはPython2でも動作しますが、後でちゃんと直すかもしれません。

```py:flickr.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import requests
import xml.dom.minidom as md

class Flickr(object):
    u"""
    基本的な使い方

    # 適宜修正してください
    api_key = 'ENTER_YOUR_API_KEY'
    
    f = Flickr(api_key)

    # pyconjpのuser_id
    print(f.get_photoset_ids_from_user_id('102063383@N02'))
    # PyCon JP 2014 day4 Sprintフォトセットのphotoset_id
    print(f.get_photos_from_photoset_id('72157647739640505'))
    # 上記フォトセットの中の写真のひとつ
    print(f.get_url_from_photo_id('15274792845'))
    """

    def __init__(self, api_key):
        self.api_url = 'https://api.flickr.com/services/rest/'
        self.api_key = api_key

    def get_photoset_ids_from_user_id(self, user_id):
        u"""
        ユーザー(user_id)が所有するアルバムidの一覧(photset_idのリスト)を返す
        今回の目的には必要ない
        """
        # requestの送信
        r = requests.post(self.api_url, {'api_key': self.api_key,
                                         'method': 'flickr.photosets.getList',
                                         'user_id': user_id
                                         })        

        # xmlをパースしてdomオブジェクトにする
        dom = md.parseString(r.text.encode('utf-8'))

        # domオブジェクトからphotoset_idを探し出す
        result = []        
        for elem in dom.getElementsByTagName('photoset'):
            result.append(elem.getAttribute('id'))            
        return result

    def get_photos_from_photoset_id(self, photoset_id):
        u"""
        アルバムid(photset_id)内の写真一覧(photo_idのリスト)を返す
        """
        # requestの送信
        r = requests.post(self.api_url, {'api_key': self.api_key,
                                         'method': 'flickr.photosets.getPhotos',
                                         'photoset_id': photoset_id
                                         })  

        # xmlをパースしてdomオブジェクトにする
        dom = md.parseString(r.text.encode('utf-8'))

        # domオブジェクトからphoto_idを探し出す
        result = []
        for elem in dom.getElementsByTagName('photo'):
            result.append(elem.getAttribute('id'))            
        return result

    def get_url_from_photo_id(self, photo_id):
        u"""
        写真(photo_id)が実際に格納されているURLを返す
        """
        # requestの送信
        r = requests.post(self.api_url, {'api_key': self.api_key,
                                         'method': 'flickr.photos.getSizes',
                                         'photo_id': photo_id
                                         })        

        # xmlをパースしてdomオブジェクトにする
        dom = md.parseString(r.text.encode('utf-8'))

        # domオブジェクトからURLを探し出す
        result = None
        for elem in dom.getElementsByTagName('size'):
            # オリジナルのサイズのもののみにする
            if elem.getAttribute('label') == 'Original':
                result = elem.getAttribute('source')
                # オリジナルは1個だと考えて他はスキップ
                break
            else:
                # 何もない場合はNone
                pass
        return result

if __name__ == '__main__':
    # 動作確認
    
    # 適宜修正してください
    api_key = 'ENTER_YOUR_API_KEY'

    f = Flickr(api_key)
    
    # pyconjpのuser_id
    print(f.get_photoset_ids_from_user_id('102063383@N02'))
    # PyCon JP 2014 day4 Sprintフォトセットのphotoset_id
    print(f.get_photos_from_photoset_id('72157647739640505'))
    # 上記フォトセットの中の写真のひとつ
    print(f.get_url_from_photo_id('15274792845'))
```

# 実行結果

## コードの実行結果

mainの中の'ENTER_YOUR_API_KEY'を自分のAPIキーに書き換えて

```bash
python flickr.py
```
を実行すると、以下の写真のURLが入手できます。

* https://www.flickr.com/photos/pyconjp/15274792845/in/set-72157647739640505

## 使用例

URLをまとめて取得するには、flickr.pyをインポートするなりコピペするなりして、以下のようにすればよいでしょう。
なお、ここでもENTER_YOUR_API_KEYの部分は自分のAPIキーに変更してください。
また、テスト実行時に時間がかからないようにbreakがついているので、実際の実行時にはbreakを取ってください。

targetsとかを指定するのが面倒なら、get_photoset_ids_from_user_idを使えば特定のユーザーの全アルバムが取得でき、そちらを使うののありかもしれません。これだとかなりクローリングぽくなりますね。

```pycon
>>> from collections import defaultdict

>>> api_key = 'ENTER_YOUR_API_KEY'     # 適宜修正してください

>>> f = Flickr(api_key)

>>> targets = ['72157641092641983',    # PyCon JP 2014 下見 - an album on Flickr
...            '72157647111767068',    # PyCon JP 2014 day1 Tutorial - an album on Flickr
...            '72157647184237569',    # PyCon JP 2014 day2 Conference - an album on Flickr
...            '72157647216509890',    # PyCon JP 2014 day3 Conference - an album on Flickr
...            '72157647739640505'     # PyCon JP 2014 day4 Sprint - an album on Flickr
...           ]

>>> d = {}
>>> for elem in targets:               # photosetの中のphotoの一覧を作成
...     d[elem] = f.get_photos_from_photoset_id(elem)
...     break    # 取る
    
>>> d2 = defaultdict(list)
>>> for k,v in d.items():              # データのurl一覧を作成
...     for elem in v:
...         d2[k].append(f.get_url_from_photo_id(elem))
...         break    # 取る
...     break    # 取る
    
>>> for k,v in d2.items():             # urlの一覧を用いてファイルを取得
...     if not os.path.exists(k):
...         os.mkdir(k)
...     for elem in v:
...         r = requests.get(elem)     # urlからデータを取得
...         # photoset_id/ファイル名.jpg で保存
...         with open("{0}/{1}".format(k, elem.split("/")[-1]), 'wb') as g:
...             g.write(r.content)
```

# 今後
* 大した内容はありませんが、個々の機能の解説を書くかもしれません。
* コード自体は改善の余地が大ありなのでアドバイスを頂ければと思います。