html_to_text.py

html_to_text.py — 底层 HTML 清洗

最底层工具,被多个上层模块调用:HTML 转纯文本,对 <table> 按行合并保留表格语义,清理 CSS/JS/隐藏元素/&nbsp; 等噪音。

代码

# -*-coding:utf-8-*-
# 功能:将HTML文本(含复杂表格)清洗转换为纯文本,去除标签、样式、脚本、注释等冗余内容
import re
from bs4 import BeautifulSoup, Comment

# 正则表达式:匹配需要在末尾添加换行符的HTML闭合标签,用于文本分段
brs_pattern = re.compile("</h\d+>|</div>|<br>|<br/>|</head>|</tr>|</li>|</title>|</dt>|</dd>|</ul>|</p>|</table>|</article>|</aside>|</details>|</figcaption>|</figure>|</footer>|</header>|</hgroup>|</menu>|</nav>|</section>|</blockquote>|</address>|</fieldset>|</caption>")

class Html2txt():
    """HTML转纯文本工具类"""

    def _html_text(self, _html):
        """
        私有方法:提取HTML中的纯文本,并替换换行符为空格
        :param _html: HTML字符串
        :return: 处理后的纯文本
        """
        # 解析HTML
        soup = BeautifulSoup(_html, "lxml")
        # 提取纯文本内容
        _text = soup.get_text()
        # 替换所有换行符为空格,去除首尾空白
        return re.sub(r"(\r\n)|\n", " ", _text).strip()

    def _table_text(self, table):
        """
        私有方法:专门处理HTML表格,规整表格内容为纯文本(处理单元格、行列对齐)
        :param table: BeautifulSoup的table节点对象
        :return: 处理后的表格纯文本
        """
        # 查找嵌套表格,有则直接提取文本返回
        _tables = table.find_all("table")
        if _tables:
            return table.get_text()

        # 获取所有行
        trs = table.find_all("tr")
        if trs:
            for tr in trs:
                # 获取当前行所有单元格
                tds = tr.find_all("td")
                new_tds = []
                if tds:
                    for td in tds:
                        # 格式化单元格HTML,去除多余换行
                        _html = td.prettify()
                        _html = re.sub(r"(\r\n+)|\n+", "", _html)

                        # 匹配标签并添加换行,用于分段
                        brs = brs_pattern.findall(_html)
                        for br in set(brs):
                            _html = _html.replace(br, br + "\n")

                        # 按换行分割处理
                        _brs = re.split(r"\n+", _html)
                        # 修正末尾闭合标签位置
                        if _brs[-1] == "</td>":
                            _brs.pop(-1)
                            _brs[-1] = _brs[-1] + "</td>"
                        # 过滤空内容并去除空白
                        _brs = [_br.strip() for _br in _brs if _br and _br.strip()]
                        # 再次修正闭合标签
                        if _brs[-1] == "</td>":
                            _brs.pop(-1)
                            _brs[-1] = _brs[-1] + "</td>"
                        new_tds.append(_brs)

                if new_tds:
                    # 计算单元格最大列数,用于对齐不规则表格
                    _len = [len(t) for t in new_tds]
                    _max = max(_len)
                    data = []
                    _ldiff = [_l for _l in _len if _max != _l]

                    if _ldiff:
                        # 补全短单元格,使行列对齐
                        re_tds = []
                        for t in new_tds:
                            diff_len = _max - len(t)
                            if diff_len:
                                for _ in range(0, diff_len):
                                    t.append("<td></td>")
                            re_tds.append(t)
                        # 行列转置并拼接
                        for i in [list(t) for t in zip(*re_tds)]:
                            data.append("".join(i))

                    elif _max > 1:
                        try:
                            # 正常行列转置
                            for i in [list(t) for t in zip(*new_tds)]:
                                data.append("".join(i))
                        except:
                            print("****", re_tds)
                    else:
                        # 单行数据直接拼接
                        data = [" ".join([k[0].strip() for k in new_tds if k[0]])]

                    # 提取每个单元格纯文本
                    data = [self._html_text(d) for d in data]
                    # 用处理后的文本替换原表格行
                    _re_str = "\n".join(data)
                    tr.replace_with(_re_str)
        # 返回最终表格文本
        return table.get_text()

    def clear_tables(self, s):
        """
        递归清理HTML中的所有表格,替换为规整文本
        :param s: HTML字符串
        :return: 清理表格后的文本
        """
        soup = BeautifulSoup(s, "lxml")
        # 移除HTML注释内容
        comments = soup.findAll(text=lambda text: isinstance(text, Comment))
        [comment.extract() for comment in comments]

        # 查找所有table标签
        tables = soup.findAll("table")
        _text = soup.get_text()

        if tables:
            for table in tables:
                # 跳过嵌套表格
                if table.findAll("table"):
                    continue
                # 处理单个表格
                _text = self._table_text(table)
                if _text:
                    # 用纯文本替换原table标签
                    table.replace_with(_text)
            # 递归处理剩余表格
            _text = soup.get_text()
            self.clear_tables(_text)
        return _text

    def clean_html(self, s):
        """
        对外核心方法:完整清洗HTML,转换为规范纯文本
        :param s: 原始HTML字符串
        :return: 最终纯文本
        """
        # 匹配标签样式(忽略大小写)
        style_filter = re.compile("(?is)<style[^>]*?>(.*?)")

        # 还原HTML转义字符
        s = re.sub("&lt;", "<", s)
        s = re.sub("&gt;", ">", s)
        s = re.sub("&amp;", "&", s)

        # 移除脚本内容
        s = re.sub("(?is)<script.*?", "", s)

        # 移除设置了隐藏样式(display:none)的HTML元素
        s = re.sub("(?is)<[^<]*?display\s*\:\s*none[^>]*?>[^<]*?</\w*?>", "", s)

        # 移除下拉选择框<select>
        s = re.sub("(?is)<select>.*?</select>", "", s)

        # 移除CSS样式内容
        s = style_filter.sub("", s)

        # 单元格标签后添加空格分隔
        s = re.sub(r"</td>", "</td> ", s)
        # 统一表格表头<th>为<td>
        s = re.sub("<th>", "<td>", s)
        s = re.sub(r"</th>", "</td> ", s)

        # 关键标签后添加换行,实现文本分段
        brs = brs_pattern.findall(s)
        for br in set(brs):
            s = s.replace(br, br + "\n")

        # 清理所有表格
        _text = self.clear_tables(s)

        # 移除IE条件注释
        _text = re.sub("(?is)<!\[if.*?\]>", "", _text)
        _text = re.sub("(?is)<!\[endif.*?\]>", "", _text)

        # 统一换行符为Windows格式 \r\n
        _text = re.sub(r"\n", "\r\n", _text)
        # 合并多余空格、制表符、换行符
        _text = re.sub(r"[ \f\v]{2,}", " ", _text)
        _text = re.sub(r"\t{2,}", "\t", _text)
        _text = re.sub(r"(\r\n){2,}", "\r\n", _text)
        _text = re.sub(r"\r\n\s+", "\r\n", _text)

        # 去除空白特殊字符 &nbsp;
        _text = re.sub(r"\xa0\xa0", "", _text)
        _text = re.sub(r"\r\n\xa0", "\r\n", _text)
        _text = re.sub(r"\xa0\xa0", "", _text)

        # 去除首尾空白并返回
        return _text.strip()

if __name__ == '__main__':
    # 测试:读取本地HTML文件并转换为纯文本
    filepath = r"/Users/ziguangchu/source/python/clawler_data/data/ardata/sch_00131/detail_e49385645df507c84d6fd149cc93cff1.html"
    with open(filepath, "r", encoding="utf-8", errors='ignore')as f1:
        s = f1.read()
    # 执行转换
    res = Html2txt().clean_html(s)
    print(res)

项目分区导航cjob_model ⬅️ | 06-html_to_text | ➡️ auto_gen