LoginSignup
16
6

More than 3 years have passed since last update.

knowledgeの前月リストを取得し、投稿日を可視化する

Posted at

はじめに

みなさん、knowledgeはご存知ですか?
Qiitaのような情報共有サイトを簡易的に構築できるオープンソースです。
knowledge/jp

弊社も社内で技術情報を共有し、スキルアップを図るために利用しています。

そんなknowledgeさんですが、単体ではレポーティング機能はない(はず)です。
DBにH2databaseまたはPostgreSQLを利用できるようなのでSQLで直接データは取れるのですが、私にそんな権限はないです。
WebAPIもないです。

そうだ。スクレイピングしよう

成果物

8月のレポート
◇◆-----------------------------◆◇
#100 knowledgeの前月リストを取得し、投稿日を可視化する ayumu_terahara
https://xxxxxxxx.co.jp/knowledge/open.knowledge/view/100
◇◆-----------------------------◆◇
contributions in the last month
      □ □ □ ■
Mon   ■ □ □ □
      □ □ □ ■
Wed   □ □ ■ ■
    □ □ □ □ □
Fri □ □ □ □ □
    ■ □ ■ ■ □
◇◆-----------------------------◆◇

投稿内容の一覧と投稿が何日に行われたのかを可視化します(データはダミー)
ちなみに可視化部分はGithubのContributionsのあれのオマージュです。
# 可視化部分だけ記事にした方が映えそう :thinking:

ソースコード

使ったもの

chromelessは現在更新が止まっており、Puppeteerの利用が推奨されていますが、そんなに運用考えていないのでとりあえずchromelessで。

処理概要

前月リストの取得

const scraping = async () => {
  const chromeless = new Chromeless();

  const list = await chromeless
    .goto(`https://${process.env.DOMAIN}/knowledge/open.knowledge/list`)
    .type(process.env.KNOWLEDGEUSER, 'input[name="username"]')
    .type(process.env.KNOWLEDGEPASS, 'input[name="password"]')
    .click("#content_top > div > form > div:nth-child(4) > div > button")
    .press(13)
    .wait("#knowledgeList")
    .evaluate(() => {
      return [].map.call(
        document.querySelectorAll(
          "#knowledgeList > div.col-sm-12.col-md-8.knowledge_list > div"
        ),
        div => {
          const title = div.childNodes[1].childNodes[1];
          const writer =
            div.childNodes[1].childNodes[3].childNodes[3].innerText;
          const postDate = div.childNodes[1].childNodes[3].childNodes[4].data.substring(
            8,
            13
          );
          return {
            title: title.innerText,
            href: title.href.split("?")[0],
            writer,
            postDate
          };
        }
      );
    });

  chromeless.end();

  return list;
};

ログインしてからTOPページの投稿一覧を取得するだけの簡単なお仕事です。
TOPページに表示される投稿一覧は50だけ表示されています。
もし、ページ跨ぎでチェックしたい場合は50番目の投稿日をチェックしてから再帰判定してください。

前月リストの取得

const contributes = (list, beforeMonth) => {
  const targetList = list.filter(
    contribution => contribution.postDate.substring(0, 2) === beforeMonth
  );
  return targetList.map(l => `${l.title} ${l.writer}\n${l.href}`).join("\n");
};

投稿リストの整形です。
下記をリスト数分並べるだけの簡単なお仕事です。

#100 knowledgeの前月リストを取得し、投稿日を可視化する ayumu_terahara
https://xxxxxxxx.co.jp/knowledge/open.knowledge/view/100

投稿日の可視化

const contributionsGraphGenerator = (list, month) => {
  const beginDate = moment();
  beginDate.month(month - 1);
  beginDate.date(1);
  const lastDate = moment();
  lastDate.date(1);
  lastDate.month(month);
  lastDate.date(0);

  const beginDay = beginDate.weekday();

  const dates = Array(lastDate.date()).fill(" ");
  const contributionsPoint = dates.map((val, index) => {
    const mmdd = `${month}/${("0" + (index + 1)).slice(-2)}`;
    return list.find(l => l.postDate === mmdd) ? "" : "";
  });
  const emptyPoints = Array(beginDay).fill(" ");
  const flatFullContributionsPoints = emptyPoints.concat(contributionsPoint);

  const daysInWeek = 7;
  const matrixFullContributions = Array(
    flatFullContributionsPoints.length / daysInWeek
  )
    .fill()
    .map((val, index) =>
      flatFullContributionsPoints.slice(
        index * daysInWeek,
        index * daysInWeek + daysInWeek
      )
    );

  const transposeMatrix = a => a[0].map((_, c) => a.map(r => r[c]).join(" "));
  const t = transposeMatrix(matrixFullContributions).map((val, index) => {
    const empty = "    ";
    switch (index) {
      case 0:
        return empty + val;
      case 1:
        return "Mon " + val;
      case 2:
        return empty + val;
      case 3:
        return "Wed " + val;
      case 4:
        return empty + val;
      case 5:
        return "Fri " + val;
      case 6:
        return empty + val;
      default:
        return " ";
    }
  });

  const graph = `contributions in the last month\n\`\`\`
${t.join("\n")}\n\`\`\``;
  return graph;
};

長くなってしまったので切り出します。。。

  const beginDate = moment();
  beginDate.month(month - 1);
  beginDate.date(1);
  const lastDate = moment();
  lastDate.date(1);
  lastDate.month(month);
  lastDate.date(0);

  const beginDay = beginDate.weekday();

  const dates = Array(lastDate.date()).fill(" ");
  const contributionsPoint = dates.map((val, index) => {
    const mmdd = `${month}/${("0" + (index + 1)).slice(-2)}`;
    return list.find(l => l.postDate === mmdd) ? "" : "";
  });
  const emptyPoints = Array(beginDay).fill(" ");
  const flatFullContributionsPoints = emptyPoints.concat(contributionsPoint);

contributionsPoint: 月初日、月末日を取り、配列を決めます。
emptyPoints: 当該月にかからない部分は半角スペース埋めで、表示しないようにします。

  const daysInWeek = 7;
  const matrixFullContributions = Array(
    flatFullContributionsPoints.length / daysInWeek
  )
    .fill()
    .map((val, index) =>
      flatFullContributionsPoints.slice(
        index * daysInWeek,
        index * daysInWeek + daysInWeek
      )
    );

週の7日分ずつ切り出して二次元配列化します。
ただし、この時点では縦横が出力したい状態と逆になっています。

  const transposeMatrix = a => a[0].map((_, c) => a.map(r => r[c]).join(" "));
  const t = transposeMatrix(matrixFullContributions).map((val, index) => {
    const empty = "    ";
    switch (index) {
      case 0:
        return empty + val;
      case 1:
        return "Mon " + val;
      case 2:
        return empty + val;
      case 3:
        return "Wed " + val;
      case 4:
        return empty + val;
      case 5:
        return "Fri " + val;
      case 6:
        return empty + val;
      default:
        return " ";
    }
  });

縦横をtransposeします。
ついでに列ヘッダに曜日列を追加しています。

おわりに

可視化部分は投稿有・無だけで判別していますが、本当は例のあれと同様に濃さで表現したかったです。
例のあれのアスキーアートどこかに転がってないですかね? :eyes:

16
6
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
16
6