【シェルと遊ぼう】はてなブログAPIを叩くシェルスクリプトを作ろう

はじめに

はてなブログには、AtomPubというプロトコルに則ったAPIが提供されている。

このAPIを使えば、下記の操作ができる。

  • サービス一覧の取得
  • カテゴリ一覧の取得
  • 一定件数ずつ記事の取得
  • 1つの記事に対して
    • 取得
    • 更新
    • 削除
  • 新しい記事の投稿

今回は、上記操作を行うために、HTTPリクエストを送るスクリプトを作ってみる。

必要となる、はてなブログAPIについての知識は下記ページにまとめた。
ひと目通しておくと、作るものが見えてくるかと思う。

つくるもの

今回のスクリプトの概要はこちら。 リクエストを送って、レスポンスを出力するのがお仕事です。
(受信したレスポンスは一切加工しない)

  • はてなブログAPIサービスにHTTPリクエストを送信
    • HTTPメッソドや送るパス、ボディコンテンツをユーザが指定
    • 認証情報は予め用意したファイルから読み込む
      • 未指定時はデフォルトパスのファイルを利用
    • nextリンクがあるなら自動で次のページをGETしにいく
      • つまり記事ページで最新のものから過去に向かって全部とってくる
      • オプションで抑制可(entryタグが50件見つかったら取得をやめる、など)
  • HTTPレスポンスのボディを標準出力に出力
  • その他オプション
    • APIのHTTPリクエスト・レスポンスヘッダを標準エラーに出力
    • 次の取得ページのURLを指定先(標準出力|標準エラー|ファイル)に出力

以降、今回作るスクリプト名をhateblog-requestとする前提で説明していく。

ヘルプ

引数・パラメータ

パラメータやオプションの意味は以下の通り。

[Usage]
  hateblog-request GET    {Path} [-a Auth-File][-v][-n FD][-l Num[:Target]]  
  hateblog-request DELETE {Path} [-a Auth-File][-v]  
  hateblog-request PUT    {Path} [-a Auth-File][-v][-f Tx-File]  
  hateblog-request POST   {Path} [-a Auth-File][-v][-f Tx-File]  
引数・パラメータ 意味
GET,DELETE,PUT,POST HTTPリクエストのメソッドそのまま
{Path} HTTPリクエストを送るURL({ルートURL}以降でも良い)
-a Auth-File はてなブログAPIの認証情報ファイル
-v HTTPレスポンス取得用
-n FD 次のページリンクURLの出力(標準出力(=1) or 標準エラー(=2) or ファイルパス)
-l Num[:Target] TargetがNum回以上出現したら次のページの取得をやめる
-f Tx-File 送信する記事のファイル(XML形式)を指定する(未指定時は標準入力)

使用例

以下のような感じで使う。

> hateblog-request GET                      # サービス文書を取得  
> hateblog-request GET -a file              # 認証ファイル指定する例  

> hateblog-request GET category             # カテゴリ一覧  

# 記事コレクションの取得(いずれも最新の記事から過去に向かって)
> hateblog-request GET entry                # 一定件数ずつ(サービスの仕様に依存)  
> hateblog-request GET entry -n 2           # 一定件数ずつ、次のページLinkを標準エラーに出力  
> hateblog-request GET entry -l 50          # 記事を50件以上取得するまでページを取得を続ける  
> hateblog-request GET entry -l 50:"<td>"   # <td>を50個以上見つけるまで取得  
> hateblog-request GET entry?page=XXXXX     # 指定ページから過去に向かって取得  

# 指定の1つの記事の操作
> hateblog-request GET entry/xxxxx          # 指定の記事を取得  
> hateblog-request DELTEL entry/xxxxx       # 指定の記事を削除  
> hateblog-request PUT entry/xxxxx < file   # 指定の記事を更新(標準入力からのデータを送信)  
> hateblog-request PUT entry/xxxxx -f file  # 指定の記事を更新(指定ファイルを送信)  

# 記事の新規投稿
> hateblog-request POST entry < file        # 新しい記事を投稿(標準入力からのデータを送信)  
> hateblog-request POST entry -f file       # 新しい記事を投稿(指定ファイルを送信)  

出力

HTTPレスポンスのボディを、そのまま標準出力に出力する。
以下は、サービス文書取得時の出力結果例。

<?xml version="1.0" encoding="utf-8"?>  
<service xmlns="http://www.w3.org/2007/app">  
  <workspace>  
    <atom:title xmlns:atom="http://www.w3.org/2005/Atom">HacoLab</atom:title>  
    <collection href="https://blog.hatena.ne.jp/sample/sample.hatenablog.com/atom/entry">  
      <atom:title xmlns:atom="http://www.w3.org/2005/Atom">HacoLab - 記事一覧</atom:title>  
      <accept>application/atom+xml;type=entry</accept>  
    </collection>  
  </workspace>  
</service>  

認証ファイル例

デフォルトの認証ファイルは、~/.local/share/hateblog/authとした。
無かったから自動で上記パスにファイルを作ってしまうお行儀が悪い子。

設定ファイルは以下みたいな感じ。各自の情報に合わせて入力しよう。
(trimとかしてないので余計な空白とか余計な情報入れちゃうとアウト)

BLOG_ID=sample.hatenabolg.com
HATENA_ID=myhatenaid
API_KEY=myapikey

必要なもの

今回はcurlを利用するので、インストールしていない人はしておいてね。

FreeBSDの場合は、下記コマンドでインストールできる。
(管理者権限で実行してね)

> pkg install curl  

コード

今回のコードは、長いのでリクエストを送るところ辺りを抜粋で掲載。
コード全体はこちらを見てねと。

何か変なところがあれば、ご指摘下さいまし。

HTTPリクエストによる分岐

HTTPリクエスを送る箇所は、以下みたいな感じ。
第1引数のパラメータによって、curlへのパラメータ・オプションを切り替えている。
GETのときだけは、次のページを取得するために繰り返しリクエストを送るなどの処理が必要になるため関数化している。

case "$HTTP_METHOD" in  
    GET      )  get_request_pages "$REQUEST_URL" "$COUNT_PATTERN" "$LIMIT_COUNT" ;;  
    DELETE   )  curl $CURL_OPT "$REQUEST_URL" ;;  
    PUT|POST )  curl $CURL_OPT --data-binary @"$TX_FILE" "$REQUEST_URL" ;;  
    *        )  error_exit "Invalid method '$HTTP_METHOD'" ;;  
esac  

カールのオプションは以下になっている。
-Xには、第1引数で指定したGETなどのパラメータ。
-uには、認証情報ファイルから読み出した値を指定している。

また、本スクリプト-vオプション指定時は、curlの-v`でHTTPヘッダを標準エラーに出力している。

CURL_OPT="-s -H 'Content-Type: applicatoin/xml' -X $HTTP_METHOD -u $HATENA_ID:$API_KEY"  
[ -n "$PRINT_HEADER" ] && CURL_OPT="$CURL_OPT -v"

GETによる次のページの繰り返し取得

GETリクエスト時の処理は以下。
curlのレスポンスを後で読み出すために、teeで一時ファイルにも出力している。

次のページのURLは、取得したレスポンスの<link rel="next"href="xxxx"に入っている。
このタグが先程取得したレスポンスから見つからなければ、breakで取得ループを抜ける。

-lオプション未指定時は、continueでループ先頭に戻り、次のページのリクエストを送る。

-lオプション指定時は、指定件数以上の項目が見つかったかチェックする。
見つかっていない場合は、指定件数から今回見つけた件数を引いて、次のページのリクエストを送る。

get_request_pages() {  
    # Clear temp  
    >| "$TMP_HEADER"  

    # Get collection pages  
    next_page=$1  
    limit_count=$3  
    while :  
    do  
        # Send HTTP request  
        curl $CURL_OPT "$next_page" | tee "$TMP_BODY"  

        # Next page exist?  
        next_page=`grep '<link rel="next"' < "$TMP_BODY" | sed 's/^.*href="\([^"]*\)".*/\1/'`  
        [ -z "$next_page" ] && break  

        # No limit?  
        [ "$limit_count" -eq 0 ] && continue  

        # Over limit?  
        match_count=`grep -Ec "$2" "$TMP_BODY"`  
        [ "$limit_count" -le "$match_count" ] && break;  
        limit_count=`expr $limit_count - $match_count`  
    done >&1  

    # Print next page URL  
    [ -z "$PRINT_NEXT_URL" ] && return  
    fomated_next_url="NEXT>$next_page"  
    case "$FD_OF_NEXT_URL" in  
        1 ) echo "$fomated_next_url" >&1  ;;  
        2 ) echo "$fomated_next_url" 1>&2 ;;  
        * ) echo "$fomated_next_url" > "$FD_OF_NEXT_URL" ;;  
    esac  
}  

最後のwhileを抜けた後の処理は、-nオプション指定時の処理。
次のページのURLを、標準出力、標準エラー、ファイルのいずれかに出力している。

取得を繰り返すには、こちらの方のように、再帰で実装する方法もあった。
ただ、次のページがいくつ見つかるか分からない中、コールスタックを積んでいくのはちょっと気が乗らなかったのでループにしてみた。
でも、再帰の方がシンプルで見た目もいいですよね。

最後に

今回は、はてなブログAPIを叩く一番下回りのスクリプトを作成した。

コマンドやAPIなどの仕様を考えるのって結構好き。

どういう仕様にするかで使い勝手とどこまで汎用的に使えるか変わっていく。

今回は、シンプルにサービスにリクエスを送ることに専念する子を作った。

これで記事の取得から投稿まで、冒頭で説明した操作は全部できる。

とはいえ、これだけでは日常的に使えるものではない。

はてなブログからのレスポンスはXML形式で送られてくる。

実際に使うには、そのXMLデータを解析しなければいけない。

次回は、このXMLデータから欲しい情報を取り出すスクリプトを作っていこう。

確認環境

PC Thinkpad X1 Carbon 2nd Gen
OS 12.0-RELEASE-p7
cURL curl 7.65.1

参考

以上。