cangoxina

生産性向上見習いのブログ的な何かです

はてなブログのAPIを叩いて最新記事一覧を取得する[Javascript]

f:id:korosuke613:20190627195506p:plain

はてなブログの記事一覧を取得して、自分のポートフォリオに最新記事を貼り付けたいってことありませんか?

僕はありました。

今回は、JavascriptはてなブログAPIを叩いて最新記事一覧を取得する方法を記します。

また、今回、コチラの記事を参考にしました。

cartman0.hatenablog.com


目次


はてなブログAtomPub

はてなブログにははてなブログAtomPubというAPIが用意されています(以下はてなブログAPIと呼称する)。

ブログエントリ一覧の取得や、ブログエントリの取得、更新、削除などをAPIを使って行えます。

今回はこれを利用します。

developer.hatena.ne.jp

やりかた

やりかたの流れと、実際のフルのコードを記します。

node --version
v8.15.0

で動作を確認しています。

流れ

f:id:korosuke613:20190627202517p:plain
だいたいこんな感じです。

詳細

はてなブログAPIを呼び出す

はてなブログAPIは10件の最新記事情報をXMLで返します。 その先の記事情報を取得するためには、レスポンスに含まれる別のURIを使います。

JSONではなくてXMLです...

APIを叩くのに、最近よく名前を聞くaxiosというライブラリを使いました。

axios.get()でGETメソッドを叩けます。記事一覧を取得するURIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entryになります。

また、OAuth 認証、WSSE認証、Basic認証のいずれかで認証できるのですが、今回は、ユーザ本人が使う前提なので、楽々なBASIC認証を使います。

実際のコードがコチラです。

const res = await axios.get(url, {
  auth: { username: process.env.HATENA_NAME, 
          password: process.env.HATENA_PASS }
});

usernameはてなIDpasswordブログの詳細設定に記載されたAPIキーを記述します。(今回は、環境変数を使っています。)

XMLをオブジェクトに変換し、記事一覧と次のURIを取得する。

前述したとおり、はてなブログAPIXMLを返すため、扱いやすくするためにオブジェクトに変換します。

また、オブジェクトから記事一覧次の記事一覧を得るためのURIを取得します。

オブジェクトに変換するのに、xml2jsというライブラリを使いました。 実際のコードがコチラです。

xml2js.parseString(data.toString(), (err, result) => {
  if (err) {
    reject(err);
  } else {
    const entry = result.feed.entry;
    const next_url = result.feed.link[1].$.href;
    esolve({ entry, next_url });
  }
});

entryが記事の配列です。 next_urlが次の記事一覧を得るためのURIです。

下書きの記事を除外し、記事を配列に格納する

先ほど、

はてなブログAPIは10件の最新記事情報をXMLで返します。

と言いましたが、その10件の最新記事には、下書き(draft)も含んでいます。

なので、下書き記事を除外します

下書きかどうかはapp:draftというタグがyesnoかで判断します。

以下が実際のコードです。

// 下書きを除く記事を配列に追加する。
const insertItems = (entry, item_list) => {
  for (let e of entry) {
    if (e["app:control"][0]["app:draft"][0] == "yes") {
      // 下書き記事をスキップする。
      continue;
    }

    // はてな記事のJSONを生成。
    const item = {
      day: moment(e.published.toString()).format("YYYY-MM-DD"),
      title: e.title.toString(),
      href: e.link[1].$.href
    }
    item_list.push(item);
  }
};

実際のコードでは、app:draftnoであった場合、すなわち、下書き記事でない場合に、その記事情報をJSONにして、配列に格納するという処理をしています。

今回は、

  • day 日付
  • title 記事タイトル
  • href 記事URL

という単純な構造にしました。

ちなみに、日付をYYYY-MM-DDにするために、momentというライブラリを使用しています。

固定数の記事が得られるまで繰り返す

ここまでで、下書きを除く記事情報がいくつか取得できましたが、最大でも10件までしか記事情報を取得できていません。

それ以前の記事情報を取得したい場合、次の記事一覧を得るためのURIに対してさらにGETメソッドを叩く必要があります。

次の記事一覧を得るためのURIは先ほど取得したnext_urlを使います。

実際のコードはこちらです。

// 記事一覧を取得するURIは”https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry”
let url =
  "https://blog.hatena.ne.jp/korosuke613/korosuke613.hatenablog.com/atom/entry";

// 記事を格納する配列
let item_list = [];

// 下書き以外の記事が10件溜まるまで記事一覧を取得する
while (item_list.length < 10) {
  const xml_data = await getHatenaData(url); // APIを叩いて記事一覧のXMLを取得する。
  const { entry, next_url } = await extractItemsAndNextUri(xml_data); // 記事一覧と次の記事一覧のURIを抽出する。
  insertItems(entry, item_list); // item_listに下書き以外の記事一覧を格納する。

  url = next_url;
}

JSON形式でファイルに保存

最後に、記事情報の配列をJSON形式で保存します。

配列からJSONに変換するのに、JSON.stringify()というメソッドを使っています。

// 記事一覧をJSON形式で保存
fs.writeFileSync(
  "./hatena.json”,
  JSON.stringify(item_list)
);

実際に生成されるJSONがこちらです。(今回は3件のみ、整形済み)

[
  {
    "day": "2019-06-24",
    "title": "爆速で日本語LaTeX執筆環境を用意する",
    "href": "https://korosuke613.hatenablog.com/entry/2019/06/24/171246"
  },
  {
    "day": "2019-02-23",
    "title": "農業ハッカソンに参加しました",
    "href": "https://korosuke613.hatenablog.com/entry/2019/02/23/234135"
  },
  {
    "day": "2019-02-09",
    "title": "Keynoteの「フォントの置換」ができない",
    "href": "https://korosuke613.hatenablog.com/entry/2019/02/09/012652"
  }
]

コード

フルのコードがコチラです。

"use strict";

const axios = require("axios");
const xml2js = require("xml2js");
const moment = require("moment");
const fs = require("fs");

// APIを叩いてはてなブログのXMLを取得する。
const getHatenaData = async url => {
  try {
    const res = await axios.get(url, {
      auth: { username: process.env.HATENA_NAME, password: process.env.HATENA_PASS }
    });
    return res.data;
  } catch (err) {
    const { status, statusText } = err.response;
    console.log(`Error! HTTP Status: ${status} ${statusText}`);
  }
};

// XMLから記事一覧と次の記事一覧のURIを抽出する。
const extractItemsAndNextUri = async data => {
  return new Promise((resolve, reject) => {
    xml2js.parseString(data.toString(), (err, result) => {
      if (err) {
        reject(err);
      } else {
        const entry = result.feed.entry;
        const next_url = result.feed.link[1].$.href;
        resolve({ entry, next_url });
      }
    });
  });
};

// 下書きを除く記事を配列に追加する。
const insertItems = (entry, item_list) => {
  for (let e of entry) {
    if (e["app:control"][0]["app:draft"][0] == "yes") {
      // 下書き記事をスキップする。
      continue;
    }

    // はてな記事のJSONを生成。
    const item = {
      day: moment(e.published.toString()).format("YYYY-MM-DD"),
      title: e.title.toString(),
      href: e.link[1].$.href
    }
    item_list.push(item);
  }
};

const main = async () => {
  // 記事一覧を取得するURIは”https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry”
  let url =
    "https://blog.hatena.ne.jp/korosuke613/korosuke613.hatenablog.com/atom/entry";

  // 記事を格納する配列
  let item_list = [];

  // 下書き以外の記事が10件溜まるまで記事一覧を取得する
  while (item_list.length < 10) {
    const xml_data = await getHatenaData(url); // APIを叩いて記事一覧のXMLを取得する。
    const { entry, next_url } = await extractItemsAndNextUri(xml_data); // 記事一覧と次の記事一覧のURIを抽出する。
    insertItems(entry, item_list); // item_listに下書き以外の記事一覧を格納する。

    url = next_url;
  }

  // 記事一覧をJSON形式で保存
  fs.writeFileSync(
    "./hatena.json",
    JSON.stringify(item_list)
  );
};

main();

実際にホームページに組み込んでみた

実際に、ホームページに組み込みました。トップ画面にはてなブログの最新記事5件が表示されるようになっています。

korosuke613.github.io

f:id:korosuke613:20190627222748p:plain

実際に使っているソースコードコチラです。

僕は、自分のホームページのホスティングgithub pagesを利用しています。

ページが閲覧されるたびにAPIを叩くわけにはいきません。 なのでCircle CIを使って、毎日決まった時間にスクリプトを実行し、生成したJSONファイルをページ閲覧時に読み込むようにしました。

github pagesを使う以上、APIキーをGitHubにあげなくてはいけません。もちろんそんなことはできないのですが、Circle CIで実行することによって解決できました。

おわりに

今回は、はてなブログの最新記事一覧をJavascriptを使って取得する方法を記しました。

はてなブログを書いてる人には使い道があるのではないでしょうか?

また、Javascript、Nodeの扱いには慣れてなく、今回紹介したコードは突っ込みどころ満載かと思います。 ここはこうした方が良いよ!というアドバイスがあったら、ぜひ、コメント欄で指摘していただけると幸いです。



スポンサーリンク