この記事は、はてなエンジニア Advent Calendar 2021 の 18 日目の記事です。 昨日は id:dekokun の bitcoinのfull nodeをAWSでなるべく安く運用してみる - でこてっくろぐ ねお でした。
full node の存在知らなかったし EBS SC1 を使うって発想もなかった (最近 gp3 以外選んでない) ので面白かった。Bitcoin 思ってたより善意に支えられてるんですね。
さて本題。
はてなスタッフの日記
はてなスタッフは、だいたい個人ブログを持っています。また、技術ブログと日記とを分けずに一つのブログに書いている人もそこそこ多くいます。僕も分けてないので年末は「今年買ったもの」を投稿していたりする。今年も書きます。
技術記事を Hatena Developer Blog (会社のテックブログ) に書くこともありますが、個人ブログの集合体がコミュニティなんだよって気持ちから、個人ブログに書くのももちろん推奨されています。
月次のブログ紹介
毎週木曜日に技術勉強会を開催していますが、その中でも月初は特別編として、社外向けに書かれた技術記事をまとめて眺めています。
月初の勉強会は月に一回の特別編です。前月に書かれた技術エントリ(社内ではなく社外向けに書かれたもの)にブックマーク数100usersを超えるものがあったら寿司とビールでお祝いをします。
今はリモート開催なので寿司ビールはやれてないんですが、月次でのみんなの記事の共有は引き続き変わらず行っています。
入社時の儀式
これを実現するためには個人ブログの技術記事を集約する必要があり、そのデータとして全員のアウトプット先を YAML として持っています。
こんな感じ。
- owner: hatenatech url: https://developer.hatenastaff.com/ kind: hatenadeveloperblog weight: 2 - owner: onk url: https://onk.hatenablog.jp/ kind: hatenablog - owner: onk url: https://www.slideshare.net/takafumionaka/ kind: slideshare - owner: onk url: https://blog.onk.ninja/ kind: other
owner
, url
は見たまんま。 kind
はパーサ向けの情報。 weight
は面白機能で、Hatena Developer Blog に書いた場合は、特上寿司獲得ランキングにおいて 2 倍のスコアになります。
入社時に、自分のブログやスライド投稿先を足して PR を出す、というのが、エンジニア職で入社した人の儀式になっています。
投稿されたらワイワイしたい
月次で眺める会を実施しているのは前述の通りなんだけど、もっとワイワイしたいので、リアルタイムにも眺めたい。
リアルタイムにワイワイする、イコール Slack の #engineer に流す、をやりたい。
日記は車だったり釣りだったり育児だったり VTuber だったりと、まぁ本当に日記で、#engineer に流すにはちょっとノイジーなので、技術記事だけに絞り込む必要がある。
つまりこうです。
- ブログリストから
- 新着をポーリングして
- 技術記事かどうかを判定して
- Slack に流す
もうちょっと詳しく
ブログリストから
YAML をパースする
新着をポーリング
- ブログ URL から Feed URL を見つける
- 定期的に取得する
- 新着かどうかの判定
をやれば良い。
フィードはブログ URL で取得した HTML から、以下の XPath で探しています。
//link[@rel='alternate'][@type='application/atom+xml'] //link[@rel='alternate'][@type='application/rdf+xml'] //link[@rel='alternate'][@type='application/rss+xml']
ブログリストの kind
を使って決め打ちすることも可能そうです。
- はてなブログ
- speakerdeck
- /:username.atom
- 例: https://speakerdeck.com/hatena.atom
- scrapbox
- /api/feed/:projectname
新着判定は、記事 URL が一意なので、前回までに見た記事 URL に含まれているかどうかで判定可能。
技術記事かどうかを判定して
- 本文を抽出する
- 技術記事かどうかを判定する
をやる必要があります。
本文を抽出するのは、フィードに全文が入っていたらそれを使えます。サマリーしか入っていない場合は、フィードの URL から HTML を取得して、本文っぽいところを推測します。「それ Pla」とか「LDR Full Feed」というワードが頭をよぎりますね。
LDR Full Feed はサイトごとに XPath を用意していました。全サイトについて XPath を用意するのは少し大変なので、フォールバック先として「いいかんじに推測する」という実装も用意しておきたい。本文抽出は Webページの本文抽出 (nakatani @ cybozu labs) とか HTML::ExtractContent とかのようなライブラリが存在します。
技術記事かどうかを判定するのは、意外とヒューリスティックな方法でもそこそこの精度が出ます。ですが、自分にドメイン知識がある内容の 2 値分類機を作るのは難しくない (日々の生活の中でアノテーションできる) ので、ちゃんと育てていきたいですね。とまれ今は技術ワード 200 個ぐらいについて正規表現でマッチした個数で判定を行っています。
- HTML を除去して
- 正規表現でマッチ
Go
とかUI
とかが誤爆しまくるので単語境界を上手くやる必要がある- 単純に
\b
ではダメで、スペース区切り言語文字と非スペース区切り言語文字の境界も単語境界とみなす で対応する
Slack に流す
はい。
というのをやって 2 年が経ちました
体感としてはすごく良いし、このまま継続していきます。
技術記事判定ができているので、スタッフの個人ブログをまとめて公開することもできるようになったなぁと考えています。公開したい技術記事としたくない日記とが一箇所に書かれるので、単にブログ記事を集約するだけだとイマイチなんですよねー……。ブログのカテゴリ機能等を使うことで配信する/しないを選ぶことも可能ですが、できれば書き手に手間を掛けさせずに、全自動でやりたい。
お裾分け
ブログの URL を入れると技術記事のみの title, url が帰ってくる API を用意しました。どうぞご利用ください。裏側は API Gateway + Lambda なので、意図的に殺さない限り動き続けるかなと思います。
$ curl -G -d url=https://onk.hatenablog.jp/ https://tech-entry.onk.ninja/entries | jq . [ { "title": "ソフトウェア式年遷宮という概念の歴史と、Hatena::Let での実例", "url": "https://onk.hatenablog.jp/entry/2021/11/28/070728" }, { "title": "フルタイムでやる仕事を作る #wantedlydev", "url": "https://onk.hatenablog.jp/entry/2021/10/18/200338" }, ... ]
https://github.com/onk/tech-entry