Wikipediaのランダム記事から実在人物をfail-closedで外す
創作の題材を機械的に選ぶとき、Wikipedia のランダム記事は手軽な材料になる。ただし、そのまま使うと実在の人物や事件を怪異の当事者にしてしまう。フィクションの題材にしてよいのは、地名・地形・生物・物・概念などであって、実在の人物や事故災害ではない。
こわいはなしでは1日2話を自動生成しており、その着想に Wikipedia のランダム記事を使っている。ここで実在人物を引いてしまうと取り返しがつかないので、判定をfail-closed(判定できないものは全部落とす)で組んでいる。
この記事では、そのフィルタの段構成と、実測でどれくらい落ちるのかを示す。確認した環境は Windows 11 / Node.js v24.11.1 で、数字は 2026-09-19 に実際に回した2回ぶんの実測値である。
カテゴリ名の除外では漏れる
最初に思いつくのは、記事のカテゴリ名で除外する方法である。「日本の俳優」「1980年生」といったカテゴリを持つ記事を落とす、という考え方だ。これは効くが、これだけでは足りない。
理由は2つある。
- カテゴリが付いていない記事がある。 スタブ(書きかけ)記事はカテゴリが少なく、人物でもカテゴリから判別できないことがある。
- カテゴリの語彙が揺れる。 「○○の政治家」「○○出身の人物」「○○家」など、人物を表すカテゴリ名は無数にある。ブロックリスト方式では必ず取りこぼす。
そこで、カテゴリは入口の1段として使い、最終判定は別の根拠で行う。
Wikidata の P31 まで見る
Wikipedia の各記事には、たいてい対応する Wikidata の項目がある。Wikidata の P31(instance of/分類) は「この項目は何であるか」を機械可読な ID で持っている。
| P31 の値 | 意味 |
|---|---|
Q5 |
ヒト(=実在の人物) |
Q16521 |
分類群(生物の分類) |
Q213907 |
特定の分類階級の一つ |
つまり P31 に Q5 が入っていれば、カテゴリ名がどうであれ実在人物である。逆に、こちらが許可する ID の集合(生物・地形・物・概念など)に明示的に一致した場合だけ通すという allowlist にできる。
どちらも API で引ける。まず Wikipedia の記事名から Wikidata の項目 ID を出す。pageprops を使う。
1 | |
titles は URL エンコードした記事名で、上の例は「枯草菌」である。実行結果はこうなった。
1 | |
出た ID を Wikidata に渡して P31 を取る。
1 | |
1 | |
分類群なので、生物として通る判定になる。この2つを繋ぐと、「記事名 → Wikidata の項目 → P31 の集合」まで機械で辿れる。なお claims.P31 は項目によっては空なので、|| [] を省くと落ちる。取れなかった場合をどう扱うかが、あとで効いてくる。
段の構成
判定は複数の段に分かれていて、どの段で落ちたかが結果に残る。段の名前と、落ちた件数が集計されて出てくる。
| 段 | 何を見るか | 落ちるもの |
|---|---|---|
| L3 | 記事名 | 曖昧さ回避のページ |
| L4 | 本文の量 | スタブ(判断材料が足りない) |
| L5 | 記事の形 | 人物伝の体裁 |
| L7 | 記事名・本文の正規表現 | 事故・災害・事件・企業名など |
| L8 | Wikidata の P31 | 許可した分類に明示的に一致しないもの全部 |
| cat-* | カテゴリ | 人物・生年・事件・作品・組織 |
| title-* | 記事名の形 | 一覧記事・学名・曖昧さ回避 |
最後の L8 が allowlist になっているのが要点である。l8-not-allowed(許可した分類に無い)と l8-no-claims(P31 そのものが取れない)をどちらも落とす。取れなかったものを通すと、Wikidata 側の欠落がそのまま穴になる。
実測:240件から1〜2件
2026-09-19 の2回ぶんの実測値である。1回の実行で 240 件の候補を評価している。
| 回 | 評価した候補 | 採用 |
|---|---|---|
| 1回目 | 240 | 1 |
| 2回目 | 240 | 2 |
1回目の内訳(落ちた理由の上位)はこうだった。
| 段 | 件数 |
|---|---|
l4-stub(スタブ) |
150 |
l8-not-allowed(P31 が許可集合に無い) |
15 |
l5-biography(人物伝) |
16 |
title-latin-name(学名の形) |
10 |
l3-disambiguation(曖昧さ回避) |
8 |
cat-work(作品) |
8 |
l8-no-claims(P31 が取れない) |
7 |
cat-organization(組織) |
3 |
cat-person / cat-birth-year / cat-incident |
各 2 |
l7-rx-disaster(災害の語) |
1 |
150件がスタブで落ちているのが実態である。ランダム記事の過半は判断材料が足りない。そして人物系(l5-biography 16 + cat-person 2 + cat-birth-year 2)が20件ある。ランダム記事の実測ではおよそ3割が実在人物になるので、この段が無いと事故は時間の問題である。
採用されたのは「枯草菌」(土壌や植物に広くいる細菌)で、P31 は上で実測したとおり Q16521(分類群)と Q213907 だった。生物なので通る。
歩留まりは 240件から1〜2件、つまり 1% 未満である。 この低さは欠陥ではなく、fail-closed の設計そのものの結果である。通すべきものを取りこぼしても「今日は別の題材になる」だけで済むが、通してはいけないものを通すと公開事故になる。非対称なので、厳しい側に倒す。
「気に入らない題材が出たら引き直す」をやらない
運用でいちばん壊れやすいのはここである。フィルタを通った題材が使いにくいと、つい --no-cache で引き直したくなる。これをやると2つ壊れる。
- 組み合わせの再現性が壊れる。 同じ日の同じ枠で再実行したとき、同じ題材が出なくなる。生成が決定論でなくなると、二重実行の検知も効かなくなる
- フィルタを緩める動機が生まれる。 「引き直せばいい」と思っていると、判定が厳しすぎるのではという疑いが常に湧く
そこで、seed を「日付+枠」だけにして、同じ枠の再実行では必ず同じ題材が出るようにした。引き直しの入口を塞ぐと、フィルタを手で緩める話も出てこない。
使うときの制約:記事名を本文に書かない
もう1つ、著作権まわりの制約がある。Wikipedia の本文は CC BY-SA なので、要約であっても転載はできない。そこで次の2つを決めている。
- 記事から抜いた本文(
topic_extract)は執筆者が読むためだけのもので、作品の本文に写さない - 記事名(固有名詞)は本文に書かない。着想として使うだけ
代わりに、記事から拾った具体語を3件選んで記録し、それが本文に実際に出ているかを機械で検査する。
1 | |
出力はこうなる。
1 | |
longest_common_substring は、書いた本文と元記事の抜粋のあいだでいちばん長く一致した文字列の長さである。これが 8 以上なら要約を写した疑いがあるとみなす。上の例では 3 なので、たまたま同じ短い語が出ただけだと分かる。
anchors_ok は「拾った3語が全部本文に出ているか」である。題材を1回挿しただけの話を弾くための検査で、これが通らないとビルドが止まる。
まとめ
ランダムな外部データを創作の入力にするときの勘所は3つある。
- カテゴリ名ではなく、機械可読な分類(Wikidata の P31)で判定する。 語彙の揺れに強い
- allowlist にして fail-closed にする。 「悪くなさそうだから通す」を作らない。判定できないもの・分類が取れないものは落とす
- 歩留まりの低さを受け入れる。 240件から1件でよい。引き直しの入口を作らず、seed を固定して決定論にする
そのうえで、落ちた件数を段ごとに出力へ残しておく。今回の「150件がスタブ」のような分布が見えていると、フィルタが厳しすぎるのか、母集団がそもそもそういうものなのかを区別できる。
生成された話はこわいはなしで読める。どの話も、この記事で説明したフィルタを通った題材から着想を得ている。