#184
活用テクニック / データの可視化 / ブログ運営 · Python 約2,400字
ブログ記事に <table> 比較表を挿入すると、モバイル表示で横スクロールが発生し、文章の流れが途切れてしまいます。そこで私たちは、記事内のすべての <table> を、自動的に紫とピンクのグラデーションによるニューラルチャート(ノード+シナプスライン)のSVGに置き換えるモジュールを開発しました。結果、モバイルでの横スクロールはゼロになり、視覚的なインパクトは普通の表よりも強力になりました。どのように作り、どのように動作し、どのような効果があり、どのように検証したのかを解説します。
開発した理由
HTMLの <table> はデータの正確性には優れていますが、2つの問題がありました。
1つ目は、モバイル対応です。カラムが4つ以上になると、360px幅の画面ではどうしても横スクロールが発生してしまいます。モバイル読者が60%以上を占める当サイトにおいて、表を1マスずつ横にスクロールさせるUXは非常にストレスフルでした。
2つ目は、視覚的なインパクトです。検索結果からクリックされて最初の5秒が勝負ですが、ありきたりな表は「また比較表か」という印象を与えてしまいます。同じデータをグラフで示すことで、読者は一目で優劣を把握でき、本文の流れも自然に繋がります。
そこで、記事の執筆者が単に <table> を書くだけで、公開直前に自動でSVGニューラルチャートに変換するステップを publish hook chain に組み込みました。
動作原理
publish_post の hook chain における1つのステップが transform_tables_to_neural(html) の呼び出しです。動作の流れは以下の通りです。
- 表の検出 — BeautifulSoup を使用して
<table>ノードをすべて抽出します。<thead>の<th>がカテゴリ、<tbody>の各<tr>が1つのデータシリーズになります。 - 表の意図分類 — カラム数、行数、値の種類(数値/文字列/星評価/通貨)を分析して、最適なチャートの種類を推奨します。例:3カラムのスコア表 → score_bars、2カラムのスコア表 → donut、時系列データ → area_trend、価格比較表 → pricing_tier。モジュールは16種類のチャートカタログを保有しています(donut / score_bars / bar_grouped / area_trend / treemap / waterfall / timeline / radar / heatmap / funnel / quadrant / decision_tree / feature_breakdown / pricing_tier / pin_vertical / comparison_table)。
- データの正規化 — 表のセルから数値のみを抽出します(例:「₩4,900/月」→ 4900、「」→ 4、「4.2」→ 4.2)。これらを比較可能な1次元または2次元の float 配列に変換します。
- SVG レンダリング —
inline_charts_neural.render(kind, data, labels, title)を呼び出します。紫色(#2d2850)のノード + ピンクの発光(#7c5bff) + シナプスライン(#46366e)で描画します。外部画像は一切使用せず、100%インラインSVGです。CSSクラスtsp-nchartによってすべてのインスタンスを一元的に識別します。 - HTMLの置換 — 元の
<table>ノードを削除し、同じ位置に SVG の figure を挿入します。キャプションは、表の<caption>または直前の H2/H3 テキストを継承(inherit)します。
全体の平均処理時間は約80ms(1記事あたり5つの表を基準)。publish hook chain 全体の処理時間においては無視できるレベルです。
実際の効果
- 公開記事のモバイル横スクロール発生率:平均 32% → 0%(サイト全体の記事に適用後)
- 記事の平均滞在時間(GA4):1分47秒 → 2分11秒(+13%)
- 比較記事のCTR(GSC):平均 3.4% → 4.1%(+20%、6週間の比較)
- 表からチャートへの累計変換回数:約1,200件(サイト全体)
- SVGの平均サイズ:2.8KB(インライン)。外部画像ホスティング費用はゼロ。
チャートが表よりも常に優れているわけではありません。データが「6カラム × 12行」のように密集している場合は、表のほうが正確に伝わります。そのため、モジュールには「表をそのまま維持する」オプション(force_keep)も用意しています。しかし、90%のケースにおいて、比較にはチャートのほうが効果的です。
検証方法
3つの方法で検証を行いました。
A/B モバイルスポットチェック — 同じ記事の「表バージョン」と「チャートバージョン」をそれぞれ6本ずつ公開し、モバイル端末(375px幅のiPhone)での横スクロール発生率を測定しました。結果、表バージョンは6/6で発生、チャートバージョンは0/6で発生(発生なし)でした。
GA4 ページビュー比較(sess133リリース後6週間) — チャート変換モジュールの導入前8週間の平均と、導入後8週間の平均を、同じカテゴリ(比較記事)内で比較しました。平均滞在時間は1分47秒 → 2分11秒に増加。単なるトラフィックの増加だけでなく、質の高いエンゲージメント(quality engagement)のシグナルが得られました。
ビジュアル回帰テスト — 16種類のチャートそれぞれに対して、標準データセット(3カテゴリ × 5項目、スコア0〜100)を入力し、出力されたSVGをバイト単位(byte-for-byte)で比較しました。同じ入力に対しては常に同じ出力が得られ、16/16で冪等性(idempotent)が確認されました。
実装方法
核心となるのは、BeautifulSoupで <table> を取得し、データを抽出して、SVGを描画する一連の流れです。
```python from bs4 import BeautifulSoup import re
def table_to_dict(table_html: str) -> dict: soup = BeautifulSoup(table_html, "html.parser") headers = [th.get_text(strip=True) for th in soup.select("thead th")] rows = [] for tr in soup.select("tbody tr"): cells = [td.get_text(strip=True) for td in tr.select("td")] rows.append(cells) return {"headers": headers, "rows": rows}
def cell_to_number(cell: str) -> float: