形式と配信
提案する配信仕様では、合意に応じて板および期間でパーティション分割した JSONL または Apache Parquet を使用します。レコードは安定した識別子を用いて設計され、スナップショットと差分納品の結合・重複排除が可能です。
配信方法、パーティション方式、更新頻度、フィルタ条件、最終フィールド一覧はスコーピング時に合意します。以下の例は説明用です。書き出しスキーマは納品前に確定します。
標準・合意・説明用の区分
- — 案件で確認された標準:UTF-8 出力、合意フィールド一覧、ファイル一覧とチェックサムを含む配信マニフェスト。
- — 案件ごとに合意:板・日付スコープ、秘匿処理プロファイル、更新頻度、配信チャネル(オブジェクトストレージ、定期書き出し、差分フィード、専用エンドポイント)。
- — 本ページの説明用:JSON レコード例、フィールド表、コードスニペット。これらは提案スキーマを示すものであり、スコーピングなしに特定の書き出しを保証するものではありません。
投稿レコードスキーマ(提案)
説明用のレコード例です。最終フィールド、正規化ルール、秘匿処理プロファイルはスコーピング時に合意します。
{
"post_id": "5ch:news4vip:1712345678:0042",
"thread_id": "5ch:news4vip:1712345678",
"board_id": "news4vip",
"board_name_ja": "ニュー速VIP",
"board_category_ja": "雑談系",
"post_index": 42,
"posted_at": "2024-04-05T21:14:38+09:00",
"posted_at_precision": "second",
"name_field": "以下、5ちゃんねるからVIPがお送りします",
"poster_id": "aB3xY9Zk0",
"body_text": "そういうのはこっちのスレでやってくれ\n>>38 が言ってたやつな",
"body_normalized": "そういうのはこっちのスレでやってくれ >>38 が言ってたやつな",
"reply_to": [38],
"quoted_spans": [
{ "target_post_index": 38, "raw": ">>38" }
],
"char_count": 34,
"contains_ascii_art": false,
"redaction_profile": "standard",
"redactions_applied": 0,
"source_snapshot_id": "snap-2026-08-01",
"record_version": 1
}| Field | Type | 説明 |
|---|---|---|
| post_id | string | 投稿の永続識別子 |
| thread_id | string | 親スレッドの識別子 |
| board_id | string | 板の識別子 |
| board_name_ja | string | 板の表示名 |
| board_category_ja | string | 板のカテゴリ |
| post_index | int | スレッド内の連番(1始まり) |
| posted_at | string (RFC 3339) | 投稿日時(JST、タイムゾーン明示) |
| posted_at_precision | enum | 元データの時刻精度(時代により異なる) |
| name_field | string | 投稿時の名前欄 |
| poster_id | string | null | 元データに存在する場合の投稿者ID |
| body_text | string | 投稿本文(原形、改行保持) |
| body_normalized | string | NFKC正規化・空白整形済みの本文 |
| reply_to | int[] | 解決済みのレス先(post_index) |
| quoted_spans | object[] | 引用アンカーの原文と解決結果 |
| char_count | int | 本文の文字数 |
| contains_ascii_art | bool | アスキーアート判定フラグ(ヒューリスティック) |
| redaction_profile | enum | 秘匿処理プロファイル |
| redactions_applied | int | 本レコードに適用された秘匿処理の件数 |
| source_snapshot_id | string | 書き出し元スナップショットID |
| record_version | int | レコードのスキーマバージョン |
スレッドレコードスキーマ(提案)
説明用のレコード例です。スレッドの範囲とエッジ解決は、合意スコープとソースの完全性に依存します。
{
"thread_id": "5ch:news4vip:1712345678",
"board_id": "news4vip",
"title": "日本語の口語表現について語るスレ",
"title_normalized": "日本語の口語表現について語るスレ",
"created_at": "2024-04-05T20:47:58+09:00",
"last_post_at": "2024-04-06T03:22:11+09:00",
"post_count": 412,
"unique_poster_id_count": 87,
"status": "archived",
"reply_edges": [[42, 38], [43, 42], [44, 38]],
"source_snapshot_id": "snap-2026-08-01",
"record_version": 1
}reply_edges はソース内の >>N アンカーから得た [from_post_index, to_post_index] の明示的ペアです。明示的なレスリンクを捕捉しますが、すべての会話関係は含まれません。アンカーがない・曖昧な場合は文脈が欠落する可能性があります。
| Field | Type | 説明 |
|---|---|---|
| thread_id | string | スレッドの識別子 |
| board_id | string | 板の識別子 |
| title | string | スレッドタイトル |
| title_normalized | string | NFKC正規化済みタイトル |
| created_at | string (RFC 3339) | スレッド作成日時 |
| last_post_at | string (RFC 3339) | 最終投稿日時 |
| post_count | int | スレッド内の投稿数 |
| unique_poster_id_count | int | ユニーク投稿者ID数 |
| status | enum | スレッド状態 |
| reply_edges | int[][] | レス関係のエッジリスト |
| source_snapshot_id | string | 書き出し元スナップショットID |
| record_version | int | スキーマバージョン |
エンコーディングと正規化(提案)
- — 出力は UTF-8 です。旧来のソースエンコーディングは、文書化された対応表に基づき解析時に変換します。変換に失敗したケースは、黙って破棄せず記録します。
- — body_text は原形を保持します(全角文字・顔文字・意図的な表記揺れを含む)。
- — body_normalized は、合意がある場合の NFKC 正規化・空白整形を適用した並行フィールドです。両方を納品する場合があります。
- — レスアンカー(>>N、>>N および過去の表記揺れ)は構造化フィールドに解析できます。原文は body_text に保持されます。
- — タイムスタンプは JST オフセットを明示した RFC 3339 形式です。元データの精度が秒未満の粒度に満たない場合は、精度を偽らず posted_at_precision に記録します。
- — 識別子は、定義された書き出し内でスナップショット間で安定するよう設計されています。同一書き出し定義の納品をまたいで post_id は変化しません。
スナップショットと差分納品(合意がある場合)
スナップショットとは、指定範囲(板・期間・フィルタ条件)の特定時点の書き出しです。source_snapshot_id により識別し、全ファイル・レコード件数・SHA-256 チェックサムを記載したマニフェストを添付します。
差分とは、2つのスナップショット間で追加・変更・撤回されたレコードの集合を、型付き変更レコードとして表現したものです。全件再取得なしに下流のデータストアを更新できます。
ソース側の削除・訂正イベントは、ライセンスで定めた範囲で差分フィードを通じて伝播できます。差分更新の可否はスコーピング時に確認します。
# JSONL
import json
with open("posts.jsonl", encoding="utf-8") as f:
posts = [json.loads(line) for line in f]
# Parquet
import pyarrow.parquet as pq
table = pq.read_table("posts.parquet")
df = table.to_pandas()
# Hive partitions by board and month
part = pq.read_table("5ch_posts/board_id=news4vip/year=2026/month=08/")
# Reconstruct a thread with its reply graph
thread_id = posts[0]["thread_id"]
thread = [p for p in posts if p["thread_id"] == thread_id]
thread.sort(key=lambda p: p["post_index"])
edges = [(p["post_index"], t) for p in thread for t in p["reply_to"]]配信オプション
- 暗号化オブジェクトストレージ
- マニフェストとチェックサムを伴う暗号化シャードへの署名付きURL — 標準オプション
- 定期スナップショット
- 指定範囲の定期的な全件書き出し — 案件ごとに合意
- 差分フィード
- スナップショット間の型付き変更レコード — 案件ごとに合意
- 専用アクセスエンドポイント
- 契約範囲に限定したプログラマティックアクセス — 案件ごとに合意
- プライベートリポジトリ納品
- 顧客管理のプライベートリポジトリへの納品 — 案件ごとに合意
| Item | Notes |
|---|---|
| 暗号化オブジェクトストレージ | マニフェストとチェックサムを伴う暗号化シャードへの署名付きURL — 標準オプション |
| 定期スナップショット | 指定範囲の定期的な全件書き出し — 案件ごとに合意 |
| 差分フィード | スナップショット間の型付き変更レコード — 案件ごとに合意 |
| 専用アクセスエンドポイント | 契約範囲に限定したプログラマティックアクセス — 案件ごとに合意 |
| プライベートリポジトリ納品 | 顧客管理のプライベートリポジトリへの納品 — 案件ごとに合意 |
技術プレビューをご請求ください。
対象の板、期間、想定用途をお知らせください。プレビューまたは有償評価のスコープと次のステップを確認いたします。