Posted at

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


はじめに

みなさん、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のあれのオマージュです。

# 可視化部分だけ記事にした方が映えそう


ソースコード

https://github.com/a-terarara/fetch-knowledge-list-amonth


使ったもの

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します。

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


おわりに

可視化部分は投稿有・無だけで判別していますが、本当は例のあれと同様に濃さで表現したかったです。

例のあれのアスキーアートどこかに転がってないですかね?