いづいづブログ

娘2人をもつ札幌のシステムエンジニア。自分で勉強したこととか日常のこととかを残しています。アジャイル札幌スタッフ。パクチー大好き。激辛大好き。

FlickerAPIを使って任意の画像を取得する #2

f:id:izumii-19:20181206155816p:plain:w500

前回のFlickerAPIを使って任意の画像を取得する #1の続きで、ベタ書きコードを変数を使って自由度を高めてみる。*1

アドベントカレンダーの6日目の記事として載せるにもかかわらず、読んだ人が「ふーん。で?」ってなりそうな内容だけど、いいんです。

Linuxゲリラ戦記の#47〜#52あたり。

前回までのコードと、いきなり今回のコード

前回のコード

今回は完成したコードを参考に、どこをどう変えていったのかを追っていく感じで説明します。

まず前回までのコードはこちら。永遠にパクチーの画像しか取得できないコード。

#!/bin/sh

# "パクチー料理"をエンコードし、APIを使ってデータを取得
curl "https://api.flickr.com/services/rest/?\
method=flickr.photos.search&api_key=APIキー&format=rest&per_page=20\
&text=%E3%83%91%E3%82%AF%E3%83%81%E3%83%BC%E6%96%99%E7%90%86&extras=url_q" > temp.txt

# url-q の行だけを抜き出す
cat temp.txt | tr ' ' '\n'  | tr -d '"' | grep "url_q" > url.txt

# 「url-q=」を削除
cat url.txt | sed s/url_q=/''/ > result.txt

# 画像を取得
wget  -i result.txt -P image

今回のコード

#!/bin/sh

# キーワードを引数で受け取り、エンコードする
keyword=$1
keyword=`echo $keyword |nkf -wMQ|tr = %`

# キーワードが取得できなければメッセージを表示
if [ -z $1 ]
then
        echo "検索キーワードを入れてください"
        exit
fi

# キーワードをエンコードし、APIを使ってデータを取得
curl https://api.flickr.com/services/rest/?\
method=flickr.photos.search\&api_key=APIキー\&format=rest\&per_page=10\
\&text=${keyword}\&extras=url_q > response.txt

#レスポンスのstatusをみて、ok以外だったら処理を終了
response="response.txt"
status=`cat $response | grep "<rsp stat=" | sed -e 's/<rsp stat="//g' -e 's/">//g'`
if [ $status != "ok" ]; then
  echo "ステータスは[ $status ]でした。処理を終了します。"
  rm response.txt
  exit
fi

#取得件数をみて0件だったら処理を終了
totalCount=`cat $response | tr ' ' '\n' | tr -d '"' | grep "total=" | sed s/total=/''/`
if [ $totalCount -eq 0 ]; then
  echo "該当する画像は $totalCount 件でした。処理を終了します。"
  rm response.txt
  exit
fi

# "url_q="の行だけを抜き出す
cat response.txt | tr ' ' '\n'  | tr -d '"' | grep "url_q=" | sed s/url_q=/''/ > result.txt

# 画像を取得(タイムアウト30秒、リトライなし)
# キーワードをエンコードし、APIを使ってデータを取得
wget  -T 30 -t 1 -i result.txt -P image

# 不要なファイル削除
rm result.txt response.txt

以下、ざっくりと変更点。

  • キーワードを引数で指定できるようにした
  • キーワードが指定されていないときに入力を促したり、エラー処理をちょっとだけいれた
  • リトライしないようにした
  • タイムアウト時間を設定した
  • 不要な中間ファイルは削除するようにした

これ以外にも、毎回画像を20件取得するときの待ち時間が長いので10件にしたり、中間ファイル名がちょっとイマイチだから変更したりしたけど、この辺りは本題と関係ないので特に説明はしない。

ということを前置きしておいて…、以下変更点を解説する。

解説

1. キーワードを引数でうけとる

前回までは、エンコード済みのキーワードをそのままクエリの引数に指定していたが、今回はキーワードそのものをユーザーが指定できるようにした。

keyword=$1
keyword=`echo $keyword |nkf -wMQ|tr = %`

$1は引数処理に使用する変数。$1は「1つ目の引数」と言う意味で、2つ目の引数は$2、3つ目なら$3…というように書く。10こ目以降の引数は${10}と書く。

これは先頭文字列からみていった時に「"$1"という引数と"0"という文字列」なのか「"$10"」という引数なのかを明確に区別するために{}で括っている。

コマンドの実行結果を変数に入れる場合は、バッククォート(`)で囲む。そして「=」の前後にスペースは入れない!
この2つは結構忘れる・・・。

変数とか書き方で参考にしたサイトはここ。

2. クエリの引数を固定文字列から変数に変更する

text=パクチー料理をエンコードした文字列にしていたところ、text=${keyword}にする。 変数の値を参照する時には$をつける(代入する際は$を使わない)。

&text=${keyword}

3. curlの引数となるURL文字列を""でくくるのをやめる

実は「2.」の変更だけではうまくいかない。なぜならcurlの引数は全て、

curl "https://api.flickr.com ~中略~ &extras=url_q"

のように文字列扱いされているので、その中を変更しても結局は文字列の文字を変更しているに過ぎないからである。なので、""でくくるのをやめる。

そもそも””でくくっていたのは「&」がシェルの「&」と競合してしまい正しく解釈されていなかったからなので、""で全体をくくるのをやめるかわりに「&」を「\&」でエスケープすれば同じことである。

タイポしないように1つ1つエスケープする。目が乾くわぁ。

# キーワードをエンコードし、APIを使ってデータを取得
curl https://api.flickr.com/services/rest/?\
method=flickr.photos.search\&api_key=APIキー\&format=rest\&per_page=20\
\&text=${keyword}\&extras=url_q > response.txt

4. 検索キーワードの入力を促す

キーワードを引数で受け取るように変更したので、もしキーワードを指定し忘れていたらメッセージを出力するようにするif文を書く。

if [ -z $1 ]
then
        echo "検索キーワードを入れてください"
        exit
fi

if [ -z $1 ]は「$1の変数が空だったら」という意味。わかりづらい。*2

そして、ifの条件式にあてはまればthen以降の処理を行う、つまり"検索キーワードを入れてください"ってメッセージを表示して、処理を中断する。

5. 処理が正常に終了しなかったらメッセージを表示

いきなりだけど完成コード。

#レスポンスのstatusをみて、ok以外だったら処理を終了
response="response.txt"
status=`cat $response | grep "<rsp stat=" | sed -e 's/<rsp stat="//g' -e 's/">//g'`
if [ $status != "ok" ]; then
  echo "ステータスは[ $status ]でした。処理を終了します。"
  rm response.txt
  exit
fi

curlコマンドを実行した後に取得した結果をresponse.txtという中間ファイルに保存しているのだが、その中にステータスを表すデータstat=ok(失敗するとfail)が存在するので、その値を見て正常か異常かを判断する。

response.txtからgrepやら、sed やらをつかってstat=■の■のみ取得できるように処理を行う。

この辺はFlickerAPIを使って任意の画像を取得する #1でやったのと同じようにやれば良い。

6.画像が1つもが取得できなかったらメッセージを表示

#取得件数をみて0件だったら処理を終了
totalCount=`cat $response | tr ' ' '\n' | tr -d '"' | grep "total=" | sed s/total=/''/`
if [ $totalCount -eq 0 ]; then
  echo "該当する画像は $totalCount 件でした。処理を終了します。"
  rm response.txt
  exit
fi

この処理はおまけで入れてみた。この辺もやりかたは同じなので説明は割愛。

7.リトライ回数とタイムアウトを指定

wgetコマンドは、"アクセス不許可"と"ファイルが見つかりません(404)"エラー以外で画像が取得できない場合は、20回も画像の再取得に挑戦するらしい。
今はそんなにリトライしたくないので、リトライを行わないようにする。
くわえて、ここでタイムアウト時間も設定する。

リトライは「ネットワーク接続はできているんだけれど、なんらかの別な理由でレスポンスが返ってこないような時に何回トライするか」のことで、
タイムアウトはそもそもネットワーク接続がうまくいかないときに何秒間接続を試みるかのこと。

◯リトライの指定

$ wget -t チャレンジする回数

タイムアウトの指定

$ wget -T タイムアウト時間(s)

で、今回はリトライはしない(接続は1回)のと、タイムアウトまでは30秒にするのでこうなる。

wget  -T 30 -t 1 -i result.txt -P image

8.中間ファイルは削除

中間ファイルは削除する。

rm result.txt response.txt

以上!

実行結果

レッサーパンダの画像を取得してみる。シェルスクリプト実行時に「レッサーパンダ」と指定。

$ ./flicker.sh レッサーパンダ

f:id:izumii-19:20181206152302p:plain:w500

ちゃんと通信している。

f:id:izumii-19:20181206005614p:plain:w500

取得できた!
次にキーワードを指定しわすれたらメッセージを表示されるかどうかを確認。

$ ./flicker.sh

f:id:izumii-19:20181206153445p:plain:w500

OK!続けてステータス異常の場合を検証してみる。あらかじめAPIキーを架空のキーにしてエラーが返ってくるように仕込んでおく。

f:id:izumii-19:20181206152426p:plain:w500

ステータスが[fail]である旨のメッセージが表示された。本当は「異常です。」とかって言ってあげたいところだけど、まあやりたいことはできたので良しとする。

最後に指定したキーワードでの検索結果が0件の場合。

$ ./flicker.sh ぱんぱんだんぱ

f:id:izumii-19:20181206153648p:plain:w500

OK。ということで今回をもってFlickerAPIを使って任意の画像を取得する学習は終了!

かなり勉強になったし簡単なシェルスクリプトならもう書ける気がするくらい色んなことを覚えたと思うけどつかれた。

*1:ちなみに仕事でベタ書きするとクソコード扱いされる

*2:ifの書き方いろいろ kei0310.info