背景
毎日更新のサイトを何本か回しているのですが、いちばん厄介だったのが「今日はネタがなかった」という終わり方でした。
本当にネタが無い日もあります。
でも、調べ方が雑だっただけの日も同じ顔をして終わります。
ログにはどちらも「更新なし」としか残らない。
で、これを区別する仕組みを入れたら、そこそこ効いたので書いておきます。
結論から
やったことは2つだけです。
1つめは、候補を機械で集めて「書ける候補が残っているか」を判定値として返すようにしたこと。
2つめは、逃げ道(後述する evergreen 記事)を使うコマンドの入口で、その判定値を見て拒否するようにしたことです。
2つめが肝でした。
判定を出すだけだと、結局それを見ないで逃げ道に入れてしまうので…。
何が問題だったか
うちのセール情報サイトは、記事の型が3つあります。
(A) 値引きセール記事。割引率と終了日が出典で確認できたときだけ書ける。
(B) ニュース型記事。値引きが無くても、日付の確定した発売告知なら書ける。
(C) evergreen 記事。evergreen というのは時期に左右されない読み物のことで、価格に依存しない選び方・計算方法を書く型だ。
問題は (C) です。
これは「(A) も (B) も取れなかった日に、それでも1本出すための逃げ道」として用意したものでした。
ところが運用していると、(B) を探し切らずに (C) へ落ちる日が出てきます。
(A) は Amazon のページが取れなくて成立しない日が多いので、「今日もセールが無いから evergreen で」という流れが癖になる。
(C) の記事自体は悪くないのですが、これが続くとその日に実際にあった発売告知を取りこぼし続けることになります。
候補収集に「判定」を持たせる
まず、候補を集めるコマンドに判定値を返させました。
RSS だけだと射程が狭いので、メーカーや流通のニュース一覧HTMLも機械で読ませています。
集めた候補は、既に公開済みの記事の出典URLと突き合わせて差し引きます。
下記が候補収集のコマンドです。
--site が対象サイト、--out が結果の JSON の書き出し先になっています。
$ node automation/run.js food:news --site ocha --out /tmp/ocha-news.json
この JSON の中に actionableVerdict というキーが入ります。
取り得る値は4つで、意味はそれぞれ違います。
has-actionable は「未来日付つきの未掲載候補が残っている」。つまり書ける。
all-upcoming-already-published は「候補はあるが全部掲載済み」。調査不足ではない。
rss-empty-html-not-checked は「HTML層まで見ていない」。何も結論できない。
nothing-anywhere は「本当に何も無い」。
大事なのは、真ん中の2つを分けたことです。
以前はどちらも「0件」で、見分けがつきませんでした。
実際に今日の出力がこれでした。
書き出した JSON から、判定値と「書ける候補の残り数」だけを抜いています。
$ node -e "const j=require('/tmp/senzai-news.json');console.log(j.actionableVerdict, j.actionableTotal)"
all-upcoming-already-published 0
洗剤サイトのほうは候補35件が全部掲載済みで、書ける新ネタが無い。
一方でお茶サイトは has-actionable で、残り3件でした。
逃げ道の入口で拒否する
判定値が出るようになっても、それを見ないで evergreen を書いてしまえば同じです。
なので、「なぜセール記事にできなかったか」を記録するコマンドのほうに、その JSON を食わせることを必須にしました。
このコマンドを通さないと evergreen が正規の手順として成立しない、という置き方です。
下記がその記録コマンドで、--source-check に先ほどの JSON のパスを渡します。
--reason は書けなかった理由、--tried は試した URL とその結果、--fallback は代わりに出したものです。
$ node automation/run.js no-update-report --site ocha \
--source-check /tmp/ocha-news.json \
--reason "..." --tried '["..."]' --proposal "..." --fallback "evergreen: {公開したslug} を追加"
渡した JSON の actionableVerdict が has-actionable だと、このコマンドは exit 3 で落ちます。
実際に今日、お茶サイトで落ちました。
no-update-report: この報告は受け付けられません(candidates-exist)。
food:news が ocha の「未来日付つき・未掲載」候補を 3 件返している
候補があるのに evergreen へ落とすのは調査不足である。
しかもご丁寧に候補3件のタイトルとURLまで出してきます。
言い訳の余地が無い…。
拒否されたあと何をしたか
候補3件のうち2件はノイズでした。
1件は日付の暦ページ、もう1件は既に別の出典で書いた題材の再掲です。
残りの1件が、京都のポップアップで新作のお茶が先行販売される、という告知でした。
価格が書いていないので最初は見送ろうとしたのですが、(B) は値引きが無くても成立する型なので、日付が確定していれば書けます。
会場側の告知ページを当たったら、イベント名と時間と会場が別ソースで取れました。
それで1本書けた、という流れです。
判定が無ければ、この1本は確実に落としていました。
「価格が無いから弱い」という自分の感覚だけで切っていたはずなので。
出したものは下記にあります。
なぜ「警告」ではなく「拒否」にしたか
最初は警告にしていました。
has-actionable なら warn を出して、でも処理は通す、という形です。
これは効きませんでした。
warn は読まれないからです。特に無人で回す処理では、出力の最後の数行しか見ない。
一方で、拒否にすると副作用があります。
判定が誤って has-actionable になったとき、本当にネタが無い日でも逃げ道が塞がれる。
このトレードオフは、塞がれたほうがマシと割り切りました。
理由は、誤検知のコストが「候補URLを3つ開いて確かめる」で済むのに対して、見逃しのコストは「その日の発売告知が永久に載らない」だからです。
非対称なので、うるさいほうに倒しています。
持ち帰り
自動化で毎日何かを出す仕組みを作るとき、「出せなかった日」の扱いを最初に決めておくと後が楽です。
そのとき、「出せなかった」の判定を書く側に任せないこと。
候補が残っているかどうかは機械で数えられるので、機械に言わせる。
そして逃げ道を使うコマンドの入口で、その数を見て止める。
出力に判定を足すだけでは足りません。
止めるところまでやって、はじめて運用が変わります。
本記事はAI補助で執筆した、個人開発の紹介記事です。