4. 文本数据清洗(上):去除HTML标签、特殊字符与大小写转换
文本数据清洗,说白了就是把那些乱七八糟的「噪音」从文本里揪出来扔掉。我做了这么多年数据工程,处理过的文本数据少说也有几十TB,最深的体会就是:干净的数据长得都差不多,脏的数据各有各的脏法。
今天咱们先搞定四个最基础的清洗操作。别看它们简单,实战中80%的文本脏数据都能靠这几招解决。
本章核心知识点一览:
- 用 BeautifulSoup 精准剥离 HTML 标签
- 正则表达式清理特殊字符与标点
- 大小写统一转换的策略选择
- 空白字符的「斩草除根」
4.1 去除HTML标签:BeautifulSoup 的正确打开方式
从网页爬下来的数据,十有八九都带着一堆 HTML 标签。<div>、<p>、<a href="...">…… 这些标签对机器解析来说是结构,但对文本分析来说就是噪音。
我见过不少新手直接用正则表达式去匹配标签,结果要么漏掉嵌套标签,要么把标签里的属性值也切坏了。其实 Python 生态里已经有现成的轮子——BeautifulSoup,专门干这个的。
我的习惯:只要涉及 HTML 解析,我第一反应就是上 BeautifulSoup。它底层用的是 lxml 或 html.parser,对标签结构的理解比正则表达式靠谱得多。
基本用法很简单:
from bs4 import BeautifulSoup
html_text = "<p>Hello, <b>World</b>!</p>"
soup = BeautifulSoup(html_text, 'html.parser')
clean_text = soup.get_text()
print(clean_text) # 输出: Hello, World!
你看,get_text() 方法直接把所有标签剥干净,只留下纯文本。不过这里有个坑——默认会把所有标签里的文本拼在一起,中间不留空格。
比如:
html_text = "<li>苹果</li><li>香蕉</li><li>橘子</li>"
soup = BeautifulSoup(html_text, 'html.parser')
print(soup.get_text()) # 输出: 苹果香蕉橘子
嗯,这显然不是我们想要的。解决办法是加个分隔符参数:
print(soup.get_text(separator=' ')) # 输出: 苹果 香蕉 橘子
我曾经踩过的坑:有一次处理一个新闻网站的 RSS 数据,直接用 get_text() 没加 separator,结果所有段落连在一起,分词模型直接崩了。后来排查了半天才发现是这里的问题。所以我的建议是——不管用不用得上,先加上 separator=' ',养成习惯。
另外,如果你只想提取某个特定标签里的内容,可以用 find() 或 find_all() 先定位:
soup = BeautifulSoup(html_doc, 'html.parser')
# 只提取所有 <p> 标签里的文本
paragraphs = [p.get_text(strip=True) for p in soup.find_all('p')]
strip=True 这个参数也很实用,它会自动去掉每个标签文本前后的空白字符。
4.2 去除特殊字符与标点:正则表达式是王道
HTML 标签去掉了,但文本里可能还残留着各种「妖魔鬼怪」——@、#、$、%、^、&…… 还有各种全角半角标点。这些符号对自然语言处理来说基本都是噪音。
我个人习惯用 Python 的 re 模块来处理。正则表达式虽然看起来像「天书」,但常用的就那么几个模式。
最通用的做法是保留字母、数字和空格,其他全干掉:
import re
text = "Hello, World! @2024 #数据清洗 $100%"
clean_text = re.sub(r'[^\w\s]', '', text)
print(clean_text) # 输出: Hello World 2024 数据清洗 100
这里 \w 匹配字母、数字和下划线,\s 匹配空白字符。[^\w\s] 的意思就是「既不是字母数字也不是空白的字符」,统统替换成空字符串。
但有时候你想保留某些符号,比如句号或连字符。那就把要保留的符号加到排除列表里:
# 保留句号、逗号和连字符
clean_text = re.sub(r'[^\w\s\.\,\-]', '', text)
一个小技巧:中文文本里经常混着全角标点,比如中文逗号「,」、中文句号「。」。你可以先用 str.translate() 把全角转半角,再用正则清理。这样一步到位。
全角转半角的代码片段:
def fullwidth_to_halfwidth(text):
result = []
for char in text:
code = ord(char)
if 0xFF01 <= code <= 0xFF5E: # 全角字符范围
result.append(chr(code - 0xFEE0))
elif code == 0x3000: # 全角空格
result.append(' ')
else:
result.append(char)
return ''.join(result)
你想想看,如果不清除这些特殊字符,后面做词频统计的时候,「hello」和「hello!」会被当成两个不同的词,结果就偏了。
4.3 大小写转换:统一才是硬道理
英文文本里,「Python」、「python」、「PYTHON」在语义上是一回事,但在计算机眼里它们是三个不同的字符串。所以,统一大小写是文本清洗里必不可少的一步。
Python 自带的方法就够用:
| 方法 | 作用 | 示例 |
|---|---|---|
str.lower() |
全部转小写 | "Hello" → "hello" |
str.upper() |
全部转大写 | "Hello" → "HELLO" |
str.capitalize() |
首字母大写,其余小写 | "hello world" → "Hello world" |
str.title() |
每个单词首字母大写 | "hello world" → "Hello World" |
我处理英文语料时,90% 的情况都用 lower()。为什么?因为小写是最「安全」的归一化方式,不会丢失信息,而且后续做词嵌入或 TF-IDF 时,小写形式更容易匹配。
注意:有些专有名词转小写后会丢失含义。比如「US」(美国)转成「us」(我们),意思完全变了。如果你处理的文本里专有名词很多,建议先做实体识别,把实体保护起来再转小写。
中文文本一般不需要大小写转换,但如果你处理的是中英混合文本,建议只对英文部分做转换。可以用正则把英文部分摘出来处理:
import re
def normalize_case(text):
# 只对英文字母部分转小写
return re.sub(r'[a-zA-Z]+', lambda m: m.group(0).lower(), text)
4.4 去除空白字符:别小看这些「隐形杀手」
空白字符包括空格、制表符 \t、换行符 \n、回车符 \r 等。它们肉眼看不见,但经常在数据里捣乱。
最常见的场景:从 CSV 或 Excel 里读出来的字段,前后可能带着空格。比如 " Python " 和 "Python",你看着差不多,但 == 比较返回 False。
Python 自带的 strip() 方法可以去掉字符串首尾的空白:
text = " Hello, World! \n"
print(text.strip()) # 输出: "Hello, World!"
但有时候空白字符藏在字符串中间,比如 "Hello World"(多个连续空格)。这时候 strip() 就无能为力了,得用正则:
text = "Hello World Python"
# 把连续多个空白字符替换成单个空格
clean_text = re.sub(r'\s+', ' ', text)
print(clean_text) # 输出: "Hello World Python"
我的实战经验:处理从 PDF 或 OCR 识别出来的文本时,经常遇到各种奇怪的空白字符,比如不换行空格(\xa0)、零宽空格(\u200b)。这些用 \s 匹配不到,得单独处理:
text = text.replace('\xa0', ' ').replace('\u200b', '')
如果你想把所有空白字符全部删掉(包括单词之间的空格),可以用:
text = "Hello World Python"
no_space = re.sub(r'\s+', '', text)
print(no_space) # 输出: "HelloWorldPython"
但说实话,我很少这么干。除非是做某些特殊的字符级模型,否则保留单词之间的空格对后续分词更友好。
4.5 组合起来:一个完整的清洗函数
好了,四个步骤都讲完了。咱们把它们串成一个完整的清洗函数:
import re
from bs4 import BeautifulSoup
def clean_text_pipeline(text, remove_html=True, remove_punct=True,
to_lower=True, strip_whitespace=True):
"""
文本清洗流水线
"""
# 1. 去除HTML标签
if remove_html:
soup = BeautifulSoup(text, 'html.parser')
text = soup.get_text(separator=' ')
# 2. 去除特殊字符与标点
if remove_punct:
text = re.sub(r'[^\w\s]', '', text)
# 3. 大小写转换
if to_lower:
text = text.lower()
# 4. 去除多余空白
if strip_whitespace:
text = re.sub(r'\s+', ' ', text).strip()
return text
# 使用示例
raw_text = "<h1>Hello, World!</h1><p>Python & 数据清洗</p>"
cleaned = clean_text_pipeline(raw_text)
print(cleaned) # 输出: "hello world python 数据清洗"
关键点总结:
- 清洗顺序很重要:先标签 → 再符号 → 大小写 → 最后空白
- BeautifulSoup 的
get_text(separator=' ')比正则更可靠 - 正则清理符号时,想清楚要保留什么,而不是一味删除
- 大小写转换用
lower()最通用,但要注意保护专有名词 - 空白字符不止空格,还有
\t、\n、\xa0等「隐形杀手」
这四步看起来简单,但我在实际项目中见过太多因为「没洗干净」导致模型效果差一大截的案例。说白了,数据清洗的功夫都在细节里。你多花十分钟把数据洗干净,后面建模就能省下十个小时的调参时间。