2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.jsでWordPressのRSSを(なるべく)リアルタイムで持ってくるお話

Posted at

プロ生ちゃん Advent Calendar 2018 21日目の記事、担当は音色ねいろです。

本来はもっとプロ生ちゃん色の強い何かを作ろうとしましたが、どうもこうもアイデアも出ず記事を書く時間も見つからずで、結果的にこの記事になりました。来年のネタを早めに温めておきたい。
今回の記事では、Node.jsを使用してWordPressのFeedをなるべくリアルタイムに持ってくる内容となっています。申し訳程度にプログラミング生放送のフィードURI (https://pronama.jp/feed)を例に取っています。

きっかけ

要件(今回やること)

  • Node.jsで(なるべくリアルタイムに)更新を検知する
    • この際相手のサーバに過大な負荷をかけることは許されないため、最小でも1分間隔で取る
  • モジュール化する
    • 依存しないように取得部分と表示部分とをEventEmitterで分ける。

今回使用したライブラリ

  • data-utils
  • iconv-lite
  • xml2js
  • sync-request

実装

wpFeed.js
const EventEmitter = require('events').EventEmitter /* 1 */

require('date-utils')
const iconv = require('iconv-lite')
const xml2js = require('xml2js')
const request = require('sync-request')

let tempPostID = 0
let startupTime = '0'

module.exports = class wpFeed extends EventEmitter { /* 1 */

  constructor() {
    super()

    startupTime = new Date() /* 2 */
  }

  start() {
    const self = this

    setInterval(() => self._wordpress(), 60 * 1000) /* 3 */
  }

  _wordpress() {
    const res = request(
      'GET',
      'http://pronama.jp/feed'
    ) /* 4 */
    const buf = Buffer.from(res.getBody())
    const feedXml = iconv.decode(buf, 'UTF8') 

    xml2js.parseString(feedXml, (err, result) => { /* 5 */
      if(err !== null) {
        console.error(err)
      }

      const postData = result.rss.channel[0].item[0]
      const postID = postData['post-id'][0]._
      const pubDate = postData.pubDate
      const pubDateObject = new Date(pubDate)

      if(pubDateObject.getTime() < startupTime.getTime()) return /* 2 */
      if(postID === this.tempPostID) return /* 6 */

      this.emit('post', postData) /* 7 */
      this.tempPostID = postID /* 6 */
      
    })
  }
}
app.js
const wpFeed = require('./wpFeed')
const wf = new wpFeed()

wf.on('start', () => {})
wf.on('post', data => {
  console.log(`記事が更新されました!\n「${data.title[0]}」`)
})

wf.start()

※実際に使用する際は、各処理を別ファイルに分離することをおすすめします。

1 : EventEmitterを継承する

今回の記事のキモです。
イベントモジュールのEventEmitterオブジェクトを継承しています。

2 : 取得開始前の記事をスキップする

これをしないと取得開始する度に最新の記事が取得されてしまい非常に厄介です。
そのため、コンストラクタ時にstartupTimeを格納し、記事取得時の時間と比較することによって取得開始後の記事のみをエミットすることができます。

3 : setIntervalで繰り返す

下に定義したwpFeedを120秒間隔で実行します。
もっとまともな書き方があれば教えていただきたいです…!

4 : フィード情報を取得してくる

現在sync-requestが非推奨になっていますが、同期的に処理したいためこれを利用しています。
まともな書き方があれば(以下略)

5 : XMLからJSONにコンバートする

XMLだと非常に扱いにくいので、JavaScriptのために生まれてきたJSONにコンバートします。
XMLに誤りがあると変換がうまくいかない場合があるので、必ずエラー処理を噛ませておきます。

6 : 重複スキップ

直前に取得した記事IDと現在処理中の記事IDとを比較します。
比較した際、直前のIDと一致した場合は早期リターンを用います。
ただ、内容が変更された場合は感知できないようなので比較対象を内容にするべきですね。

7 : エミット

更新された記事をエミットします。
こうすると、app.js側のwpFeed.on('post', postData => {})にデータが飛び、自由自在にデータを利用することができます。

まとめ

去年の今頃からNode.js書き始めましたが、「こいつ動くんか?」といったコードも(それなりに綺麗なコードで)すんなり動いてくれてとても好きです。

今回、EventEmitterを利用しましたが、本当に様々なことに使えるので大好きです。(ep8もデータの取得にEventEmitterを使用しています)

今後やりたいこと

  • 更新されたらLEDがチカチカするIoTしたい
  • クラス生成時にURL指定、更新間隔を変更できるようにしたい
  • npmで配布したい
  • TypeScriptで書いてみたい

最後に

本当に昨日(20日)まで何を書こうか全く決まらなくて、結果今年学習した内容の記事になりました。
来年こそはもっとプロ生ちゃんにコミットしたいです……!
ここまでご覧頂きまして、本当にありがとうございました。

プロ生ちゃん Advent Calendar 2018 22日担当は120byteさんです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?