Skip to content
Yuki on GitHubYuki on Twitter

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

モチベーション

  • 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 で情報収集するのかなりおすすめです!

I'd be happy to have ☕!
このエントリーをはてなブックマークに追加