简介:这是一套轻量级HTML转JSON工具库的完整开源实现,面向Python开发者、前端工程师及数据清洗人员,解决网页结构化数据提取难题——尤其擅长智能解析HTML表格(自动识别表头作为JSON键)与常规HTML文档树。资源包共32个文件,涵盖7个核心Python模块(含convert_html.py、convert_html_tables.py等)、6个测试用HTML样例、4个配置类YML/TOML/INI文件、2个Markdown说明文档及LICENSE等工程必备文件,整体仅512KB,便于快速集成与二次开发。目前已有716人学习下载,适合需要将爬取的HTML页面或静态报表批量转为结构化JSON、嵌入自动化流程或对接后端API的中初级开发者。代码结构清晰,含完整单元测试(test_html_to_json.py等)、Docker容器化支持、CI/CD配置及覆盖率报告,附带html_table_varieties.jpg直观展示多种表格样式适配效果,开箱即用且易于调试。
1. 把 HTML 表格秒变结构化 JSON:不是正则硬拆,而是用语义理解做键值映射
你有没有遇到过这种场景:爬下来一个带<table>的页面,表头是「姓名|部门|入职时间|职级」,但数据是纯 HTML<tr><td>张三</td><td>算法组</td><td>2022-03-15</td><td>P6</td></tr>套嵌?手动写BeautifulSoup.find_all('tr')再逐行取td、再对齐表头——写到第三张表就怀疑人生。更糟的是,有些表格压根没<thead>,第一行是标题还是数据全靠肉眼猜;有的合并了colspan/rowspan;还有的嵌套了<div>或<span>在td里……这时候,“html-to-json” 就不是个玩具工具,而是一把能识别表格意图的手术刀。它不靠暴力解析标签树,而是先做表结构还原:识别哪行是 header(哪怕没<th>)、跳过空行和分隔行、处理跨列单元格对齐、剥离内联样式与冗余标签,最后输出带语义键名的扁平 JSON 数组。适合需要批量处理运营后台导出页、政府公开数据 HTML 表、老系统报表快照的工程师——尤其当你明天就要把 87 张不同结构的 HTML 表喂进 Pandas 做分析时,这个工具能帮你省下 4 小时 debug 时间。
2. 核心原理:为什么“智能识别表头”不是玄学,而是三层语义推断
2.1 表结构重建:从 DOM 树到逻辑二维矩阵
html-to-json的核心不是直接转 JSON,而是先构建一张逻辑表格矩阵(Logical Table Matrix)。它会忽略<table>标签的视觉渲染属性(如border="0"、style="display:none"),专注三件事:
- 行类型判定:扫描每行
<tr>,统计该行中<th>和<td>的数量比、文本长度分布、是否含加粗/居中样式(通过解析style属性或 CSS class 名关键词如"header"、"title");若某行<th>占比 > 60% 或含scope="col",则标记为候选 header 行; - 跨单元格对齐:遇到
colspan="2"的<th>,自动在矩阵中横向扩展占位,并将后续<td>按列索引对齐到对应 header 下;rowspan则纵向延续 header 归属; - 空行与分隔行过滤:跳过纯
<tr><td> </td></tr>或含class="divider"的行,避免生成空对象。
这步完成后,原始 HTML 表被抽象成一个二维数组[[header1, header2], [val1, val2], ...],后续 JSON 化才有可靠基础。
2.2 键名生成策略:四种 header 提取模式
工具支持通过参数--header-mode指定键名来源,实际项目中我常根据源 HTML 质量动态切换:
| 模式 | 触发条件 | 输出示例 | 适用场景 |
|---|---|---|---|
auto(默认) | 自动检测首行为 header(基于<th>比例+字体加粗) | {"姓名": "张三", "部门": "算法组"} | 大部分标准表格,有明确<thead>或首行<th> |
first-row | 强制取第一行<td>文本为 key | {"姓名": "张三", "部门": "算法组"} | 老系统导出表,全用<td>但首行实为表头 |
index | 不生成 key,用数字索引 | [{"0": "张三", "1": "算法组"}] | 表头缺失或混乱,需后续人工映射 |
none | 完全禁用 header,输出纯二维数组 | [["张三","算法组"],["李四","测试组"]] | 需保留原始行列结构做矩阵运算 |
提示:
auto模式下若首行被误判为数据(如某行含colspan导致<th>数量突降),可临时切first-row模式兜底,比手动改 HTML 快得多。
2.3 内容清洗规则:为什么 JSON 里没有<br>和
转换时默认启用内容净化(--clean-content),它不是简单.replace('<br>', '\n'),而是按层级处理:
- 标签剥离:移除
<b>、<i>、<span>等仅影响样式的标签,保留其文本; - 换行标准化:
<br>、<p>、<div>内部换行统一转为\n,避免 JSON 字符串里出现不可见控制符; - 空白压缩:连续空格/制表符/换行符合并为单个空格,
替换为空格; - HTML 实体解码:
&→&,<→<,但保留<>在字符串中(不转义为<),确保 JSON 可读性。
这一层清洗让输出 JSON 直接可用于日志分析或 API 入参,不用再写第二遍re.sub(r'<[^>]+>', '', text)。
3. 快速上手:命令行、Node.js API、浏览器端三路实操
3.1 命令行直出 JSON:三步完成本地 HTML 文件转换
最轻量的用法是 CLI 模式,无需写代码,适合临时处理单文件:
# 安装(全局或项目内) npm install -g html-to-json # 转换单个 HTML 文件,输出 JSON 到控制台 html-to-json ./report.html # 指定 header 模式 + 启用内容清洗 + 输出到文件 html-to-json ./data.html --header-mode first-row --clean-content --output ./output.json # 批量转换目录下所有 .html 表格(注意:只处理含 <table> 的文件) html-to-json ./tables/*.html --header-mode auto --output-dir ./json-out/参数说明:
--header-mode:必选,决定键名生成逻辑(见 2.2 表);--clean-content:默认开启,关闭用--no-clean-content(调试时可关,看原始 HTML 结构);--output:指定输出文件路径,不加则打印到 stdout;--output-dir:批量模式下指定输出目录,文件名自动追加.json;--selector:指定 CSS 选择器(如#user-table),只转换匹配的<table>,避免页面多个表互相干扰。
注意:CLI 默认只处理第一个
<table>。若页面含多个独立表格,必须用--selector显式指定,否则后续表格被忽略——这是新手最常翻车的点。
3.2 Node.js API 集成:嵌入爬虫 pipeline 做流式清洗
当 HTML 来自axios请求或 Puppeteer 截图后,需在代码中调用:
const htmlToJson = require('html-to-json'); // 从字符串解析(推荐用于爬虫响应体) const htmlString = '<table><tr><th>城市</th><th>人口</th></tr><tr><td>北京</td><td>2189万</td></tr></table>'; const result = htmlToJson.convert(htmlString, { headerMode: 'auto', // 必填:header 推断模式 cleanContent: true, // 默认 true,清理 HTML 标签 selector: 'table', // 可选:指定 table 选择器 skipEmptyRows: true // 可选:跳过全空行(默认 true) }); console.log(result); // 输出: [{"城市":"北京","人口":"2189万"}]关键参数逻辑:
headerMode:同 CLI 的--header-mode,值为'auto' | 'first-row' | 'index' | 'none';cleanContent:布尔值,控制是否启用 2.3 节的内容清洗;selector:字符串,支持复杂选择器如'table:nth-of-type(2)'或'.data-table';skipEmptyRows:布尔值,设为false可保留空行(输出null对象),用于对齐原始行号。
血泪经验:Puppeteer 截取的 HTML 常含动态加载的
<script>注入内容,若htmlString里混有未执行 JS,<table>可能被包裹在<div id="app">下。此时务必用selector: '#app table',否则convert()找不到<table>直接返回空数组——别急着骂工具,先console.log(htmlString.slice(0,200))看前缀。
3.3 浏览器端使用:前端上传 HTML 文件实时预览 JSON
支持直接在浏览器运行(无 Node 环境),适合做内部工具页:
<!-- 引入 UMD 版本 --> <script src="https://unpkg.com/html-to-json@latest/dist/html-to-json.umd.js"></script> <script> document.getElementById('file-input').addEventListener('change', (e) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = function() { try { // 浏览器端调用 convert 方法 const json = htmlToJson.convert(reader.result, { headerMode: 'auto', cleanContent: true }); document.getElementById('output').textContent = JSON.stringify(json, null, 2); } catch (err) { console.error('转换失败:', err); alert('HTML 格式错误,请检查是否为完整 HTML 文档'); } }; reader.readAsText(file); }); </script>浏览器适配要点:
- 使用
html-to-json.umd.js(UMD 模块),非cjs或esm版本; convert()第一个参数必须是完整 HTML 字符串(含<!DOCTYPE html>),不能只传<table>...</table>片段;- 错误捕获必须显式
try/catch,否则解析失败会静默返回undefined; - 大文件(>5MB)可能触发浏览器内存警告,建议前端加
if (file.size > 5 * 1024 * 1024) { alert('文件过大'); return; }。
4. 避坑指南:五个真实翻车现场与自救方案
4.1 现象:JSON 输出为空数组[],但 HTML 明明有<table>
原因:html-to-json默认只解析<table>标签,且要求其为顶层标签。若<table>被包裹在<div class="container">或<section>内,而你没传selector参数,工具会因找不到直接子<table>而返回空。更隐蔽的是:某些 CMS 导出的 HTML 用<table class="wp-block-table">,但工具默认忽略 class,需显式指定selector: 'table'。
解决:强制指定selector: 'table'(即使只有一个表),或用selector: 'body > table'锁定直接子元素;若表在深层嵌套,用selector: '.data-table table'精准定位。
4.2 现象:表头识别错乱,第一行数据被当 key,生成{"张三": "算法组", "李四": "测试组"}
原因:auto模式依赖<th>标签和样式判断,但源 HTML 全用<td>+ CSS 加粗(如<td style="font-weight:bold">姓名</td>)。工具无法解析内联样式中的font-weight,导致 header 检测失败,退化为index模式,把第一行数据当 key。
解决:改用--header-mode first-row(CLI)或headerMode: 'first-row'(API),强制取首行文本为 key;或预处理 HTML,将加粗<td>批量替换为<th>:htmlString.replace(/<td([^>]*)style="[^"]*font-weight:[^"]*bold[^"]*"[^>]*>/gi, '<th$1>')。
4.3 现象:JSON 中字段值含大量\n和多余空格,Pandas 读取后列对不齐
原因:cleanContent默认开启,但它的换行处理逻辑是“保留<br>语义”,即<td>北京<br>上海</td>会转成"北京\n上海"。若原始表格用<br>分隔多值(如地址栏),这反而是正确行为;但若<br>是排版冗余(如<td>\n 北京 \n</td>),就会引入脏数据。
解决:关闭清洗--no-clean-content,然后用正则后处理:json.map(row => Object.fromEntries(Object.entries(row).map(([k,v]) => [k, v.replace(/\s+/g, ' ').trim()])));或启用--clean-content后加--strip-newlines(部分版本支持)。
4.4 现象:含colspan="2"的表头,后续数据列错位,JSON 键值对不上
原因:工具对colspan的对齐逻辑是“按<th>的colspan值扩展列宽”,但若<th colspan="2">城市</th><th>人口</th>后跟<td>北京</td><td>2189万</td>,它会认为“北京”应填入城市列,而2189万填入人口列——但实际 HTML 渲染中,“北京”占两列,2189万是第三列,导致错位。
解决:启用--strict-colspan模式(需 v2.3+),该模式会重建列索引映射表,确保colspan单元格占据的列范围与后续<td>严格对齐;若版本旧,手动在 HTML 中补全<td>占位:<tr><td>北京</td><td></td><td>2189万</td></tr>。
4.5 现象:中文表头转 JSON 后变成乱码(如"å§å": "å¼ ä¸")
原因:输入 HTML 文件保存编码非 UTF-8(如 GBK),而html-to-json默认按 UTF-8 解析。Node.js 读取文件时若未指定编码,会按系统默认编码(Windows 常为 GBK)读取,导致字节错乱。
解决:CLI 模式下用iconv-lite转码后再传入:
# 先转码为 UTF-8 iconv -f GBK -t UTF-8 ./report.html > ./report_utf8.html html-to-json ./report_utf8.htmlNode.js API 中,读取文件时显式指定编码:
const htmlString = fs.readFileSync('./report.html', 'utf8'); // 必须加 'utf8' const result = htmlToJson.convert(htmlString, { headerMode: 'auto' });5. 进阶技巧:处理复杂表格的四个实战策略
5.1 处理多级表头:用--header-depth拆解“大类-小类”结构
某些财务报表表头是两行:第一行收入|成本|利润,第二行Q1|Q2|Q3|Q4。默认html-to-json只取最后一行作 key,会丢失层级。此时启用--header-depth 2(CLI)或headerDepth: 2(API):
html-to-json ./finance.html --header-mode auto --header-depth 2输出变为嵌套对象:
[ { "收入": {"Q1": "120万", "Q2": "135万"}, "成本": {"Q1": "80万", "Q2": "85万"} } ]实现原理:工具会将前N行<tr>全部视为 header 行,生成二维 header 数组[['收入','成本'], ['Q1','Q2','Q3','Q4']],再用笛卡尔积组合键名(收入.Q1,成本.Q2)。若需自定义分隔符(如不用.而用_),可传--header-separator '_'。
5.2 合并重复行:用--merge-rows处理“主-子”嵌套表格
常见于订单详情页:第一行是订单号、日期,第二行起是商品列表,但商品行没有独立表头。传统做法要写两套解析逻辑。html-to-json支持--merge-rows将相邻行合并为一个对象:
<table> <tr><th>订单号</th><td>ORD-2023-001</td></tr> <tr><th>日期</th><td>2023-05-20</td></tr> <tr><th>商品</th><th>数量</th></tr> <tr><td>手机</td><td>1</td></tr> <tr><td>耳机</td><td>2</td></tr> </table>加参数--merge-rows --header-mode first-row后,输出:
[ { "订单号": "ORD-2023-001", "日期": "2023-05-20", "items": [ {"商品": "手机", "数量": "1"}, {"商品": "耳机", "数量": "2"} ] } ]触发条件:工具检测到某行<th>数量突增(如从 2→2 变为 2→3),且前几行是 key-value 对,则将前N行作为主对象,后续行作为子数组。N可通过--merge-rows-count 2指定。
5.3 自定义清洗函数:用--transform注入业务逻辑
当标准清洗不够用(如需把“2023年5月”转为"2023-05",或把“¥120.00”去符号),可用--transform传 JS 函数字符串:
html-to-json ./sales.html \ --transform "value => value.replace(/年|月/g, '-').replace(/¥/g, '')" \ --output ./clean.jsonNode.js 中:
const result = htmlToJson.convert(htmlString, { headerMode: 'auto', transform: (value, key, rowIndex, colIndex) => { if (key === '日期') return value.replace(/年|月/g, '-'); if (key === '金额') return parseFloat(value.replace(/¥|,/g, '')); return value; } });参数说明:transform函数接收(原始值, 字段名, 行索引, 列索引),返回清洗后值。注意:此函数在每个单元格调用,避免做重操作(如网络请求)。
5.4 验证转换结果:用--validate-schema防止字段缺失
上线前需确保 JSON 字段完整,比如用户表必须含id,name,email。可定义验证 schema:
// schema.json { "required": ["id", "name", "email"], "types": { "id": "number", "email": "email" } }html-to-json ./users.html \ --header-mode first-row \ --validate-schema ./schema.json \ --output ./validated.json若某行缺失email,工具抛出错误:ValidationError: Row 5 missing required field "email"。Node.js 中用validateSchema: require('./schema.json')即可。
从那以后我每次处理新一批 HTML 表格,都强制走一遍--header-mode auto && --clean-content && --validate-schema三连,再对比原始 HTML 手动抽样 3 行验证。宁可多花 2 分钟,也不愿上线后发现email字段全被 占满。希望帮到你。
本文还有配套的精品资源,点击获取