2、Python文本处理基础:字符串操作、正则表达式入门、Python的re模块详解
各位同学,欢迎来到第二章。
上一章我们聊了文本挖掘的整体框架,说白了就是知道我们要干什么。这一章,咱们得把工具磨利了——Python的文本处理基本功。我做了这么多年文本挖掘,可以负责任地告诉你:字符串操作和正则表达式,是你每天都会碰到的两把刀。磨刀不误砍柴工,咱们今天就把它们练熟。
本章核心脉络:从最基础的字符串切片,到正则表达式的模式匹配,再到Python re模块的实战用法。一步步来,别急。
2.1 字符串操作——你每天都在用的基本功
Python的字符串,说白了就是一串字符排排队。我刚开始学的时候觉得这有什么好讲的?后来做项目才发现,字符串操作是文本挖掘的基石。你想想看,不管多复杂的文本,最终都要落到这些基本操作上。
2.1.1 字符串切片与索引
Python的索引从0开始,这个大家都知道。但有个小技巧——负索引,从末尾往前数。我在项目中经常用它来快速取文件扩展名:
text = "hello_world.txt"
print(text[0:5]) # 输出: hello
print(text[-4:]) # 输出: .txt (取最后4个字符)
print(text[::-1]) # 输出: txt.olleh_dlrow (反转字符串,面试常考)
我的习惯:处理文件路径时,我总用 text[-4:] 来判断文件类型。但要注意,如果文件名是 ".json" 只有5个字符,用 -4 就对了。如果是 ".html" 呢?嗯,这时候用 text.split('.')[-1] 更稳妥。
2.1.2 常用字符串方法
Python的字符串方法太多了,我挑几个最常用的给你说说:
| 方法 | 作用 | 示例 | 输出 |
|---|---|---|---|
strip() |
去除首尾空白 | " hello ".strip() |
"hello" |
split() |
按分隔符拆分 | "a,b,c".split(",") |
['a','b','c'] |
join() |
合并列表为字符串 | "-".join(['a','b']) |
"a-b" |
replace() |
替换子串 | "hello".replace("l","x") |
"hexxo" |
find() |
查找子串位置 | "hello".find("ll") |
2 |
我曾经踩过的坑:用 split() 时没注意连续分隔符。比如 "a,,b".split(",") 会返回 ['a', '', 'b'],中间有个空字符串。如果你要处理CSV数据,记得用 csv 模块,别自己手写split。
2.2 正则表达式入门——从"找东西"开始
字符串操作能解决80%的问题,但剩下的20%,就得靠正则表达式了。说白了,正则表达式就是一套"模式匹配"的语言。你告诉它"我要找什么样子"的文本,它帮你找出来。
2.2.1 元字符——正则的"字母表"
正则表达式里,有些字符有特殊含义,我们叫它"元字符"。记住这几个,你就入门了:
.—— 匹配任意单个字符(除了换行符)\d—— 匹配数字,等价于[0-9]\w—— 匹配字母、数字、下划线\s—— 匹配空白字符(空格、制表符、换行)^—— 匹配字符串开头$—— 匹配字符串结尾
举个例子,你想从一段文本里提取手机号(假设是11位数字):
import re
text = "我的电话是13800138000,请惠存。"
pattern = r"\d{11}" # 匹配连续11位数字
result = re.findall(pattern, text)
print(result) # 输出: ['13800138000']
我个人习惯:写正则时,前面加个 r 变成原始字符串。比如 r"\d" 而不是 "\\d"。这样不用考虑转义问题,少掉头发。
2.2.2 量词——控制匹配次数
光有元字符还不够,你得告诉它"匹配多少次"。量词就是干这个的:
| 量词 | 含义 | 示例 | 匹配 |
|---|---|---|---|
* |
0次或多次 | ab* |
a, ab, abb, abbb... |
+ |
1次或多次 | ab+ |
ab, abb, abbb...(不匹配a) |
? |
0次或1次 | ab? |
a, ab |
{n} |
恰好n次 | \d{3} |
123, 456... |
{n,} |
至少n次 | \d{3,} |
123, 1234, 12345... |
{n,m} |
n到m次 | \d{2,4} |
12, 123, 1234 |
避坑指南:正则默认是"贪婪"的,它会尽可能多地匹配。比如 r"a.*b" 去匹配 "a123b456b",它会匹配整个 "a123b456b" 而不是 "a123b"。想让它"非贪婪"?加个 ? 就行:r"a.*?b"。
2.3 Python的re模块详解——把正则用起来
正则表达式写好了,怎么在Python里用?re模块就是干这个的。我常用的就四个函数,你记住它们就够了:
2.3.1 re.match() 与 re.search()
这两个容易搞混,我当年也迷糊过。简单说:
re.match()—— 从字符串开头匹配。开头对不上,就返回None。re.search()—— 在整个字符串里搜索。只要找到一处就返回。
import re
text = "我的邮箱是abc@test.com,备用邮箱是xyz@test.com"
# match:从开头找,开头不是邮箱,所以返回None
result1 = re.match(r"\w+@\w+\.com", text)
print(result1) # 输出: None
# search:找到第一个匹配就返回
result2 = re.search(r"\w+@\w+\.com", text)
print(result2.group()) # 输出: abc@test.com
我的经验:90%的情况下,我用 re.search() 而不是 re.match()。因为实际文本很少恰好以你要找的内容开头。除非你在做表单验证,比如检查用户输入的电话号码是不是以1开头。
2.3.2 re.findall() 与 re.finditer()
想找所有匹配?用 findall 或 finditer:
import re
text = "电话1:13800138000, 电话2:13912345678"
# findall:返回所有匹配的列表
phones = re.findall(r"1\d{10}", text)
print(phones) # 输出: ['13800138000', '13912345678']
# finditer:返回迭代器,适合处理大量数据
for match in re.finditer(r"1\d{10}", text):
print(f"找到电话: {match.group()}, 位置: {match.start()}-{match.end()}")
我曾经踩过的坑:处理几百万行的日志文件时,用 findall 一次性把所有结果加载到内存,结果内存爆了。后来改用 finditer 逐行处理,问题解决。记住:数据量大时,用迭代器。
2.3.3 re.sub()——替换的艺术
文本清洗里,替换是最常见的操作。re.sub() 可以让你用正则来替换:
import re
text = "我的手机是138-0013-8000,请打这个号码。"
# 去掉手机号中的横线
cleaned = re.sub(r"(\d{3})-(\d{4})-(\d{4})", r"\1\2\3", text)
print(cleaned) # 输出: 我的手机是13800138000,请打这个号码。
# 更狠一点:直接脱敏,只显示前3位和后4位
masked = re.sub(r"(\d{3})\d{4}(\d{4})", r"\1****\2", text)
print(masked) # 输出: 我的手机是138****8000,请打这个号码。
核心技巧:re.sub() 的第二个参数可以用 \1、\2 引用分组。这在数据脱敏、格式化输出时特别好用。我做过一个项目,需要把日志里的IP地址全部替换成 xxx.xxx.xxx.xxx,一行re.sub就搞定了。
2.3.4 re.compile()——性能优化
如果你要多次使用同一个正则,别每次都写一遍。用 re.compile() 预编译一下:
import re
# 预编译正则
phone_pattern = re.compile(r"1[3-9]\d{9}")
# 多次使用
text1 = "我的电话是13800138000"
text2 = "他的电话是13912345678"
result1 = phone_pattern.search(text1)
result2 = phone_pattern.findall(text2)
print(result1.group()) # 13800138000
print(result2) # ['13912345678']
我的习惯:只要正则会在循环里用,或者被调用超过3次,我就用 compile。性能提升可能不明显,但代码更清晰,可读性更好。你想想看,把正则定义放在文件开头,后面直接调用,多清爽。
2.4 实战小案例:清洗用户评论数据
光说不练假把式。咱们来个小案例,把今天学的串起来:
import re
# 模拟一段用户评论数据
comments = [
"这个产品真不错!!!!! 推荐购买!!!",
" 质量一般般吧,价格有点贵。 ",
"客服电话:400-123-4567,有问题可以打",
"垃圾产品!!千万别买!!!!!",
" Email: support@shop.com "
]
def clean_comment(text):
# 1. 去除首尾空白
text = text.strip()
# 2. 去除多余标点(连续感叹号只保留一个)
text = re.sub(r"!+", "!", text)
# 3. 脱敏电话号码
text = re.sub(r"(\d{3})-(\d{3})-(\d{4})", r"\1-****-\3", text)
# 4. 脱敏邮箱
text = re.sub(r"\w+@\w+\.\w+", "***@***.***", text)
return text
cleaned = [clean_comment(c) for c in comments]
for c in cleaned:
print(c)
# 输出:
# 这个产品真不错! 推荐购买!
# 质量一般般吧,价格有点贵。
# 客服电话:400-****-4567,有问题可以打
# 垃圾产品!千万别买!
# Email: ***@***.***
你看,几行代码就把脏数据洗干净了。这就是正则表达式的威力。
好了,这一章的内容就到这。字符串操作是基本功,正则表达式是进阶武器,re模块是把武器用起来的接口。多练练,你会发现文本处理其实没那么难。