项目场景:
Python爬虫抓取复杂页面文字信息,如何处理保留表格格式?
问题描述
抓取页面文字时,遇到表格信息如果直接使用xpath
的text()
方法提取,会破坏文字原有的格式信息。
同时表格上下文还存在一些非表格类型的文字,需要保证文本在页面文章中的位置。
示例页面:
表格的HTML代码:
<table style="width: auto;">
<tbody>
<tr>
<td colspan="1" rowspan="1" width="86">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">一级类目</span>
</td>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">二级类目</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">三级类目名称</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="8" width="86">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">生鲜</span>
</td>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">海鲜水产</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">其他水产、海产礼盒、海参、鱼类、虾类、蟹类、贝类、海产干货、软足类、藻类、海鲜制品、海鲜卡券</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">面点烘焙</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">西式烘焙、水饺/馄饨、汤圆/元宵、面点、火锅丸串、速冻半成品、奶酪黄油、低温粽子、低温月饼、新鲜蛋糕、其他西点、蛋挞、披萨</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">禽肉蛋品</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">鸡肉、鸭肉、其他禽类、蛋类</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">肉禽蛋奶</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">牛排、牛羊肉、鸡鸭禽类</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">乳品冷饮</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">冷藏果蔬汁、冰激凌</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">蔬菜</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">叶菜类、茄果瓜类、根茎类、鲜菌菇、葱姜蒜椒、半加工蔬菜</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">熟食腊味</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">熟食、腊肠/腊肉、火腿、半成品、面食、糕点</span>
</td>
</tr>
<tr>
<td colspan="1" rowspan="1" width="115">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">水果</span>
</td>
<td colspan="1" rowspan="1" width="345">
<span style="color: rgb(0, 0, 0); font-size: 14px; font-family: 微软雅黑;">苹果、橙子、奇异果/猕猴桃、车厘子/樱桃、芒果、蓝莓、火龙果、葡萄/提子、柚子、香蕉、牛油果、梨、菠萝/凤梨、桔/橘、柠檬、草莓、桃/李/杏、更多水果、水果礼盒/券、瓜、国产水果、进口水果、蔬菜、榴莲、百香果、龙眼、椰青、石榴、荔枝、枣、山竹、木瓜、杨梅、枇杷、芭乐、菠萝蜜、莲雾、柿子</span>
</td>
</tr>
</tbody>
</table>
解决方案:
首先逐行对表格分析,存入Dataframe
中,用to_markdown()
将表格转为Markdown格式存入文本。
代码实现:
拿到整个页面的HTML文本后,先将<table>
标签内进行处理
from lxml import etree, html
import numpy as np
import pandas as pd
# 将<table>元素转为Markdown
def insert_markdown_table(html_content):
if html_content == "":
return ""
# html.fromstring()函数不允许传入空文本,因此前面需要判空
data = html.fromstring(html_content)
# 提取所有表格
tables = data.xpath('//table')
# 处理每个表格
for table in tables:
# 提取表头,如果没有表头设为空列表
headers = [th.text for th in table.xpath('.//thead/tr/th')] if table.xpath('.//thead/tr/th') else []
# 提取表格内容
rows = table.xpath('.//tbody/tr')
# 通过XPath获取包含HTML标签的文本内容
data1 = [[etree.tostring(td, with_tail=False, encoding='unicode') for td in row.xpath('.//th|td')] for row in
rows]
# 对于合并单元格的表格,不能按照td的标签数量直接计数,需要以colspan数为准
col_num = [[int(num.xpath('.//@colspan')[0]) if len(num.xpath('.//@colspan')) else 1 for num in row.xpath('.//th|td')] for row in rows]
row_num = [[int(num.xpath('.//@rowspan')[0]) if len(num.xpath('.//@rowspan')) else 1 for num in row.xpath('.//th|td')] for row in rows]
# 处理合并单元格,对合并的每个单元格直接填充
data1 = trans_struct(col_num, row_num, data1) # 单独编写一个函数
try:
# 创建Pandas DataFrame
df = pd.DataFrame(data1, columns=headers) # 表头与表body不匹配
except:
headers = data1[0] # 表body第一行作为表头
# print(headers)
try:
# print(data1[0])
# 创建Pandas DataFrame
df = pd.DataFrame(data1[1:], columns=headers) # 第一行仍与后面不匹配
except:
max = 0
for xm in data1:
if len(xm) > max:
max = len(xm)
headers = [i for i in range(max)] # 直接以编号作为表头
# 创建Pandas DataFrame
df = pd.DataFrame(data1, columns=headers)
# 将DataFrame转为Markdown
markdown_table = df.to_markdown(index=False)
# print(markdown_table)
# 将Markdown插入原来的位置,并用一个标签包含(防止后面text()取不到)
table.getparent().replace(table,
html.fromstring(f'<div>\'\'\'\n</div><div>{markdown_table}</div><div>\n\'\'\'</div>'))
# 将修改后的HTML内容转为字符串
modified_html = etree.tostring(data, encoding='unicode')
return modified_html
# 处理合并单元格结构
def trans_struct(col_list, row_list, data):
code_num = max([sum(i) for i in col_list]) # 统计每行列数和的最大值
# 创建temp二维列表,大小为每行列数和的最大值*行数
temp = [['<pad>' for i in range(code_num)] for i in range(len(data))] # 用<pad>填充
# 开始遍历处理原表格
for i in range(len(data)):
for j in range(len(data[i])):
# 读取原始表格i行j列的colspan和rowspan数
col_num, row_num = col_list[i][j], row_list[i][j]
for i_ in range(i, i + row_num): # 往下填充
for j_ in range(j, j + col_num): # 往右填充
# print(data[i][j])
print("changed")
# 每行遇到'<pad>'再填充
# 保证出现连续的两个以上合并单元格且原始起始位置小于实际位置的情况下
# 不会覆盖前面的单元格
for j__ in range(j_,len(temp[i_])):
if temp[i_][j__] != '<pad>':
continue
else:
temp[i_][j__] = data[i][j]
break
return temp
html_code = "你的HTML文本"
data = insert_markdown_table(html_code)
data = etree.HTML(data)
text = ''.join(data.xpath('//text()'))
print(text)
对原始的HTML文本处理后,再获取文本信息后表格就转为Markdown了
示例效果:
......
本规则适用于在京东平台(JD.com)经营生鲜类目的SOP、SOPL商家,具体类目如下。'''
| 一级类目 | 二级类目 | 三级类目名称 |
|:-----------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 生鲜 | 海鲜水产 | 其他水产、海产礼盒、海参、鱼类、虾类、蟹类、贝类、海产干货、软足类、藻类、海鲜制品、海鲜卡券 |
| 生鲜 | 面点烘焙 | 西式烘焙、水饺/馄饨、汤圆/元宵、面点、火锅丸串、速冻半成品、奶酪黄油、低温粽子、低温月饼、新鲜蛋糕、其他西点、蛋挞、披萨 |
| 生鲜 | 禽肉蛋品 | 鸡肉、鸭肉、其他禽类、蛋类 |
| 生鲜 | 肉禽蛋奶 | 牛排、牛羊肉、鸡鸭禽类 |
| 生鲜 | 乳品冷饮 | 冷藏果蔬汁、冰激凌 |
| 生鲜 | 蔬菜 | 叶菜类、茄果瓜类、根茎类、鲜菌菇、葱姜蒜椒、半加工蔬菜 |
| 生鲜 | 熟食腊味 | 熟食、腊肠/腊肉、火腿、半成品、面食、糕点 |
| 生鲜 | 水果 | 苹果、橙子、奇异果/猕猴桃、车厘子/樱桃、芒果、蓝莓、火龙果、葡萄/提子、柚子、香蕉、牛油果、梨、菠萝/凤梨、桔/橘、柠檬、草莓、桃/李/杏、更多水果、水果礼盒/券、瓜、国产水果、进口水果、蔬菜、榴莲、百香果、龙眼、椰青、石榴、荔枝、枣、山竹、木瓜、杨梅、枇杷、芭乐、菠萝蜜、莲雾、柿子 |
'''
1.4保险费计算规则
......