核心问题:直接提取全部文本、去掉所有空格做字符串比对,是否合理?
结论:不完全合理,有坑,但可以改造后可用。
为什么直接“全部提取文本+删掉全部空格换行”有问题
- 无关文本干扰:广告、侧边栏、时间戳、访问统计、页面弹窗、js动态渲染提示、底部版权时间,每次刷新会变,实际正文没有变,比对会误判为变更。
- 标签内属性文本混入(meta、script、style内文字),这些不属于页面可见正文。
- 空格语义被完全抹除:正常段落内部空格、换行全部删除,虽然适合简单比对,但丢失原文;另外html里
、\t、\r多种空白字符需要统一处理。 - dom顺序微调:网页开发者微调标签嵌套、增加class/id属性,可见文本完全没变,但html源码不一样;如果你只关心人看到的内容,应该对比渲染后的可见文本,而不是原始html源码。
适用场景:适合简单静态页面、正文占绝大部分、噪声少的场景;不适合新闻门户、带广告、侧边组件频繁改动的网页。
提取html可见纯文本(使用beautifulsoup)
关键点:
- 移除
<script> <style> <noscript>标签,不要把js、css代码当成文本。 - 提取用户肉眼可见文本。
- 空白字符归一化:不是粗暴删除所有空格,两种策略:
- 策略a(用于比对):把所有空白(换行、制表、多个空格、
)压缩成单个空格; - 策略b(你想要):全部删除所有空白字符,得到一整串连续字符串用于哈希比对。
- 策略a(用于比对):把所有空白(换行、制表、多个空格、
- 对文本做哈希(md5)保存到数据库,下次抓取直接对比哈希,不用存大段文本。
完整示例代码
from bs4 import beautifulsoup
import re
import hashlib
def extract_visible_text(html: str, remove_all_whitespace: bool = false) -> str:
"""
提取html可见文本,剔除script/style等标签内容
:param html: html原始字符串
:param remove_all_whitespace: true=删除全部空白;false=多个空白压缩成单个空格
:return: 清洗后的纯文本
"""
soup = beautifulsoup(html, "html.parser")
# 删除脚本、样式标签,不要它们的文本
for bad_tag in soup(["script", "style", "noscript"]):
bad_tag.decompose()
# 获取全部可见文本
raw_text = soup.get_text(separator=" ")
# 替换所有空白字符:换行\r\n、制表\t、nbsp、多个空格
# \s 匹配:空格、制表符、换行、回车
if remove_all_whitespace:
# 全部删掉所有空白字符,得到连续字符串
clean_text = re.sub(r"\s+", "", raw_text)
else:
# 多个空白压缩为1个空格,保留语义
clean_text = re.sub(r"\s+", " ", raw_text).strip()
return clean_text
def calc_text_hash(text: str) -> str:
"""计算md5哈希,用于快速比对,存入数据库"""
return hashlib.md5(text.encode("utf-8")).hexdigest()
# ---------------- 使用示例 ----------------
if __name__ == "__main__":
html1 = """
<html>
<body>
<div>你好 世界!</div>
<script>console.log(123)</script>
<p>第一行<br>第二行</p>
</body>
</html>
"""
text_all_remove = extract_visible_text(html1, remove_all_whitespace=true)
print(f"删除全部空白文本:{text_all_remove}")
# 输出:你好世界!第一行第二行
text_compress = extract_visible_text(html1, remove_all_whitespace=false)
print(f"空白压缩为单个空格:{text_compress}")
# 输出:你好 世界! 第一行 第二行
hash_val = calc_text_hash(text_all_remove)
print(f"文本md5:{hash_val}")
# 比对逻辑:数据库存上次的md5
old_hash = "xxx"
if hash_val != old_hash:
print("页面内容发生变化")
else:
print("页面没有变化")
两种比对思路对比
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 比对原始html源码 | 直接对比requests拿到的response.text | 最简单 | css class、注释、换行、js随机变量微小改动就判定变更,大量误报 |
| 提取可见文本,删除全部空格后比对 | re.sub(r"\s+","",text) 后对比字符串/md5 | 字符串短,比对极快;不关心排版换行 | 丢失段落语义;页面插入一个空格就不变,但是文字顺序改动可以识别;广告噪声会带来误判 |
| 提取可见文本,空白压缩为单个空格比对 | 多个空白压缩为一个空格 | 保留基础语义,更贴近阅读体验 | 字符串略长,比对速度略低 |
建议:数据库存储清洗后文本的md5哈希值,不要每次存整段大文本,节省存储空间。
重要缺陷与优化建议(解决误报)
问题1:页面存在噪声(时间、广告、访问计数)
比如网页底部:更新时间:2026‑08‑05,每次抓取时间变化,正文不变,会判定页面改动。
优化:
如果页面有固定选择器(比如正文是div.content),不要抓取整个页面的全部文本!只提取正文区块。
# 只提取正文部分,不要整个网页
content_div = soup.select_one("div.content")
if content_div:
raw_text = content_div.get_text(separator=" ")
这是最关键优化!不要拿整个网页,优先定位正文dom节点。
问题2:js动态渲染页面
requests只能拿到初始静态html,如果内容是js渲染出来,beautifulsoup提取不到。
解决:使用drissionpage / playwright获取渲染完成后的html。
问题3:极小改动(标点、一个字改动)
md5可以精准识别任意字符改动;如果业务希望容忍微小改动,可以用文本相似度算法(difflib、simhash),而不是严格相等。
- md5:严格完全相等才判定无变化;只要一个字符不同,哈希完全不同;适合要求精准比对。
- simhash:适合新闻网页,允许少量文字改动,判断是否“大体内容没变”。
回到你的业务,什么场景适合“提取全部文本去空格比对”
适合场景:
- 静态页面,正文是主体,广告/噪声很少;
- 只关心文字内容,不在乎排版、换行;
- 需要快速判断页面是否改动。
不适合场景:
- 网页有动态时间戳、广告、侧边栏、计数器;
- 需要区分排版,空格换行有业务意义;
- js渲染为主的网页。
知识扩展
从 html 中提取纯文本是数据清洗和网页解析中非常常见的需求。python 中有几种高效的方式可以实现,下面我会按照从最推荐到最备选的顺序,逐一给出完整的代码示例和适用场景分析。
使用 beautifulsoup(最推荐)
beautifulsoup 是处理混乱 html 的首选库,它的 .get_text() 方法能轻松提取所有文本,且支持通过参数控制分隔符和空白处理。
from bs4 import beautifulsoup
def extract_text_bs4(html: str) -> str:
"""
使用 beautifulsoup 提取纯文本
"""
soup = beautifulsoup(html, 'html.parser')
# get_text() 默认用空格连接多个块,strip=true 去除首尾空白
text = soup.get_text(separator=' ', strip=true)
# 可选:将多个空格合并为单个
text = ' '.join(text.split())
return text
# 示例
html_content = "<html><body><h1>标题</h1><p>这是 <b>粗体</b> 内容。</p></body></html>"
print(extract_text_bs4(html_content))
# 输出: "标题 这是 粗体 内容。"- 优点:容错性强,能处理不规范的 html,api 直观。
- 缺点:速度相对 lxml 稍慢(但通常可忽略)。
如果需要保留换行结构,可以这样调整:
text = soup.get_text(separator='\n', strip=true)
使用 lxml(高性能)
lxml 是基于 c 库的高性能解析器,适合处理大量文档。它同样提供 .text_content() 方法提取所有文本。
from lxml import html
def extract_text_lxml(html_content: str) -> str:
"""
使用 lxml 提取纯文本
"""
tree = html.fromstring(html_content)
# text_content() 会获取所有后代文本,并保留相对顺序
text = tree.text_content()
# 清理多余空白
text = ' '.join(text.split())
return text
# 示例
print(extract_text_lxml(html_content))
# 输出: "标题 这是 粗体 内容。"优点:解析速度极快,适合大批量处理。
缺点:对非常不规范的 html 容忍度略低于 beautifulsoup,但通常够用。
使用 html2text(保留 markdown 结构)
如果你不仅想提取纯文本,还想保留一定的层级结构(如标题、列表),html2text 是一个很好的选择——它先将 html 转换为 markdown,然后你可选择性地去除 markdown 标记或直接使用。
import html2text
def extract_text_html2text(html_content: str) -> str:
"""
使用 html2text 转换为 markdown,再提取纯文本(可选)
"""
h = html2text.html2text()
h.ignore_links = true # 忽略链接
h.ignore_images = true # 忽略图片
h.ignore_emphasis = true # 忽略粗体/斜体标记
markdown = h.handle(html_content)
# 如果需要纯文本,可以进一步清理 markdown 符号,但通常 markdown 已有较好可读性
return markdown
print(extract_text_html2text(html_content))
# 输出可能为:
# 标题
# 这是 **粗体** 内容。如果你要最终纯文本,可以再用正则或简单方法移除 markdown 标记,但大部分场景下直接使用 markdown 文本就已满足需求。
使用正则表达式(不推荐,仅限极简场景)
对于极其简单的 html(无嵌套、无属性),可以用正则暴力提取,但强烈不推荐用于生产环境,因为复杂 html 极易出错。
import re
def extract_text_regex(html: str) -> str:
# 移除所有标签
text = re.sub(r'<[^>]+>', ' ', html)
# 合并空白
text = ' '.join(text.split())
return text进阶清理:移除脚本、样式和多余空白
真实网页常包含 <script>、<style> 等无意义内容,可以在提取前移除它们。
以 beautifulsoup 为例:
from bs4 import beautifulsoup
def extract_clean_text(html: str) -> str:
soup = beautifulsoup(html, 'html.parser')
# 移除 script 和 style 标签
for tag in soup(['script', 'style']):
tag.decompose()
# 提取文本
text = soup.get_text(separator=' ', strip=true)
return ' '.join(text.split())推荐最终工作流(你的爬虫)
- 请求拿到html;
- 优先选择正文dom节点,而不是整个页面;
- 提取可见文本,过滤script/style;
- 将空白全部归一化(压缩或者删除);
- 计算md5哈希;
- 和数据库中历史md5对比:
- 哈希一致 → 页面无变化,跳过存储;
- 哈希不一致 → 内容更新,保存新文本+新md5入库。
如果无法定位正文dom,只能抓取全页文本,你需要做好心理准备:会出现少量“误报变更”,建议日志记录,定期排查。
到此这篇关于python代码提取html纯文本并删掉全部空格的文章就介绍到这了,更多相关python提取html纯文本内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论