Back to skills
extension
Category: Data & AnalyticsNo API key required

数据可视化 HTML 转 PNG 渲染器

用 HTML/CSS 排版 + Playwright 驱动 Chromium 出图,数字文字 100% 精确。替代 AI 生图做数据看板、信息图、报表长图。内置 11 种横竖版版式预设与 8 套配色主题,命令行一键切换;含三模式截图(固定画布/整页/元素)、自动缩放适配与溢出质检。

personAuthor: user_f7402d03hubcommunity

数据可视化 HTML 转 PNG 渲染器

核心思想:把浏览器当渲染引擎用。HTML/CSS 负责像素级精确排版,Chromium 负责渲染,Playwright 负责按尺寸截图。 一句话准则:凡是图里有真实文字/数字,就不要用 AI 生图。

为什么不用 AI 生图

| 维度 | AI 生图 | 浏览器渲染 | |------|---------|-----------| | 数字准确性 | ❌ 画成伪汉字/乱码/错位 | ✅ 100% 精确 | | 可复现 | ❌ 每次都不一样 | ✅ 同输入同输出 | | 可迭代 | ❌ 只能重抽 | ✅ 改一行 CSS 即可 | | 中文排版 | ❌ 字形崩坏 | ✅ 系统字体直出 | | 成本 | 按张计费 | 0 |

技术栈组成

数据(硬编码进 Python/JS)
   ↓
HTML + CSS(精确排版,画布尺寸写死)
   ↓
Playwright → Chromium(headless 渲染,device_scale_factor=2 出 Retina 图)
   ↓
PNG(固定画布 / 整页自适应 / 单元素)
   ↓
质检(溢出检测 + 图片加载 + JS 报错)→ vision_analyze 复核

版式预设(--preset)

横版/竖版一键切换,不用改 HTML--width/--height 可覆盖。

| 预设 | 尺寸 | 朝向 | 典型用途 | |------|------|------|---------| | portrait | 1500×2000 | 竖版 | 信息图看板(默认) | | a4 | 1240×1754 | 竖版 | A4 纵向 150dpi 打印 | | xhs | 1080×1440 | 竖版 | 小红书 3:4 | | story | 1080×1920 | 竖版 | 手机竖屏 9:16 | | wechat | 1080×自适应 | 整页 | 微信长图 | | landscape | 2000×1500 | 横版 | 横版看板 4:3 | | ppt | 1600×900 | 横版 | PPT / 投屏 16:9 | | feishu | 1200×675 | 横版 | 飞书卡片配图 | | banner | 1920×640 | 横版 | 宽幅横幅 3:1 | | a4l | 1754×1240 | 横版 | A4 横向 | | square | 1200×1200 | 方版 | 方图 1:1 |

版式自适应:脚本给 <html>.layout-portrait / .layout-landscape / .layout-square。 模板里把并排区块包进 .cols —— 横版自动双栏等高,竖版自动单列瀑布,同一份 HTML 通吃。

内容超高自动缩放:矮画布(feishu/banner)装不下时,--auto-fit(默认开)会把 .page 内容等比缩放并迭代收敛,报告里 fit_scale < 1 即表示缩过。要严格报错就加 --no-auto-fit

配色主题(--theme)

注入 CSS 变量,模板用 var(--primary) 等消费即可,换主题不用碰 HTML

| 主题 | 主色 | 场景 | |------|------|------| | blue | #1B5CF5 | 商务默认 | | teal | #0d9488 | 清爽/医疗/环保 | | purple | #7c3aed | 科技/AI | | orange | #ea580c | 零售/促销 | | green | #16a34a | 农业/增长 | | crimson | #dc2626 | 预警/风险 | | dark | #3b82f6 | 深色投屏/大屏 | | mono | #111827 | 黑白打印 |

注入的变量:--primary --primary-dark --accent --ink --gray --line --bg --canvas --up --down--up 涨色 / --down 跌色,默认中式红涨绿跌)

同时给 <html>.theme-<名>,需要单独微调深色态就写 .theme-dark .xxx { }

查全部预设与主题:

python3 scripts/render.py --list

环境准备(一次性)

# 1. Python 环境装 Playwright(本机已装在 /opt/lightclaw-python)
/opt/lightclaw-python/bin/python -c "import playwright; print('OK')"
# 若没有:pip install playwright && python -m playwright install chromium

# 2. 确认 Chromium 存在
ls ~/.cache/ms-playwright/chromium-*/chrome-linux64/chrome

# 3. 中文字体(缺了会出豆腐块 □□□)
fc-list :lang=zh family | sort -u | head
# 缺失时:sudo apt install -y fonts-noto-cjk fonts-wqy-zenhei

本机已验证可用的解释器:/opt/lightclaw-python/bin/python(playwright 1.58.0 + chromium-1223)。系统 python3 和 ~/venv~/.hermes/venv没有 playwright,别用。

标准流程

第一步:数据落盘 + 自校验

  1. 从来源(截图/表格/API)逐行提取数据,写成 Python dict/list
  2. 自校验:明细加总 = 小计?小计 = 合计?增长率公式统一?排名是否自洽?
  3. 口径不能混用(销售收入 vs 净收入是两套数字,不能同图并列)

第二步:写 HTML

templates/infographic.html 复制起手,改数据和区块。关键约定:

  • .page 容器宽高写死,且与截图参数完全一致
  • * { margin:0; padding:0; box-sizing:border-box } 必须有
  • 字体栈:"Noto Sans CJK SC","Source Han Sans SC","PingFang SC","Microsoft YaHei",sans-serif
  • 数字列加 font-variant-numeric: tabular-nums(等宽数字,对齐好看)
  • 内容多时给 .page.tight class 压缩间距
  • 颜色一律写 var(--primary) 这类变量,别硬编码色值,否则 --theme 换不动
  • 需要横版双栏的区块,包一层 <div class="cols">(竖版会自动退化为单列)
  • 画布尺寸建议写 width:var(--page-w); height:var(--page-h),交给 --preset 接管

第三步:截图

PY=<你的python>
R=<skill目录>/scripts/render.py

# 竖版信息图(默认)→ 3000×4000
$PY $R report.html out.png --preset portrait --theme blue --require-images --strict

# 横版投屏看板(深色)
$PY $R report.html out.png --preset ppt --theme dark

# 微信长图(高度自适应)
$PY $R page.html out.png --preset wechat

# 小红书 / 飞书卡片配图
$PY $R page.html xhs.png    --preset xhs --theme orange
$PY $R page.html card.png   --preset feishu

# 只截某个元素(卡片/表格,做素材)
$PY $R page.html el.png --selector "#card" --scale 3

# 等异步内容(ECharts/网络图片)
$PY $R chart.html out.png --wait-selector ".chart-ready" --wait 3000

# 在线页面直接截
$PY $R https://example.com out.png --preset wechat

# 查所有预设与主题
$PY $R --list

脚本返回 JSON 报告 + 退出码(0 通过 / 1 溢出或质检失败),可直接用于循环重试。

第四步:视觉验证(不能省)

vision_analyze(图片路径, "检查文字溢出/重叠/裁切、数字可读性、中文是否豆腐块")

必查项:

  • 中文有无豆腐块 □
  • 进度条百分比标签是否被压
  • 长文本是否裁切、表格是否越界
  • 数据自洽(图里的"第X位"和排行榜一致)
  • 不该有的内容(如用户要求排除"风险预警")

画布尺寸

见上方「版式预设」表,--preset 直接给。@2x 输出即表中尺寸 ×2。

Pitfalls(都是踩过的)

排版溢出

  • overflow_y > 2 就是溢出。修法优先级:补实质内容 > 加 .tight 压间距 > 调字号
  • ❌ 别用 flex 拉伸填空白——留白只是转移到卡片内部,治标不治本
  • ❌ 别靠加大 padding 填底部空白——内容不够就补对比表、口径说明、交叉分析

底部大片空白

说明内容量不够撑满固定画布。优先级:补实质内容 > 让列表型区块弹性吃掉余高 > 换 --full 整页模式

弹性吃余高的正确写法(只拉「内容型」元素,不是给卡片注水 padding):

.page > .cols            { flex:1 1 auto; min-height:0; }   /* 行吃掉剩余高度 */
.cols > .block           { display:flex; flex-direction:column; }
.cols > .block > table,
.cols > .block > .trend  { flex:1 1 auto; }                 /* 表格/柱图随之变高 */

实测:某看板底部空隙 336px→18px、593px→14px,且卡内不出现空洞。 ⚠️ 副作用:被拉高的卡片若内容太少(如只有 4 个小卡),会在卡内留空洞 —— 这时改回补内容,别硬拉。

深色主题文字看不见(真实踩坑)

浅色态里 .sumbar/th 常写 color:var(--canvas)(浅色)压深底 —— 换到 --theme dark--canvas 变成近黑,就成了深字压深底,对比度 1.2:1,肉眼读不出来。 修法:深色态显式反色。

.theme-dark .sumbar { color:var(--ink); }        /* dark 下 --ink 是浅色 */
.theme-dark .sumbar div span { color:var(--gray); opacity:1; }
.theme-dark th { background:var(--primary); color:#fff; }

每加一个深色主题,都要单独验一遍反白区块(表头、页脚条、高亮卡)。

中文豆腐块

fc-list :lang=zh 为空 → 装 fonts-noto-cjk。CSS 字体名必须写全(Noto Sans CJK SC 不是 Noto Sans)。

Chromium 找不到

Playwright 版本和 chromium 缓存版本不匹配时报 executable doesn't exist。 render.py 已内置自动探测(glob 取最新版),仍失败就 --chromium /绝对路径

数据自相矛盾

  • 排行榜排名 ≠ 焦点分析里写的"第X位" → 出图前脚本内断言校验
  • 同期为 0 时增长率记 100%,不是 undefined/∞

多版本污染

带门店/不带门店等多版本,用同一套数据源 + 布尔开关分支,别复制两份 HTML。总览版不得残留任何单店名称。

截图糊/发虚

--scale 必须 ≥2;launch args 里 --force-device-scale-factordevice_scale_factor 要一致(脚本已处理)。

验证清单

  • [ ] 数据逐行核验(明细加总=小计=合计)
  • [ ] 增长率公式统一 (目标-同期)/同期
  • [ ] overflow_y ≤ 2(脚本 exit 0)
  • [ ] 图片 100% 加载(--require-images
  • [ ] 无 JS 报错(--strict
  • [ ] 中文无豆腐块(vision_analyze 确认)
  • [ ] 无不该出现的内容 / 版本间无污染
  • [ ] 底部无半页空白

文件清单

  • scripts/render.py — 通用截图器,三模式 + 质检,命令行驱动,零改代码
  • templates/infographic.html — 基础模板(标题/横幅/KPI/进度条/表格/汇总条 + 主题变量 + 横竖版自适应)
  • templates/dashboard-full.html满版看板模板(推荐起手):5 区块(结构占比/排行表/归因四格/月度柱图/口径卡)
    • 弹性吃余高 + 深色态反色已修,1500×2000 与 2000×1500 双向实测无空白

Pitfall:视觉复核会误报

vision 模型会把双栏高度不齐误报成"内容漏在卡片外"。别直接照着改, 先用 DOM 坐标做机器裁决:

# 取元素与其所属卡片的 getBoundingClientRect,判断是否真的越界
insideOwnCard = r.x >= card.x and r.right <= card.right and r.bottom <= card.bottom

本 skill 就踩过:vision 连报两次"-4.2%/-9.8% 漏在卡外",DOM 实测 cellCount=2(无重复渲染)、insideOwnCard=true(完全在卡内)——纯误报。 根因只是左卡比右卡矮,已用 align-items:stretch + .block{height:100%} 修掉。

结论:视觉复核用来发现"哪里可疑",DOM 坐标用来判定"是否真错"。

实测记录(v1.1.0)

11 种版式预设  → 11/11 exit 0 ✅(含 feishu/banner 触发 auto-fit 0.91/0.86)
8 套配色主题   → 8/8  exit 0 ✅(含 dark 深色态,汇总条已反色可读)
满版看板模板   → 竖版/横版底部空隙 18px / 14px ✅(修前 336 / 593)
--no-auto-fit  → banner exit 1 ✅ 溢出检测未被绕过
横版双栏       → DOM 验证 equalBottom=true 底边齐平 ✅
中文渲染       → 无豆腐块,数字清晰,无裁切 ✅