$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

puppeteerを使って最近の自分のブログのサマリーを出す

評価時期で自分のアウトプットの様子を出す必要があり、この機会にTypeScript + puppeteerでシュッとスクレイピングするの試してみるかと思い、やってみた。

完成したもの

https://github.com/shibayu36/tools/blob/a7156bc03620700105cc52e54d69cac38b13f40e/script/blog-ranking.ts

スクリプト内でurls, start, endを定義しておくと、特定期間の総記事数や総ブックマーク数、記事の情報を出してくれるようになった。

使い方は

git clone https://github.com/shibayu36/tools.git
cd tools
npm install
npx ts-node script/blog-ranking.ts

コード

/* eslint-disable @typescript-eslint/no-non-null-assertion */
import axios from "axios";
import { parse, parseISO, isBefore } from "date-fns";
import { sumBy, sortBy } from "lodash";
import puppeteer from "puppeteer";

const start = parseISO("2020-07-01T00:00:00+09:00");
const end = parseISO("2021-01-01T00:00:00+09:00");
const urls = [
  "https://blog.shibayu36.org/archive/category/tech",
  "https://blog.shibayu36.org/archive/category/tech?page=2",
];

async function main(): Promise<void> {
  const browser = await puppeteer.launch();
  const articlesList = await Promise.all(urls.map((u) => fetchArticles(browser, u)));
  const articles = articlesList.flat().filter((a) => isBefore(start, a.date) && isBefore(a.date, end));

  console.log("総記事数:", articles.length);
  console.log("総ブックマーク数:" + sumBy(articles, "bookmark"));
  for (const a of sortBy(articles, [(a) => -a.bookmark])) {
    console.log(`[${a.title} ${a.url}]`, a.bookmark);
  }

  await browser.close();
}

type ArticleWithBookmark = {
  title: string;
  url: string;
  date: Date;
  bookmark: number;
};
async function fetchArticles(browser: puppeteer.Browser, url: string): Promise<readonly ArticleWithBookmark[]> {
  const page = await browser.newPage();
  await page.goto(url);
  const articles = await page.evaluate(() => {
    const articleElements = Array.from(document.querySelectorAll(".archive-entry"));
    return articleElements.map((elem) => {
      const link = elem.querySelector(".entry-title-link")!;
      const title = link.textContent!;
      const url = link.getAttribute("href")!;
      const date = elem.querySelector(".archive-date time")!.getAttribute("datetime")!;
      return {
        title,
        url,
        date,
      };
    });
  });
  page.close();

  const queries = new URLSearchParams();
  for (const article of articles) {
    queries.append("url", article.url);
  }
  const res = await axios.get<{ [url: string]: number }>(
    `https://bookmark.hatenaapis.com/count/entries?${queries.toString()}`
  );

  return articles.map((a) => ({
    title: a.title,
    url: a.url,
    date: parse(a.date, "yyyy-MM-dd", new Date()),
    bookmark: res.data[a.url] ?? 0,
  }));
}

main().catch((error) => console.error(error));

最近のブログの様子を出した

2020/07~2020/12の様子を出した。総記事数35、総ブクマ数4184と結構頑張れたと思う。「締め切りが厳しいプロジェクトで、プロジェクト初期にまずやっておきたいこと」という記事はこれまでやってきたプロジェクトマネジメントの経験を言語化できて良かった。

blog.shibayu36.org

総記事数: 35
総ブックマーク数:4184
[締め切りが厳しいプロジェクトで、プロジェクト初期にまずやっておきたいこと https://blog.shibayu36.org/entry/2020/07/27/181500] 1077
[メンターを初めて経験する人に、最初に読むものとしてオススメしている書籍たち https://blog.shibayu36.org/entry/2020/07/10/183000] 872
[部下の困りごとをマネジャーが解決しすぎない方が良い https://blog.shibayu36.org/entry/2020/11/20/180000] 471
[TypeScriptの型を手に馴染ませるためにやっていること https://blog.shibayu36.org/entry/2020/10/15/173000] 390
[現代のソフトウェア開発を学ぶために「正しいものを正しくつくる」を読んだ https://blog.shibayu36.org/entry/2020/08/17/181500] 354
[開発チームの責務を「エンジニアリング観点でのサービス継続リスクをコントロールしながら、開発速度を最大化する」としてみた話 https://blog.shibayu36.org/entry/2020/10/01/180000] 203
[今見ているファイル内をSearchしやすくするVSCode拡張を作りました https://blog.shibayu36.org/entry/2020/07/06/180000] 169
[PullRequestからチーム開発の生産性・健全性を測るCLIツールを書いてみた https://blog.shibayu36.org/entry/2020/08/24/173000] 141
[VSCodeのFindで今マッチしている場所にボーダーを引いて見やすくする https://blog.shibayu36.org/entry/2020/10/12/180000] 120
[開発チーム運営では問題発見・改善だけでなく、良かったことの共有も大事にする https://blog.shibayu36.org/entry/2020/07/28/183000] 65
[TypeScriptでCLIツール作りをするためのプロジェクトサンプルを作ってみた https://blog.shibayu36.org/entry/2020/08/05/183000] 47
[長い期間、継続的にブログを書き続けるための工夫 https://blog.shibayu36.org/entry/2020/07/15/183000] 40
[ALBで特定のpathのときだけCognito認証を通す構成をaws-cdkで作る https://blog.shibayu36.org/entry/2020/09/23/180000] 39
[GitHub Actionsでeslintを動かすだけでFiles changedにlint errorが表示されて便利 https://blog.shibayu36.org/entry/2020/11/09/173000] 36
[特定のファイルだけgit stashする https://blog.shibayu36.org/entry/2020/07/17/105146] 30
[VSCodeのExplorerでフォーカスしているファイルを、ActiveなEditor Groupの隣に開く拡張を作った https://blog.shibayu36.org/entry/2020/10/28/183000] 29
[workflow_dispatchを使うとGithub Actionsのデバッグも楽だった https://blog.shibayu36.org/entry/2020/07/22/114015] 24
[Jamboardを使ってオンラインでプラニングポーカーをする https://blog.shibayu36.org/entry/2020/09/02/170308] 17
[VSCodeの置換でEmacs風の挙動を再現する https://blog.shibayu36.org/entry/2020/10/02/180000] 16
[他人を動かすコミュニケーション手法を学ぶ - 「人を動かす」読んだ https://blog.shibayu36.org/entry/2020/12/08/180000] 13
[「デッドライン」読んだ https://blog.shibayu36.org/entry/2020/11/16/183000] 5
[PullRequestの統計情報をBigQueryに送り、変更のリードタイムを柔軟に可視化する https://blog.shibayu36.org/entry/2020/10/27/183000] 5
[開発効率化のために最近入れたツールたち(indent-rainbow / TabNine / Tree Style Tab / Clipy) https://blog.shibayu36.org/entry/2020/08/31/183000] 5
[「ピープルウェア 第3版」読んだ https://blog.shibayu36.org/entry/2020/11/13/180000] 3
[スプレッドシートで保育園の在庫管理をしようとして失敗したけど、claspによるGASの管理方法を学べた https://blog.shibayu36.org/entry/2020/09/08/183000] 3
[「プロジェクトマネジメントの基本」読んだ https://blog.shibayu36.org/entry/2020/11/17/183000] 2
[GraphQL APIを使って、特定のGitHub Teamが持つレポジトリ一覧を一発で取得する https://blog.shibayu36.org/entry/2020/11/03/173000] 2
[「MBAより簡単で英語より大切な決算を読む習慣」読んだ https://blog.shibayu36.org/entry/2020/07/07/180000] 2
[VSCodeにAwesome Emacs Keymap入れた https://blog.shibayu36.org/entry/2020/11/02/183000] 1
[ReactのuseEffect/useLayoutEffectやレンダリングの実行順について調べた https://blog.shibayu36.org/entry/2020/09/24/180000] 1
[開発チームのパフォーマンスを測る指標を学ぶ - 「LeanとDevOpsの科学」読んだ https://blog.shibayu36.org/entry/2020/09/07/183000] 1
[スクラムガイド(2017)読んだ https://blog.shibayu36.org/entry/2020/07/22/183000] 1
[「リモートワークの達人」読んだ https://blog.shibayu36.org/entry/2020/12/01/183000] 0
[TypeScriptプロジェクトのテストとlintをGitHub Actionsで実行する https://blog.shibayu36.org/entry/2020/08/25/180000] 0
[SCRUM BOOT CAMP THE BOOK【増補改訂版】を読んだ https://blog.shibayu36.org/entry/2020/07/29/183000] 0