NewsPicksのオリジナル記事用RSSを作った

📅 April 07, 2019

⏱️4 min read

モチベーション

  • NewsPicksというメディアのオリジナル特集や記事がとてもわかり易くて面白いので、よく読んでいる
  • 情報収集に一人SlackのRSSをすごくよく使う
  • NewsPicksのtwitterアカウントでニュースをツイートしてくれているので、twitterをRSSのように利用することも可能だが、オリジナル記事がどれだか分かりづらい

そこでオリジナル記事のみを配信してくれるRSS feedを作成してみました。他のサイトにも応用できそうです。

ソースコードはこちらです。

流れ

  • リポジトリをクローンして、必要なモジュールをインストールします
$ git clone https://github.com/YukiOta/newspicks-rss
$ cd newspicks-rss
$ npm install
  • アプリケーションを起動
$ npm run start

コードの中身

  • ざっくりと書いたので読み辛いのはお許しを...
  • 主要なファイルはindex.jsroutes/feed.jsの2つのみです

index.js

routes/feed.jsを読み込んで、アプリケーション・サーバーを起動します

// index.js
const express = require("express");
const app = express();
const host = process.env.HOST || "127.0.0.1";
const port = process.env.PORT || 3000;

app.set("port", port);
app.set("last_update_at", 0);

app.use("/feed", require("./routes/feed.js"));

app.use((err, req, res, next) => {
  console.log("[SERVER ERROR]", err.stack);
  const error_message = err.message || err;
  res.status(500).send({
    error: error_message
  });
});

app.listen(port);
console.log("Server listening on http://" + host + ":" + port);

module.exports = app;

routes/feed/js

使っている主なモジュールは以下のとおりです

  • express
  • moment
  • request
  • cheerio
  • feed

流れとしては、

  • /np/originalへのリクエストを受ける
  • https://newspicks.com/series (オリジナル記事のURL)からhtmlを取得
  • cheerioでパースして解析
  • feed.addItemでオリジナル記事追加していく
  • "Content-Type"に "application/xml"を指定して、RSS2.0形式で返す (feed.rss2())

となります。

const Router = require("express").Router();
const moment = require("moment");
const request = require("request");
const cheerio = require("cheerio");
const Feed = require("feed").Feed;

const feed = new Feed({
  title: "NewsPicks original articles feed",
  description: "This is my personal feed!",
  id: "https://newspicks.com",
  link: "https://newspicks.com",
  language: "ja",
  copyright: "All rights reserved 2019, yukioh",
  generator: "For personal use",
  author: {
    name: "Yuki Ota"
  }
});

Router.get("/np/original", (req, res, next) => {
  const slug = "series"
  const options = {
    uri: `https://newspicks.com/${slug}`,
    method: "GET",
    qs: {
      from: Number(
        moment()
          .utc()
          .format("YYYYMMDDHHmmssSSS")
      ),
      limit: 10,
      _: Number(moment().format("x"))
    }
  };

  request(options, (error, response, body) => {
    if (error) {
      console.log("error: ", error);
      next();
    }

    try {
      const $ = cheerio.load(body);
      const latest_article_data = $(".news-card").data();
      const latest_updated_ts_at = moment(
        String(latest_article_data.key),
        "YYYYMMDDHHmmssSSS"
      ).format("X");
      const last_updated_ts_at = module.parent.exports.get("last_update_at");

      $(".news-card").each((i, elem) => {
        const id = $(elem).data().id;
        const key = $(elem).data().key;
        const update_at = moment(String(key), "YYYYMMDDHHmmssSSS").format("X");
        if (update_at > last_updated_ts_at) {
          const title = $(elem)
            .children("a")
            .children(".title")
            .text();
          const content = $(elem)
            .children(".user-comment")
            .text();

          const article = {
            title: title,
            id: `https://newspicks.com/news/${id}`,
            link: `https://newspicks.com/news/${id}`,
            description: content,
            content: content,
            date: moment(String(key), "YYYYMMDDHHmmssSSS").toDate(),
            image: `https://contents.newspicks.com/images/news/${id}`
          };

          feed.addItem({
            title: article.title,
            id: article.id,
            link: article.link,
            description: article.description,
            content: article.content,
            date: article.date,
            image: article.image
          });
        }
      });
      module.parent.exports.set("last_update_at", latest_updated_ts_at);
      res.set({
        "Content-Type": "application/xml"
      });
      res.status(200).send(feed.rss2());
    } catch (error) {
      console.log("error: ", error);
      next();
    }
  });
});

module.exports = Router;

このアプリケーションを、Herokuなりでデプロイすれば、オリジナルのRSSフィードを配信してくれるサーバーができあがります。

これを応用すれば他のサイトのRSSも作れそう。

オリジナル記事のページと、他のテーマ別のページ(テクノロジーとか)の仕様が若干違っていたことにあとから気づいたので、オリジナル記事のみの対応になってます。。。

以上簡単な説明でした。一人Slackで情報収集するのかなりおすすめです!