仕組みと仕様
プロフィールページがどう作られているか、その考え方と使っているスクリプトを公開します。
このサイトについて
ブックマーカー図鑑は、はてなブックマークの公開情報だけをもとに、 各ユーザーの傾向を「図鑑エントリー」としてまとめた非公式の分析です。 ページは人手ではなく、以下の決まった手順(パイプライン)で機械的に生成しています。
プロフィールができるまで
-
取得 fetch_bookmarks.rb → CSV公開ブックマーク(日時・タイトル・URL・コメント・タグ・スター)を取得します。
-
正規化 normalize.py → JSONL列の揺れや日本語ヘッダを吸収し、URLを除いたコメント本文などを整えます。ここでは評価・分類はしません。
-
形態素解析 tokenize.py(SudachiPy)→ tokenized JSONLコメントを単語に分割し、はてなユーザーへの言及(idコール等)も検出します。
-
集計 aggregate.py → aggregate.json巨大な解析結果を、機械で確定できる定量データ(
computed)と、 LLMが判断するための材料(materials)に圧縮します。 この段階でも攻撃性などの断定はせず、候補の集計に留めます。 -
生成 LLM(ChatGPT)→ analyzed.json材料と実際のコメントを読み、テーマ分類・攻撃性の判断・紹介文を書きます。 出力は決まったスキーマ(
profile.schema.json)に従います。 -
描画 render.rb → profiles/<id>.htmlできあがったデータをHTMLの図鑑カードに整形します。あなたが見ているこのページも同じ仕組みです。
-
目視で確認 人による最終チェック生成されたカードを人が確認し、明らかな誤りや不適切な表現がないかを見ます。問題があれば修正・非公開にします。
設計上の約束
- 機械は断定しない。 前処理・集計は「候補」を数えるだけで、攻撃的・○○派といった評価は下しません。
- 文脈判断はLLMに委ねる。 例えば攻撃語は、引用・否定・皮肉・事実指摘の可能性を実際のコメントを見て判断します。
- 誤検出を抑える照合。 単純な文字列一致ではなく単語の境界に合わせた一致を使い、「ばかり」から「ばか」を拾うような誤検出を避けます。
- 数字で断定しない。 攻撃的な語は「該当した候補」であり回数を強調すると誤解を招くため、語のみを表示しています。
- 公開情報のみ・非公式。 非公開情報は扱わず、本人の申告に基づく評価でもありません。
LLMは何を見てどう判断しているのか
LLMには生のデータではなく、機械で集計・整理した材料(aggregate.json)を渡します。
数値の集計はすでに済んでいるので、LLMの仕事は「材料が本当にその通りか」を実際のコメントで確かめ、言葉にすることです。
- 定量データはそのまま使う。 活動時間・頻出語・スターなどの数値は機械が確定済み。LLMが数え直したり作り変えたりはしません。
- 根拠のある記述だけ。 紹介文や評価は必ずデータに根拠を持たせ、引用は実在するコメントのみ(創作・改変はしません)。
- 人格ではなく振る舞いを見る。 「こういう人間だ」と断定せず、コメントに現れた芸風・傾向として描きます。政治・思想・健康などは断定せず、関心の傾向にとどめます。
テーマ分類 ― 候補を実例で確かめる
まず機械が、テーマ辞書(政治/経済/ジェンダー/メディア/医療… の標準12分類)のキーワードを、 各ブックマークのタイトル・コメント・タグと突き合わせ、テーマごとの件数つき候補を作ります。 ただしキーワード一致は素朴なので、LLMは添えられた実例(例文)を見て次の点を補正します。
- 否定・引用で膨らんでいないか。 「『差別』はよくない」のようなコメントは、そのテーマを主張しているわけではありません。
- 媒体由来ではないか。 togetter・anond など、話題ではなくプラットフォームそのもので件数が付いていないかを見ます。
- 辞書が拾えない話題。 その人特有のテーマがあれば1〜2個だけ足します。
こうして確かめたうえで、上位8〜12テーマを「関心の地図」として提示します。件数は機械カウントを使うので、印象で盛ることはありません。
攻撃性 ― 数ではなく文脈で判断する
機械は攻撃語辞書(重み1〜8)で「攻撃語の候補」を検出し、各候補に実際のコメント例を添えて渡します。 例文はスターの高い順だけでなく、星が付かなかった生の発言も混ぜてあります(本当に危険な攻撃ほど支持されにくいため)。 LLMはこの例文を読み、その語が本当に攻撃かを次の観点で判断します。
- 誰の発言か(帰属)。 本人の攻撃か、「〇〇が『殺せ』と言っている」のような引用・紹介か、「その言い方はよくない」という否定・批判か、自虐・ネタか。特に「死ね」級の重い語は、この確認を最優先します。
- 矛先はどこか。 暴言そのものを咎めているなら攻撃に数えず、引用して相手を嘲笑・晒しにしているなら攻撃に数えます。
- 対象語は単独では攻撃としない。 「外国人」「フェミ」などの集団名そのものは攻撃語ではなく、周辺の言葉(「〜は出ていけ」等)で判断します。
- 数だけで断じない。 「バカ100回」と「殺せ3回」は同じ重さではありません。パネルを出すかは、率・重み・継続性と、例文が実際に攻撃かで決めます。
公開している仕様・スクリプト
出力の仕様書は公開しています。処理に使っているスクリプトと辞書の構成は以下の通りです。
-
パイプライン(取得・前処理・集計)
scripts/fetch_bookmarks.rb— 公開ブックマークの取得scripts/pipelines/normalize.py— 正規化(列の揺れ吸収・コメント整形)scripts/pipelines/tokenize.py— 形態素解析(SudachiPy)・言及検出scripts/pipelines/aggregate.py— 機械集計(computed / materials)scripts/pipelines/run_pipeline.py— 一括実行scripts/pipelines/mine_attack_terms.py— 攻撃語候補の発掘(辞書整備用)
-
描画
scripts/render.rb— JSON から図鑑カードHTMLを生成templates/profile.html.erb— カードのテンプレート
-
サイト生成
scripts/build_index.rb— 一覧ページscripts/build_new.rb— 新着ページscripts/build_feed.rb— Atomフィードscripts/build_sitemap.rb— サイトマップ
-
辞書(dict)
dict/attack_terms.json— 攻撃語『候補』辞書(重み・カテゴリ付き)dict/theme_terms.json— テーマ分類辞書dict/bookmark_users.json— はてなユーザー辞書(言及検出用)dict/plain_mention_blocklist.txt/plain_mention_allowlist.txt— 言及検出の制御stopwords/japanese_base.txt/hatena_noise.txt/keep_words.txt— ストップワード・保持語
-
仕様・指示書(公開済み)
profile.schema.json— 出力スキーマ。LLMが返すJSONの形(項目・型)を定義します。profile_instruction_aggregate.md— LLM指示書(集計版)。機械で前処理・集計した材料の扱いを説明する補足。テーマ確定・攻撃性の判断・紹介文の書き方を示します。profile_instruction.md— LLM指示書(ベース)。出力の契約・倫理・トーン・攻撃性判定の考え方など、土台となる方針を定めます。
※ LLMには ベースと集計版の指示書を両方渡します(集計版はベースを置き換えず、前処理済みデータ用に補足するものです)。
当初は profile_instruction.md だけで、LLMに生データを渡して解析のすべてを任せていました。
しかしこの方式では、攻撃語やテーマ語といった単語の検出精度が十分ではありませんでした。
そこで、形態素解析や集計などの機械的な前処理をあらかじめ行い、その結果をLLMに渡して判断してもらう方式へ改めました。
集計版の指示書(profile_instruction_aggregate.md)は、この新しい流れのための補足で、ベースの profile_instruction.md と一緒にLLMへ渡します(置き換えではありません)。