3、文本预处理(上):文本清洗与分词技术
文本预处理,说白了就是给原始文本「洗澡」。你想想看,从网上扒下来的数据,什么乱七八糟的东西都有——HTML标签、特殊符号、乱码字符……这些东西不清理干净,后面的分析根本没法做。
我个人习惯把文本预处理分成两大块:清洗和分词。今天咱们先聊前半段,也就是清洗和分词的基础操作。
3.1 文本清洗:把垃圾倒掉
我在项目中遇到过最头疼的事,就是爬虫抓回来的新闻数据里混着完整的HTML代码。你想想,一篇好好的文章,里面夹着几十个<div>、<span>标签,这谁受得了?
3.1.1 去除HTML标签
Python里有个现成的库叫BeautifulSoup,专门干这个的。不过我个人更推荐用re正则表达式,轻量、快速,不用额外装包。
import re
def clean_html(text):
# 匹配所有 <...> 标签
clean = re.sub(r'<[^>]+>', '', text)
return clean.strip()
# 示例
raw_text = '<div class="content">这是一段<b>重要</b>的文本</div>'
print(clean_html(raw_text))
# 输出:这是一段重要的文本
lxml解析器配合BeautifulSoup,速度能快3-5倍。我踩过这个坑,一开始用纯正则处理500万条数据,跑了快两个小时……换成lxml后,20分钟搞定。
3.1.2 去除特殊字符
特殊字符包括:标点符号、emoji、控制字符、不可见字符等。这些东西在分词时会造成干扰。
import re
def clean_special_chars(text):
# 保留中文、英文、数字、空格
pattern = r'[^\u4e00-\u9fa5a-zA-Z0-9\s]'
clean = re.sub(pattern, '', text)
# 合并多余空格
clean = re.sub(r'\s+', ' ', clean)
return clean.strip()
# 示例
text = 'Hello!你好👋,今天天气真好啊!!!😊'
print(clean_special_chars(text))
# 输出:Hello 你好 今天天气真好啊
3.1.3 统一大小写与全半角转换
英文文本里,「Hello」和「hello」应该被视为同一个词。中文文本里,全角的逗号「,」和半角的逗号「,」也需要统一。
def normalize_text(text):
# 全角转半角
result = []
for char in text:
code = ord(char)
if code == 0x3000: # 全角空格
code = 0x0020
elif 0xFF01 <= code <= 0xFF5E: # 全角字符
code -= 0xFEE0
result.append(chr(code))
text = ''.join(result)
# 英文转小写
text = text.lower()
return text
# 示例
print(normalize_text('Hello,世界!'))
# 输出:hello,世界!
3.2 分词技术:把句子拆成词
分词是中文NLP里最基础也最关键的步骤。英文天然有空格分隔,中文没有——「我爱北京天安门」到底该怎么切?是「我/爱/北京/天安门」,还是「我爱/北京/天安门」?
嗯,这里要注意:不同的分词器,切出来的结果可能完全不同。
3.2.1 Jieba分词:中文分词首选
Jieba是我用得最多的中文分词工具。它支持三种模式:精确模式、全模式和搜索引擎模式。我个人习惯用精确模式,准确率最高。
import jieba
text = '我来到北京清华大学'
# 精确模式(推荐)
words = jieba.lcut(text)
print(words)
# 输出:['我', '来到', '北京', '清华大学']
# 全模式(会把所有可能的词都切出来)
words = jieba.lcut(text, cut_all=True)
print(words)
# 输出:['我', '来到', '北京', '清华', '清华大学', '华大', '大学']
# 搜索引擎模式(精确模式基础上再切长词)
words = jieba.lcut_for_search(text)
print(words)
# 输出:['我', '来到', '北京', '清华', '华大', '大学', '清华大学']
3.2.2 自定义词典
Jieba自带的词典覆盖了大部分常见词,但遇到专业术语或新词就抓瞎了。比如「区块链」、「深度学习」这些词,在旧版词典里可能被错误切分。
我曾经在做一个医疗文本项目时,发现「阿司匹林」被切成了「阿司/匹林」……这显然不对。解决办法就是加自定义词典。
# 自定义词典格式:词 词频 词性
# 例如:阿司匹林 5 n
jieba.load_userdict('my_dict.txt')
# 或者在代码中直接添加
jieba.add_word('阿司匹林')
jieba.add_word('区块链')
text = '医生建议服用阿司匹林'
words = jieba.lcut(text)
print(words)
# 输出:['医生', '建议', '服用', '阿司匹林']
3.2.3 NLTK分词:英文文本的利器
处理英文文本时,我首选NLTK。它比Jieba更擅长处理英文的复杂形态变化,比如时态、复数、所有格等。
import nltk
from nltk.tokenize import word_tokenize
text = "I'm learning Natural Language Processing. It's fascinating!"
# 分词
tokens = word_tokenize(text)
print(tokens)
# 输出:['I', "'m", 'learning', 'Natural', 'Language', 'Processing', '.', 'It', "'s", 'fascinating', '!']
word_tokenize会把标点单独切出来,这在做某些分析时很有用。但如果你不需要标点,记得在分词前先清洗掉。
3.2.4 停用词过滤
分词之后,你会发现很多词其实没什么实际意义——「的」、「了」、「在」、「是」……这些就是停用词。去掉它们,能大幅减少数据量,提高分析效率。
# 加载停用词表
stopwords = set()
with open('stopwords.txt', 'r', encoding='utf-8') as f:
for line in f:
stopwords.add(line.strip())
# 过滤停用词
def remove_stopwords(words):
return [w for w in words if w not in stopwords and len(w) > 1]
text = '我今天去了图书馆,看了一本关于机器学习的书。'
words = jieba.lcut(text)
print(words)
# 输出:['我', '今天', '去了', '图书馆', ',', '看了', '一本', '关于', '机器', '学习', '的', '书', '。']
filtered = remove_stopwords(words)
print(filtered)
# 输出:['今天', '图书馆', '一本', '关于', '机器', '学习', '书']
3.3 本章知识体系
下面这张图总结了文本预处理的核心流程,你可以对照着梳理自己的处理步骤:
这张图把整个流程串起来了。你从原始文本开始,先做清洗,再做分词,最后得到干净的分词结果。每一步都有对应的技术方案,照着做就行。
好了,文本预处理的上半部分就聊到这儿。清洗和分词是基本功,但也是最容易出问题的地方。我建议你拿一份真实数据练练手,踩踩坑,印象会更深刻。