ホーム技術ブログ › 13
2026-09-09 #13

番組の言葉を年表にする。正規表現とSQLを組み合わせたら静かに0件になった

130万行の集計をページに置かない、表記ゆれを機械で束ねない、言い方が毎回変わる語をどう拾うか。載せない判断も含めて。

SQLite正規表現Python設計

結論から

  • 130万行の集計はページに置かない。事前に数えて JSON に書き出しておく
  • 表記ゆれは機械で束ねない。手で書く。別の語が混ざる
  • 正規表現とSQLを組み合わせるときは、先頭が取り出せなかったら止める。静かに0件になるのが一番困る
  • 作れるものを全部載せない。載せる基準を決めるほうが読めるものになる

作ったもの

語ごとに、年ごとの出現数を並べた年表です。番組の造語がいつ生まれて定着したかを見たくて作りました。 いまは11語あります。

うんちく                    1036回   変種3
エウレーカ                   427回   変種1
クリシェ                     383回   変種1
言語化                       224回   変種1
母語話者                     116回   変種1
機械音痴                     109回   変種2
ディバイド&コンカー           88回   変種4
月額会員制村作りサービス        73回   変種1
理科ができない                 53回   正規表現
デカルトみ                    42回   変種1
ミスター堀元                   36回   変種6

ページで数えない

1語ぶん数えるのに LIKE で130万行を舐めるので、1秒前後かかります。 以前、別のページで同じことをやって初回の表示が7.2秒になったことがありました。

$ python3 scripts/build_word_timeline.py            # 全部作り直す
$ python3 scripts/build_word_timeline.py kikai-onchi  # 1語だけ

出力: data/words/<slug>.json  と  data/words/_index.json

表記ゆれは手で書く

"unchiku": ("うんちく", ["うんちく", "蘊蓄", "ウンチク"], "..."),
"kikai-onchi": ("機械音痴", ["機械音痴", "機械オンチ"], "..."),

読みが同じものを自動で束ねる案も考えたのですが、 たとえば「クロード」を束ねると「クロードモネ」が混ざります。 語の数だけ例外があるので、変種は1語ずつ手で書くことにしました。11語ぶんなら手で足ります。

言い方が毎回変わる語がある

困ったのが「理科ができない」でした。番組の定番なのですが、言い方が毎回違います。

理科全然できない
理科できなさが
理科弱すぎる
理科が苦手

「理科ができない」という文字列そのままでは2件しか取れません。 それで、この語だけ正規表現で拾うようにしました。2件が53回になりました。

"rika-dekinai": ("理科ができない", [], "...",
                 r"理科[^。]{0,12}?(でき|出来|わか|分か|苦手|弱|ダメ|だめ)"),

ハマったところ: 正規表現の前段で静かに0件になる

正規表現はSQLでは使えないので、まずLIKEで粗く絞ってから、 取ってきた行にPython側で正規表現をかけています。 その粗い絞り込みに、正規表現の先頭のリテラル部分を使っていました。

# ❌ 最初の書き方
head = rx.pattern.split("[")[0]
cond, args = "(... LIKE ?)", [f"%{head}%"]

# 「理科[^。]{0,12}?(でき|...)」 → head は「理科」。ここまでは動く

ところが、括弧で始まる正規表現を足した瞬間に0件になりました。 [ で切っているので、先頭が ( の場合は何も切れず、 パターンの文字列がまるごとLIKEに渡ります。

LIKE '%(ミスター|Mr\.)堀元%'   ← こんな文字列は本文に無い

厄介なのは、エラーにならず静かに0件になることです。 「この語、まだ言ってないのかな」と一瞬思いました。

# ✅ いまの書き方
head = re.match(r"[^\[\(\{\.\*\+\?\|\]+", rx.pattern)
head = head.group(0) if head else ""
if not head:
    raise SystemExit(f"{slug}: 正規表現の先頭が取り出せない")

0件で素通りするより、止まってくれたほうがいいです。

載せない判断もする

デカルトみ      42回 / 多数の回
ソシュールみ      7回 / 2本
堀元み           2回 / 2本

年表は「いつ増えたか」を見るものなので、2回では線になりません。 作れるものを全部載せると、見る側は「どれが本当に定着した言葉なのか」が分からなくなります。

表記ゆれの多くは、こちらが作っている

音声には表記がありません。「むらづくり」と言われた音を、 ひらがなで書くか漢字で書くかを決めているのは機械のほうです。 つまり文字起こしに現れる割れは、番組の表記ゆれではなくこちらの産物です。

「村作り」がそうでした。本文ではひらがなと漢字で割れていましたが、 番組の概要欄に「月額会員制村作りサービス」と書いてあります。 番組自身の表記は1つに決まっているので、こちらが作った割れを揃えました。

「ミスター堀元」も同じです。本文では6通りに割れています。

ミスター堀元      17件
ミスター・堀元      5件
ミスター・ホリモト    7件
Mr.ホリモト        5件
ミスターホリモト     1件
Mr.堀元          1件

これも番組が6通りに書き分けたのではなく、機械が6通りに書いただけです。 では番組はどう書いているか、概要欄を引きました。

Why Mr.ホリモト?            (2本)
Why ? Mr.堀元 ?             (1本)
オープニング。Mr.Horimoto.    (1本)

カタカナの「ミスター」は0件でした。番組は Mr. と書いています。 本文にある22件の「ミスター〜」は、番組が一度も書いていない形です。

ただし後ろが「ホリモト」「堀元」「Horimoto」で割れていて、 こちらは番組自身の書き分けなので、どれかに寄せる判断はできません。 いまは揃えずに置いていますが、それは「人の呼び名だから触らない」からではなく、 寄せ先の後半が決まらないからです。

基準にしたこと

番組自身の表記が1つある   → こちらの割れを揃える     (村作り)
番組自身の表記が割れている → 寄せ先を決められない
番組自身の表記が無い     → 触らない

最初、この節を「人が名乗った名前だから直さない」と書いていました。 2つ間違えています。概要欄を引かずに「番組の表記は無い」と決めつけたのと、 自分が作った割れを番組の表記ゆれとして扱ったことです。

後者のほうが根が深いと思っています。 文字起こしを長く触っていると、目の前の文字列が「そう書かれていたもの」に見えてきます。 実際には、そこにあるのは自分たちが選んだ表記です。 揃えるかどうかを考える前に、その割れを作ったのが誰かを確かめる必要がありました。

← 語ごとの「いつ・どの回で語られたか」を1枚に置く。時… 目次へ

ゆる言語学ラジオ・ゆるコンピュータ科学ラジオ 公認/株式会社pedantic の了承と、運営者・安東暁史との合意に基づく協力体制で運営しています。ドメイン yururadio.jp は株式会社pedanticが保有。開発・保守: 安東暁史·このサイトについて·お問い合わせ