HTML内容修剪后标签未闭合致结构破损问题及代码修复需求
修复HTML修剪时的结构损坏问题
问题现状
修剪HTML内容后,因标签未正确闭合导致结构损坏,渲染时标签直接可见,示例:
< body> < able> [if mso]> < d> <![endif] [if mso]> < r> < able> <![endif] < d> < r> < body> < able> Viac na Právne noviny < d> < r> < body> < able> < d> < r> < body> < able>
...[trimmed]
原代码仅简单遍历元素并截断文本,未处理未闭合标签,导致HTML结构破坏。
优化后的代码
from bs4 import BeautifulSoup, Tag, NavigableString def trim_html(html: str) -> str: max_length = 70000 notice = "...[trimmed]" notice_length = len(notice) # 预处理空白字符,判断是否无需修剪 html = html.replace('\n', '').replace('\t', ' ').replace('\r', '') if len(html) <= max_length: return html soup = BeautifulSoup(html, 'html.parser') output_soup = BeautifulSoup('', 'html.parser') current_parent = output_soup # 当前输出的父节点指针 open_tags = [] # 跟踪未闭合的标签栈 current_length = 0 # 累计已使用的字符长度(含标签和文本) def traverse(element): nonlocal current_length, current_parent # 处理标签节点 if isinstance(element, Tag): # 计算起始标签的字符长度 tag_start = f"<{element.name}" if element.attrs: for key, value in element.attrs.items(): tag_start += f' {key}="{value}"' tag_start += ">" tag_start_len = len(tag_start) # 检查剩余空间是否足够放下起始标签 available = max_length - current_length - notice_length if available <= tag_start_len: return False # 创建新标签并挂载到当前父节点 new_tag = output_soup.new_tag(element.name, attrs=element.attrs) current_parent.append(new_tag) # 更新长度、标签栈和当前父节点 current_length += tag_start_len open_tags.append(new_tag) current_parent = new_tag # 处理文本节点 elif isinstance(element, NavigableString): available = max_length - current_length - notice_length if available <= 0: return False # 截取可容纳的文本 text_to_add = element[:available] current_parent.append(text_to_add) current_length += len(text_to_add) # 文本被截断则终止遍历 if len(text_to_add) < len(element): return False # 递归遍历子元素 for child in element.children: if not traverse(child): return False # 处理标签闭合 if isinstance(element, Tag): tag_end = f"</{element.name}>" tag_end_len = len(tag_end) # 检查剩余空间是否允许添加闭合标签 if current_length + tag_end_len + notice_length <= max_length: current_length += tag_end_len # 回退父节点并弹出标签栈 open_tags.pop() current_parent = open_tags[-1] if open_tags else output_soup else: # 空间不足则放弃闭合当前标签,直接回退 open_tags.pop() current_parent = open_tags[-1] if open_tags else output_soup return False return True # 开始遍历原HTML的根节点子元素 for child in soup.children: if not traverse(child): break # 闭合所有剩余未闭合的标签,确保结构完整 while open_tags: tag = open_tags.pop() current_parent = open_tags[-1] if open_tags else output_soup tag_end = f"</{tag.name}>" # 优先保证结构完整,若剩余空间允许则添加闭合标签 if current_length + len(tag_end) + notice_length <= max_length: current_length += len(tag_end) current_parent.append(tag_end) # 添加修剪提示 current_parent.append(notice) return str(output_soup)
核心优化点
- 采用深度优先遍历+标签栈,跟踪未闭合标签,截断后自动逐层闭合,保证HTML结构完整。
- 计算总长度时同时包含标签本身的字符长度和文本内容长度,避免总长度超限。
- 优先保障结构完整性,即使剩余空间仅够闭合标签,也会完成闭合操作。
内容的提问来源于stack exchange,提问作者SUNIL KUMAR
相关产品推荐
相关产品推荐

