README

#Arc90 Content Extraction

Readability/Trafilatura にインスパイアされたコンテンツ抽出アルゴリズム。 Article Extraction Benchmark (AEB) でテスト。

#現在のベンチマーク結果

AEB (without layout, 2026-01-22) F1: 90.09% Precision: 88.08% Recall: 95.00% AEB (with layout, 2026-01-22) F1: 90.31% Precision: 88.49% Recall: 95.00%

比較(AEB, 参考値含む):

MethodPrecisionRecallF1Notes
Crater Arc90 (without layout)88.08%95.00%90.09%2026-01-22, 181 cases
Crater Arc90 (with layout)88.49%95.00%90.31%2026-01-22, 181 cases
Readability91.4%98.2%94.7%参考値 (過去計測)
Trafilatura97.8%92.0%93.7%参考値 (過去計測)

#ナビゲーション構造抽出の評価 (AEB, 弱ラベル)

AOM から抽出した nav 候補を、Puppeteer で取得したレイアウト矩形の 弱ラベル (nav/ad パターン + link density) で評価。 strict は記事メタ (share/author/byline 等) を除外した評価。

Nav eval (AEB 181 cases, 2026-01-23) Base: loose F1=62.28 strict F1=61.80 meta R=20.65 adOverlap=1130 Refined: loose F1=73.20 strict F1=75.59 meta R= 6.36 adOverlap=2 Refined+meta: loose F1=75.84 strict F1=75.95 meta R=14.63 adOverlap=2 Refined+hier: loose F1=75.24 strict F1=77.68 meta R= 6.36 adOverlap=2 Refined+hier+meta: loose F1=78.75 strict F1=77.61 meta R=19.91 adOverlap=2

結論:
  • Refined+hier が strict F1 で最大 (77.68%)
  • adOverlap がほぼ 0 になり、広告誤検出が大幅に減少
  • Widget パターン検出により WordPress 等のサイドバーウィジェットを検出
  • カラムパターン (col1-5) と toplinks パターンを追加

#実装済み機能

#スコアリング要素

  1. Text Density: テキスト長 / バウンディングボックス面積
  2. Visual Score: サイズと比率に基づくスコア
  3. Position Penalty: ヘッダー/フッター/サイドバー位置にペナルティ
  4. Role Bonus: Article=2.0, Main=1.8, Region=1.2
  5. Tag Multiplier: article/main=1.5, section=1.1, header/footer/nav/aside=0.1
  6. Selector Score: class/id のパターンマッチング
    • Positive: article, content, post, text, body, entry, story, main, blog, news
    • Negative: sidebar, widget, ad, sponsor, comment, footer, header, nav, menu
  7. Link Density Score: リンクテキスト比率が高いとペナルティ
  8. Paragraph Bonus: p/pre/blockquote 要素が多いほどボーナス
  9. Punctuation Bonus: 句読点密度が文章らしければボーナス
  10. Navigation Detection:
    • nav/header/footer/aside などのコンテナ優先
    • list + link ratio / link density で nav を補強
    • nav 配下は content block から除外

#コンテンツ選択ロジック

  1. find_article_or_main() で article/main 要素を探す
  2. content_blocks をスコア順にソート
  3. article/main vs top content_block を比較:
    • content_block が 20 倍以上大きい場合は content_block を優先
    • それ以外は article/main を優先

#既知の問題

#1. AOM パース問題 (d90bda7ed14df195)

  • 症状: F1=0%、AOM ツリーが空になる
  • 原因: HTML に 75 個の未閉じタグがあり、パーサーが処理できない
  • 対策: HTML パーサーの堅牢性向上が必要

#2. 過剰抽出 (ac3c035520461017 など)

  • 症状: Precision が低い (8.7%)、Recall=100%
  • 原因: 短い記事に対して大きな親コンテナが選ばれる
  • : 419 文字の記事に対して 16643 文字を抽出 (39.72 倍)
  • 対策: より細かい粒度でのスコアリングが必要

#3. ナビ評価のラベル揺れ

  • 症状: h2 単体などが nav として弱ラベル化されるケースがある
  • 原因: レイアウト側の弱ラベルが粗い
  • 対策: nav と meta を分離した二段評価を導入する

#次のステップ (F1 > 90% 達成に向けて)

#高優先度

  1. Readability スタイルの段落レベルスコアリング
    • 段落 (p, pre, blockquote) に直接スコアを付ける
    • スコアを親要素に伝播(減衰あり)
    • 最高スコアの要素をメインコンテンツとして選択

  2. 過剰抽出の改善
    • 「read more」「next post」「previous post」などのナビゲーションパターン検出
    • 短いコンテンツに対しては小さなコンテナを優先

#中優先度

  1. AOM パーサーの堅牢性向上
    • 未閉じタグの自動修復
    • エラー耐性の向上

  2. ナビゲーション/記事メタの分離
    • share/author/byline を nav と分離して抽出
    • nav + meta の二段評価を標準化

  3. ナビゲーション構造抽出の精度改善
    • list 階層の優先度調整
    • selector 粒度の揺れ (h2 単体など) に対する補正

#ベンチマークの実行方法

# AEB ベンチマーク実行 npx tsx scripts/aeb-runner.ts # WASM ビルド just wasm # ナビゲーション評価 npx tsx scripts/aeb-nav-eval.ts

#レイアウト矩形のダンプ

# AEB HTML を Puppeteer でレンダリングし、矩形情報を JSON へ出力 npx tsx scripts/aeb-layout-dump.ts --limit 10 npx tsx scripts/aeb-layout-dump.ts <hash> npx tsx scripts/aeb-layout-dump.ts <hash> --no-css --timeout 15000 npx tsx scripts/aeb-layout-dump.ts <hash> --allow-assets --timeout 60000

出力先: render-results/aeb-layout/<hash>.json

#参考資料

#
ContentBlock

pub(all) struct ContentBlock {
node :
AccessibilityNode

score : Double
text_density : Double
visual_score : Double
position_penalty : Double
}

Content block with scoring information

#
ContentBlock::to_string

fn ContentBlock::to_string(self : ContentBlock) -> String

#
ExtractConfig

pub(all) struct ExtractConfig {
min_text_length : Int
viewport_width : Double
viewport_height : Double
ad_sizes : Array[(Double, Double)]
}

Configuration for extraction

#
ExtractConfig::default

fn ExtractConfig::default() -> ExtractConfig

#
ExtractionResult

Extraction result

#
ExtractionResult::to_summary

fn ExtractionResult::to_summary(self : ExtractionResult) -> String

#
extract_content

Extract main content from an accessibility tree with layout information

#
extract_main_content

Quick extraction - returns just the main content node

#
get_extraction_stats

fn get_extraction_stats(result : ExtractionResult) -> String

Extract and print statistics about content detection
fn print_extraction_result(result : ExtractionResult) -> String

Print extraction result in human-readable format