どうもこたにんです。
久々に技術記事!
今回は、はてなブログAtomPubというAPIをGASで呼び出してレスポンスをファイル保存、ということをやってみます。
はい、目次どんっ!
今回やること
とある記事を書く際に、ブログに投稿した全ての記事の内容が欲しくなりました。
そのために、全ての記事情報をはてなブログAtomPubから取得する。
また、サーバ立てたりしたくないのでGASでサクッと実装する。
最終的に欲しいのはテキストファイルだったのでGASでテキストファイル生成まで行う。
はてなブログAtomPubとは?
はてなブログAtomPub - Hatena Developer Center
Atom Publishing Protocol(以下 AtomPub) はウェブリソースを公開、編集するためのアプリケーション・プロトコル仕様です。はてなブログのAtomPubと通じて、開発者ははてなブログのエントリを参照、投稿、編集、削除するようなオリジナルのアプリケーションを作成できます。
AtomPubプロトコルに準じたはてなブログ専用のXML仕様のこと、AtomAPI。
自身のはてなブログの情報を取得したり投稿したりなどができるものです。
はてなブログAtomPubでは以下の操作が可能とのことです。
- サービスの操作 (サービス文書)
- コレクション一覧の取得 (サービス文書URI の GET)
- カテゴリの操作 (カテゴリ文書)
- カテゴリ一覧の取得 (カテゴリ文書URI の GET)
今回の記事では、やることを「ブログエントリの取得」に限定していきます。
ので、必要最低限の方法の展開になります。
はてなブログAtomPubの全容が知りたい方はリファレンスを読みましょう。
GASではてなブログAtomPubから情報を取得する
AtomPubに必要な情報の確認
はてなブログ側の設定画面にて、AtomPubの接続に必要な情報を確認します。
[設定] → [詳細設定] → [AtomPub] の項目を確認しましょう。
ここにあるルートエンドポイント、APIキーが必要となります。
後で使いますので控えておきましょう。
GASでAtomPubの呼び出し
続いてGAS側で、はてなブログAtomPubを呼び出すコードを書きましょう。
コードは至ってシンプル。
// ルートエンドポイント
const url = 'https://blog.hatena.ne.jp/Kotanin0/kotanin0.hatenablog.com/atom/entry';
// はてなのユーザID、APIキー
const user = 'Kotanin0';
const apiKey = 'xxxxxxxxxx';
// リクエストヘッダの準備
const options = {
'method' : 'GET',
'headers' : {'Authorization' : 'Basic ' + Utilities.base64Encode(user + ':' + apiKey)}
}
// xml形式で返却される
var xml = UrlFetchApp.fetch(url, options).getContentText();
UrlFetchAppでHTTP通信します。
url引数に、AtomPubのルートエンドポイントを指定。
ただし今回は記事一覧を取得したいので、末尾に /entry を追加しています。
options引数に、リクエストヘッダを指定。
リクエストヘッダには、はてなIDとAPIキーをエンコードした値をセットします。
認証形式についての説明は省略しますが、今回はBasic認証で行います。
自分のブログの情報が欲しいだけなのでオレオレ認証してしまうわけです。
何かしら一般化して作るときはOAuthなどで実装しなきゃですね。
もとい。
これで、はてなブログAtomPubから情報を取得できました。
XMLパースして必要な部分のみファイル出力する
ParserライブラリでXMLをパースする
先のUrlFetchApp.fetchによって取得されたXMLをパースして必要な情報を切り出します。
記事一覧のXMLは以下のような階層を持っています。
<feed>
:
<entry>
<id></id>
<link rel="edit"/>
<link rel="alternate"/>
<author><name></name></author>
<title></title>
<updated></updated>
<published></published>
<app:edited></app:edited>
<summary type="text"></summary>
<content type="text/html"></content>
<hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"></hatena:formatted-content>
<category />
<app:control>
<app:draft></app:draft>
</app:control>
</entry>
<entry>
:
<entry> というタグごとに、記事一覧が入っているかたちです。
XMLパーサはGAS標準で用意されているものがあります。
XML Service | Apps Script | Google Developers
ただ、階層潜るとかいちいち考えたくないなあって思ってしまいました。
ので、GASでHTMLパースできるライブラリParserを使ってパースします。
Parserのこまかい使い方はこちらの記事を参考ください。
ParserはHTMLのためのライブラリですが、中身は実際はただの文字列抽出なのです。
なのでXML形式でも同様に使うことができます、のでさくっとメソッドを書きます。
記事本文は <entry> タグ内の <content> に入っているので抽出しましょう。
// Parserでentryからcontentのリストを抽出し返却する function getContents(xml) { var entries = Parser.data(xml).from('<entry>').to('</entry>').iterate(); var contentList = []; for each(var entry in entries) { var content = Parser.data(entry).from('<content').to('</content>').build(); contentList.push(content); } return contentList; }
Google Drive上にファイル保存する
抽出された記事一覧の文字列配列を、ファイル保存したいです。
自身のGoogle Driveにファイルを保存してしまいましょう。
Google Driveにアクセスし、フォルダを作ります。
URLは以下のようになっているかと思います。
https://drive.google.com/drive/u/0/folders/{IDっぽい文字列}
このIDっぽい文字列部分を控えておきます。
あとは、Google Drive上にファイル出力するGASコードを書きましょう。
function createFile(fileName, content) {
var folder = DriveApp.getFolderById('IDっぽい文字列');
var file = Utilities.newBlob('', 'text/plain', fileName).setDataFromString(content, 'utf-8');
folder.createFile(file);
}
DriveApp.getFolderByIdの引数に、先ほどのIDっぽい文字列を指定します。
Utilities.newBlobで、保存したいデータを文字コード指定で準備します。
そして、フォルダにファイルをcreateFile。
これでGoogle Driveにテキストファイルが保存されます。
目的達成!おわり!!
まとめ
今回、これだけのことがさくっとできるようになりました
- はてなブログAtomPubでブログエントリの情報を取得
- GASでHTTPリクエストするときはUrlFetchApp
- GASでGoogle Driveにファイル保存するときはUtilities.newBlob
GASメインでHTTP通信からパース、ファイル保存までできる引き出しが手に入った!
これでスクレイピングから先の幅が広がるね!!
ちなみに、本ブログの全記事のwc -mは約189万文字ありました、壮大。