はじめに
みなさん、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のあれのオマージュです。
# 可視化部分だけ記事にした方が映えそう
ソースコード
使ったもの
- Node.js v10
- chromeless
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します。
ついでに列ヘッダに曜日列を追加しています。
おわりに
可視化部分は投稿有・無だけで判別していますが、本当は例のあれと同様に濃さで表現したかったです。
例のあれのアスキーアートどこかに転がってないですかね?