"""HTML 文件解析器,使用 bs4 + html2text 将 HTML 转换为 Markdown"""
import os
from typing import List, Optional
from bs4 import BeautifulSoup
from charset_normalizer import detect
import html2text
from exceptions import ParseError
from parsers.base import BaseParser
class HtmlParser(BaseParser):
"""HTML 文件解析器,去除脚本和样式,转换为 Markdown 格式"""
def supported_extensions(self) -> List[str]:
return [".html", ".htm"]
def parse(self, file_path: str) -> str:
"""
解析 HTML 文件,自动检测编码,去除 script/style 标签,转换为 Markdown。
参考 MaxKB HTMLSplitHandle.get_content() 核心逻辑:
读取文件字节 → 检测编码(优先 meta charset,回退 charset_normalizer)
→ 解码 → html2text 转 Markdown
Args:
file_path: 文件路径
Returns:
Markdown 格式的文本内容
Raises:
ParseError: 文件无法读取或编码检测失败时抛出
"""
file_name = os.path.basename(file_path)
try:
with open(file_path, "rb") as f:
buffer = f.read()
except Exception as e:
raise ParseError(file_name, f"文件读取失败: {e}")
if len(buffer) == 0:
return ""
encoding = self._get_encoding(buffer, file_name)
try:
content = buffer.decode(encoding)
except Exception as e:
raise ParseError(file_name, f"编码解码失败 ({encoding}): {e}")
converter = html2text.HTML2Text()
converter.body_width = 0 # Don't wrap lines
converter.ignore_images = False
converter.ignore_links = False
return converter.handle(content)
@staticmethod
def _get_encoding(buffer: bytes, file_name: str) -> str:
"""
检测 HTML 文件编码。
优先从 HTML meta charset 标签获取编码,回退到 charset_normalizer 自动检测。
Args:
buffer: 文件字节内容
file_name: 文件名(用于错误信息)
Returns:
检测到的编码名称
Raises:
ParseError: 编码无法检测时抛出
"""
# First try: extract charset from meta tags
try:
soup = BeautifulSoup(buffer, "html.parser")
meta_list = soup.find_all("meta")
for meta in meta_list:
if meta.attrs and "charset" in meta.attrs:
return meta.attrs["charset"]
except Exception:
pass
# Fallback: charset_normalizer
result = detect(buffer)
encoding = result.get("encoding") if result else None
if encoding is None:
raise ParseError(file_name, "无法检测文件编码")
return encoding