とりあえずはてなAPIをサクッとshellで実行したい人に向けて。
自分の記事を取得する
curl -u {はてなID}:{APIキー} https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry
はてなAPIはOAuth認証、WSSE認証、Basic認証のいずれかを行う必要があるので、curlで一番楽なBasic認証を使用する。
curlでのBasic認証はcurl -u ユーザーID:ユーザーPASS
でいけますね。
はてなID、ブログID、APIキーは「はてぶのダッシュボード > 設定 > 詳細設定 > AtomPub」から取得できる。
記事の取得のエンドポイントは/entry
なので、 実際に記事を取得するときは以下のような感じ。
curl -u hatena_id:XXXXXXXXX https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry
これで最新10件の記事を取得できる。
ドキュメントには最新7件と書いてあるが、現在は10件取得できるようだ。
スクリプトにしておくと楽。 hatena.sh
#!/bin/sh API_KEY='XXXXXXXX' HATENA_ID='hatena_id' BLOG_ID='blog_id' ENDPOINT_ROOT="https://blog.hatena.ne.jp/${HATENA_ID}/${BLOG_ID}/atom" ENDPOINT_ENTRY='/entry' # 記事の取得 curl -su ${HATENA_ID}:${API_KEY} ${ENDPOINT_ROOT}${ENDPOINT_ENTRY}
特定の記事を取得する
自分の書いた特定の記事を取得したい場合、/entry/エントリーID
で取得する。
ただ、このエントリーIDの取得が少々面倒くさく、/entry
で叩いたときのレスポンスからしか取得できない。
/entry
のレスポンス内の
<link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/10257846132688559263"/>
の10257846132688559263
の部分。これがエントリーID。
なのでcurlで叩くとしたらこう。
# 特定の記事を取得 curl -su ${HATENA_ID}:${API_KEY} ${ENDPOINT_ROOT}${ENDPOINT_ENTRY}/10257846132688559263
また、/entry
では最新10件しか取得できないので10件目以降を取得するには/entry?page={ページID}
で取得する必要がある。
このページIDも/entry
を叩いたときのレスポンスからしか取得できない。
/entry
のレスポンス
<link rel="first" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry" /> <link rel="next" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=1536762675" />
ページIDを指定して取得するとしたらこう
# 2ページ目の記事一覧を取得 curl -su ${HATENA_ID}:${API_KEY} ${ENDPOINT_ROOT}${ENDPOINT_ENTRY}?page=1536762675
少しまとめると
- 特定の記事を取得したい場合、その記事のエントリーIDが必要になる
- エントリーIDは
/entry
のレスポンスからしか取得できない - 1ページ目以降の記事を取得したい場合、ページIDを指定する必要がある
- ページIDは
/entry
のレスポンスからしか取得できない
ちょっと面倒くさく感じるけど/entry
のレスポンスに欲しい情報が全部詰まってるから楽っちゃ楽かな。他のAPI叩く必要がない。
全ての記事のエントリーIDを取得する
もうここからは単純にシェル芸になる。
流れとしては
/entry
を叩く- 各記事のエントリーIDを抽出
- 2ページ目もあればページIDを抽出
/entry?page=ページID
を叩く- 2~4を繰り返す
#!/bin/sh # ローカルにアカウント情報をまとめたファイルを置いているのでそこから取得 # 別に直接書いても問題ない API_KEY=`cat ~/account.json | jq -r .hatena.api_key` HATENA_ID=`cat ~/account.json | jq -r .hatena.user_id` BLOG_ID=`cat ~/account.json | jq -r .hatena.blog_id` ENDPOINT_ROOT="https://blog.hatena.ne.jp/${HATENA_ID}/${BLOG_ID}/atom" ENDPOINT_ENTRY='/entry' # 全記事のエンドポイントを取得する function getEntryId() { page=`curl -su ${HATENA_ID}:${API_KEY} ${ENDPOINT_ROOT}${ENDPOINT_ENTRY}?page=$1` # 各記事のエンドポイントを取得 # 極稀にgrepでBinary file (standard input) matchesと表示され処理が止まるのを防ぐため-aをつける echo "$page" \ | grep -a 'link rel="edit"' \ | grep -oP 'href=".*"' \ | sed 's/href="//g' \ | tr -d '"' # 次のページがある場合、再帰して各記事のエンドポイントを出力する next=`echo "$page" | grep 'link rel="next"'` if [ $? -eq 0 ] ; then pageId=`echo "$next" | grep -oP "page=[0-9]*" | tr -d "page="` getEntryId $pageId fi }
結果
$ sh hatena.sh https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry/9801XXXXX55524080 https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry/9801XXXXX39215842 https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry/9801XXXXX39148152 https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry/1025XXXXX32700158645 https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry/1025XXXXX32690969669 https://blog.hatena.ne.jp/hatena_id/blog_id/atom/entry/1025XXXXX32688559263 ...(省略)
簡単ですね。grepのところはもう少しスマートになりそうだけどとりあえずこれで良しとしよう。
エントリーIDというか各記事へのエンドポイントを取得ですね。もしエントリーIDだけ欲しかったらここから更にgrep -oP
で抽出すればいいんじゃないかな。
画像も記事内容も全てローカルにバックアップする
もはや気軽でもなんでもないが、そもそも私がしたかったのは全記事のバックアップ。
シェルを叩いたら一撃で全ての記事の内容、画像をローカルにダウンロードしてくる、というのを実現したかった。
ということでここからはもう完全にオナニーだがせっかく作ったのでお披露目しておこう。
本スクリプトを実行すると、日付・記事タイトル・記事で使用されている画像が出力される。
hatena.sh
#!/bin/sh API_KEY=`cat ~/account.json | jq -r .hatena.api_key` HATENA_ID=`cat ~/account.json | jq -r .hatena.user_id` BLOG_ID=`cat ~/account.json | jq -r .hatena.blog_id` ENDPOINT_ROOT="https://blog.hatena.ne.jp/${HATENA_ID}/${BLOG_ID}/atom" ENDPOINT_ENTRY='/entry' # エンコードされた特殊文字(&,",',<,>)をデコードする function decodeSpecialChars() { sed 's/&/&/g' \ | sed 's/"/"/g' \ | sed "s/'/'/g" \ | sed 's/</</g' \ | sed 's/>/</g' } # 全記事のエンドポイントを取得する function getEntryId() { page=`curl -su ${HATENA_ID}:${API_KEY} ${ENDPOINT_ROOT}${ENDPOINT_ENTRY}?page=$1` # entry_idの取得 # たまにBinary file (standard input) matchesと表示され処理が止まるのを防ぐため-aをつける echo "$page" \ | grep -a 'link rel="edit"' \ | grep -oP 'href=".*"' \ | sed 's/href="//g' \ | tr -d '"' # 次のページがある場合、再帰してエントリーIDを出力する next=`echo "$page" | grep 'link rel="next"'` if [ $? -eq 0 ] ; then pageId=`echo "$next" | grep -oP "page=[0-9]*" | tr -d "page="` getEntryId $pageId fi } # contentタグの中身だけ取得。特殊文字はデコードして出力 # 第一引数に対象の記事のエンドポイントをとる function getContent() { # contentタグの始めと終わりの行番号を取得するためのラベル START_CONTENT_LABEL='<content type="text\/x-markdown">' END_CONTENT_LABEL='<\/content>' endPoint=$1 article=`curl -su ${HATENA_ID}:${API_KEY} ${endPoint}` # コンテンツ内容を投稿日時毎のディレクトリに保存し、ファイル名をタイトルにするため postDate=`echo "$article" | grep 'link rel="alternate"' | grep -oP "[0-9]{4}/[0-9]{2}/[0-9]{2}"` title=`echo "$article" | grep -oP "(?<=\<title\>).*(?=\<\/title\>)"` # 画像ファイルを取得するため blogUrl=`echo "$article" | grep 'link rel="alternate"' | grep -oP '(?<=href=").*(?=")'` blog=`curl -s ${blogUrl}` imgUrls=`echo "$blog" | grep -oP '<img src.*itemprop="image"' | grep -oP '(?<=src=").*(?=" alt)'` printf "\e[92m\e[1m$postDate\e[m\n" echo $title # 記事毎に内容と画像を保存したいので、投稿日時ごとのディレクトリを作成 mkdir -p $postDate # 画像をダウンロードし、投稿日時ディレクトリに保存 for imgUrl in `echo "$imgUrls"`; do imgName=`echo $imgUrl | grep -oP "[0-9]{12}.*"` echo "$imgName" echo "${postDate}/${imgName} $imgUrl" wget -q -O ${postDate}/${imgName} $imgUrl done # contentタグの中身のみ取得したいため、始めと終わりの行番号を取得 contentLineNo=` echo "$article" \ | grep -nE "(${START_CONTENT_LABEL}|${END_CONTENT_LABEL})" \ | sed 's/:.*//g' ` # 記事の内容を投稿日時ディレクトリにmd形式で保存 start=`echo "$contentLineNo" | head -n 1` end=`echo "$contentLineNo" | tail -n 1` content=`echo "$article" | awk "NR==${start},NR==${end}"` echo "$content" | decodeSpecialChars \ | sed "s/$START_CONTENT_LABEL//g" \ | sed "s/$END_CONTENT_LABEL//g" \ > $postDate/$title.md } # 全ての記事の内容を取得する function main() { for i in `getEntryId` do getContent $i done } main
このシェルを実行すると記事投稿日毎にディレクトリが作成され、記事ファイルとその記事で使用されている画像ファイルが作成される。 私はMarkdownで記事を書いているのでmd形式の記事ファイル。はてぶに上げた形式のままになるよう整形しているので仮に記事が消えてもそのままこのmdファイルをアップロードすれば復元できる。
# シェル叩いた後 $ ls 2018 2019 hatena.sh # ディレクトリ構成 $ tree 2019 . \`-- 01 |-- 07 | \`-- 【Swift4】UIImageでURLで画像を指定する.md |-- 26 | \`-- apacheのDOCUMENT_ROOTを知る方法.md \`-- 27 |-- 20190126015937.png |-- 20190126020540.png \`-- laravel+apacheでTesting 123...と出てしまう問題の解決法.md
記事もそのまま
$ cat 01/27/laravel+apacheでTesting\ 123...と出てしまう問題の解決法.md [f:id:rasukarusan:20190126020540p:plain] サーバーにlaravelで作ったアプリを設置するときに若干詰まった。 結局シンプルな変更漏れっていうオチなんですけどね。 ## 解決1 DOCUMENT_ROOTを設定するときにhttpd.confの設定で変更漏れがあった。 \```zsh DocumentRoot /var/www/html/laravel-app/public ServerName example.com #<Directory "/var/www/html"< <Directory "/var/www/html/laravel-app/public"< # ←こっちも変更する ... \``` ...(省略)
まとめ
APIを試す時は大体shellのcurlでサクッとやってしまうのだが他の人はどうなんだろうか。
今回はてぶAPIを試す際色んな記事を見たがrubyやpythonでやってる人が多かった。
私みたいに「APIを試す時はcurlで!」という方の助けになればと思い書いてみたが、最後は気軽じゃないスクリプト載っけてしまった。絶対他の人読めない気がする。(人の書いたShellって読みづらいよね)
まあでもやりたいことは出来たので満足。