前言
做爬虫抓取网页的时候经常会遇到一类页面:网页并不是传统的<table>表格,而是用Bootstrap的row+col-md‑ div布局模拟出来的表格*。
浏览器打开网页依靠Bootstrap CSS样式,div可以整齐排列成表格模样。但是当我们把HTML片段扒取保存下来之后,问题就来了:
- 保存到本地的HTML片段,脱离原网站环境,拿不到Bootstrap样式;
- 内网服务器环境,无法访问外网CDN加载css;
- 就算正则把
visible-md这类响应式class删掉,没有栅格样式,所有div全部垂直堆叠挤在一起,完全看不出表格结构。
核心痛点:div栅格表格强依赖外部CSS,爬虫归档、数据预览场景非常不友好。
最优方案:在Python里把div伪表格解析,重新生成原生HTML
<table>。原生table自带边框布局,不依赖任何外部样式,断网、内网打开都能正常查看。
原始页面结构长什么样
网页源码类似下面这种结构:
<!--表头 -->
<div class="row structure">
<div class="boxs">
<div class="col-md‑1">序号</div>
<div class="col-md‑2">标准编号</div>
<div class="col-md‑2">项目名称</div>
</div>
</div>
<!--数据行 -->
<div class="row">
<div class="structurewords">
<div class="col-md‑1"><span>1</span></div>
<div class="col-md‑2">GB 50017‑2003</div>
<div class="col-md‑2">钢结构设计规范</div>
</div>
</div>
没有<table>、<tr>、<td>标签,全部靠div+CSS实现表格效果。
实现思路
- 使用
lxml.etree解析抓取到的HTML字符串; - XPath定位表头节点:
row structure下面的.boxs,提取内部div文本作为<th>表头; - XPath定位所有数据行节点:
div.structurewords,每一个对应表格一行<tr>; - 遍历行内子div,提取文本作为
<td>单元格; - 程序动态构建完整table DOM,设置边框、合并单元格样式;
- 序列化为完整HTML文档返回,输出可以直接保存打开。
重要:不要使用正则表达式解析HTML!HTML不是规则文本,正则处理嵌套、转义、异常标签很容易出错,优先使用lxml DOM解析。
完整Python代码实现
入参是爬虫拿到的HTML字符串片段,返回完整的table页面字符串。
from lxml import etree
def convert_div_to_table_html(content_html: str) -> str:
"""
将bootstrap div栅格伪表格,转换为原生table完整html
:param content_html: 抓取得到的div片段html字符串
:return: 完整可直接打开的html文档字符串,原生table,无外部css依赖
"""
tree = etree.HTML(content_html)
# 构建原生table,设置基础表格样式
table = etree.Element("table")
table.attrib["border"] = "1"
table.attrib["cellpadding"] = "6"
table.attrib["cellspacing"] = "0"
table.attrib["style"] = "border‑collapse:collapse;width:100%;"
thead = etree.SubElement(table, "thead")
tbody = etree.SubElement(table, "tbody")
# 解析表头
header_box = tree.xpath('//div[contains(@class,"row") and contains(@class,"structure")]//div[@class="boxs"]')
if header_box:
tr_head = etree.SubElement(thead, "tr")
for col_div in header_box[0].xpath("./div"):
text = "".join(col_div.xpath(".//text()")).strip()
th = etree.SubElement(tr_head, "th")
th.text = text
# 解析全部数据行 structurewords
row_nodes = tree.xpath('//div[@class="structurewords"]')
for row_div in row_nodes:
tr = etree.SubElement(tbody, "tr")
for cell_div in row_div.xpath("./div"):
cell_text = "".join(cell_div.xpath(".//text()")).strip()
td = etree.SubElement(tr, "td")
td.text = cell_text
table_str = etree.tostring(table, method="html", encoding="utf‑8").decode("utf‑8")
full_html = f'''<!DOCTYPE html>
<html lang="zh‑CN">
<head>
<meta charset="UTF‑8">
<title>表格预览</title>
</head>
<body>
{table_str}
</body>
</html>
'''
return full_html
# 测试示例
if __name__ == "__main__":
# 这里替换成你爬虫获取的div html片段
raw_div_html = """你的div片段html"""
result_html = convert_div_to_table_html(raw_div_html)
with open("table_output.html", "w", encoding="utf‑8") as f:
f.write(result_html)
爬虫项目中的实际调用场景
爬虫中,我们抓取多页数据,循环拼接每一页div html字符串,再调用转换函数:
# 循环拼接多页div片段
content_html = ""
for tag in content_list:
temp = etree.tostring(tag, method="html", encoding="utf‑8").decode("utf‑8")
content_html += temp
# 把div字符串转为原生table完整html
content_html = convert_div_to_table_html(content_html)
# 存入item入库
item["content_html"] = content_html
item["html"] = content_html
踩坑总结
- ❌ 不要仅删除
visible‑md / hidden‑sm这类class:只是移除显示隐藏标记,依旧依赖Bootstrap栅格CSS,内网/断网环境直接布局错乱; - ❌ 不要使用正则解析HTML DOM,正则无法处理标签嵌套、属性顺序变化;
- ✅ lxml DOM解析是可靠方案,直接解析语义节点,不依赖class样式;
- ✅ 输出原生table之后,零外部样式依赖,浏览器本地双击直接打开就能看到带边框整齐表格;
- 注意:提取文本使用
"".join(node.xpath('.//text()')),可以把span嵌套里面的文本完整取出。
适用与不适用场景
✅ 适合场景:
- 爬虫网页归档存储,内网环境预览表格;
- 原始页面使用Bootstrap div模拟表格;
- 希望输出离线可用的表格HTML。
❌ 不适合场景:
- 页面是真正原生
<table>表格; - div布局结构和示例差异很大(需要调整xpath表达式适配页面)。
小结
很多网站为了移动端适配,大量使用div栅格代替传统table。爬虫直接保存div片段会遇到样式丢失的大坑。
处理思路不要停留在修改class、引入CDN样式,从DOM层面重新构建原生table才是治本的办法,生成的结果兼容性最好,非常适合爬虫系统入库归档。