2、Python文本处理基础:字符串操作、正则表达式入门、Python的re模块详解

各位同学,欢迎来到第二章。

上一章我们聊了文本挖掘的整体框架,说白了就是知道我们要干什么。这一章,咱们得把工具磨利了——Python的文本处理基本功。我做了这么多年文本挖掘,可以负责任地告诉你:字符串操作和正则表达式,是你每天都会碰到的两把刀。磨刀不误砍柴工,咱们今天就把它们练熟。

本章核心脉络:从最基础的字符串切片,到正则表达式的模式匹配,再到Python re模块的实战用法。一步步来,别急。

Python文本处理基础 字符串操作 切片 · 查找 · 替换 · 分割 正则表达式入门 元字符 · 量词 · 分组 · 贪婪 re模块详解 match · search · findall · sub str.split() str.strip() \d \w \s * + ? {} re.compile() re.finditer() 实战:清洗文本 → 提取信息 → 替换内容

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()

想找所有匹配?用 findallfinditer

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模块是把武器用起来的接口。多练练,你会发现文本处理其实没那么难。


专注资料整理