尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python 正则表达式(十四):文本匹配、查找与替换
相信大家平时都见过这样的内容内容里明明有我们需要的信息但它们都混杂在一大段文字里。比如日志中的时间和 IP聊天记录里的手机号和邮箱没法像 JSON 或 CSV 那样直接按字段读取。思维导图这种时候正则表达式就很适合用来处理文本匹配。今天我们从第一次匹配开始逐步讲清楚查找、分组、替换和拆分最后再写一个日志解析脚本。一、正则表达式是什么正则表达式可以理解成一套文本规则。我们先写出目标内容的特征再让程序按这套规则去查找。例如下面这段文字中包含一个订单号text订单号A20260919状态已发货如果订单号始终由一个大写字母和八位数字组成它的规则可以写成[A-Z]\d{8}其中[A-Z]代表一位大写字母\d{8}代表连续八位数字。正则表达式本身并不负责读取文件它处理的是字符串。日志文件、网页内容或者 CSV 字段读成字符串以后都能交给它匹配。二、使用 re.search() 完成第一次匹配Python 的正则功能放在标准库re中不需要另外安装。先用search()找出订单号中的数字importre text订单号A20260919resultre.search(r\d,text)print(result)\d表示数字后面的表示前一个规则可以出现一次或多次。运行以后会得到一个 Match 对象re.Match object; span(5, 13), match20260919Match 对象里保存了匹配内容和位置。一般会通过这些方法读取print(result.group())print(result.start())print(result.end())print(result.span())输出结果20260919 5 13 (5, 13)start()是起始下标end()是结束位置span()会把两者放进一个元组。没有找到内容时search()返回None。直接调用group()会报错实际使用时要先判断resultre.search(r\d,订单暂未生成)ifresult:print(result.group())else:print(没有找到订单号)三、为什么正则前面经常带 r正则表达式中经常出现反斜杠。Python 字符串也使用反斜杠表示转义字符例如\n是换行\t是制表符。两套规则写在一起很容易把反斜杠写乱。在字符串前加r可以创建原始字符串patternr\d这里的r只影响 Python 解析字符串的方式最终交给正则引擎的规则还是\d。写正则时建议默认使用原始字符串。需要匹配反斜杠本身时正则中仍然要写两个反斜杠textrC:\Users\ivan\notes.txtresultre.search(r\\Users\\,text)print(result.group())四、search、match 和 fullmatch 有什么区别这三个方法都会返回 Match 对象区别在于从哪里开始找以及需要匹配多少内容。search()会在整个字符串中查找第一个符合规则的位置importre text用户ID: 1024resultre.search(r\d,text)print(result.group())match()只检查字符串开头print(re.match(r用户,text))print(re.match(r\d,text))第二次返回None因为数字不在开头。fullmatch()要求整段字符串都符合规则很适合校验格式order_idA20260919ifre.fullmatch(r[A-Z]\d{8},order_id):print(订单号格式正确)else:print(订单号格式错误)查找一段内容时用search()检查开头时用match()校验完整格式时用fullmatch()。五、常用字符规则正则中最常见的几个规则如下规则含义示例\d数字0到9\D非数字字母、中文、符号\wUnicode 字母、数字和下划线user_01、中文\W不属于\w的字符空格和多数标点\s空白字符空格、制表符、换行\S非空白字符普通可见文字.除换行以外的任意字符字母、数字、标点下面从一段文字中找出价格text键盘价格 299 元鼠标价格 129 元pricesre.findall(r\d,text)print(prices)输出结果[299,129]\w在 Python 3 中默认能匹配中文。如果只想匹配英文字母、数字和下划线可以明确写成[A-Za-z0-9_]不要把两者当成完全相同的规则。六、使用字符集合限制范围方括号可以列出允许出现的字符text订单 A102、B205、c308ordersre.findall(r[A-Z]\d,text)print(orders)输出[A102,B205][A-Z]只匹配大写英文字母小写的c308不符合规则。常见写法还有[abc] 匹配 a、b 或 c [0-9] 匹配一位数字 [a-zA-Z] 匹配一位英文字母 [^0-9] 匹配非数字字符方括号开头的^表示排除。它放在方括号外时含义会变成字符串开头后面会单独讲。七、数量词控制出现次数字符规则只说明“匹配什么”数量词负责说明“出现几次”。数量词含义*零次或多次一次或多次?零次或一次{m}恰好 m 次{m,n}最少 m 次最多 n 次{m,}最少 m 次使用数量词检查手机号的基本结构phone13812345678resultre.fullmatch(r1[3-9]\d{9},phone)print(bool(result))1是固定开头[3-9]匹配第二位\d{9}要求后面还有九位数字。这里检查的是常见手机号格式不能证明号码真实存在。八、使用锚点限制开头和结尾^表示字符串开头$表示字符串结尾textERROR: 登录失败print(bool(re.search(r^ERROR,text)))print(bool(re.search(r失败$,text)))两次结果都是True。锚点匹配的是位置本身不会占用字符。校验一整段内容时也可以直接使用fullmatch()usernameivan_2026ifre.fullmatch(r[A-Za-z][A-Za-z0-9_]{3,15},username):print(用户名可用)这个规则要求用户名以英文字母开头总长度为 4 到 16 位后面可以使用字母、数字和下划线。九、分组一次取出多个字段圆括号可以把一段规则放进一个分组。下面从日期中分别取出年、月、日importre date_text2026-09-19resultre.fullmatch(r(\d{4})-(\d{2})-(\d{2}),date_text,)ifresult:print(result.group(0))print(result.group(1))print(result.group(2))print(result.group(3))print(result.groups())group(0)是完整匹配后面的编号按左括号出现的顺序排列。groups()会一次返回所有分组2026-09-19 2026 09 19 (2026, 09, 19)分组多起来以后记编号很不方便。这时可以给分组命名pattern(r(?Pyear\d{4})-r(?Pmonth\d{2})-r(?Pday\d{2}))resultre.fullmatch(pattern,date_text)ifresult:print(result.group(year))print(result.groupdict())groupdict()会把命名分组整理成字典{year:2026,month:09,day:19}解析日志、订单和接口返回文本时命名分组比数字编号更容易维护。十、只分组不保存内容有时圆括号只是为了把几条规则放在一起并不需要把内容保存成分组。可以使用(?:...)text图片cover.jpg附件report.pdffilesre.findall(r\w\.(?:jpg|png|pdf),text)print(files)输出[cover.jpg,report.pdf]如果写成(jpg|png|pdf)findall()会优先返回捕获分组里的扩展名。这里使用非捕获分组得到的才是完整文件名。竖线|表示多个规则任选一个INFO|WARNING|ERROR它能匹配三种日志级别中的任意一种。十一、findall 和 finditer 批量查找search()只返回第一个匹配。想拿到所有结果可以使用findall()text今天处理 A102明天处理 A205A308 已完成ordersre.findall(r[A-Z]\d,text)print(orders)输出[A102,A205,A308]还需要每个结果的位置时使用finditer()formatchinre.finditer(r[A-Z]\d,text):print(match.group(),match.start(),match.end(),)finditer()返回一个迭代器不会提前把所有 Match 对象放进列表。数据比较多或者需要逐个查看分组和位置时它更合适。十二、贪婪匹配和非贪婪匹配数量词默认会尽量多匹配。下面这段 HTML 中有两个标签htmlpPython/pp正则表达式/pprint(re.findall(rp.*/p,html)).*会从第一个p一直匹配到最后一个/p[pPython/pp正则表达式/p]在数量词后面加?可以改成非贪婪匹配print(re.findall(rp.*?/p,html))这次得到两个结果[pPython/p,p正则表达式/p]这个例子适合说明贪婪规则真实 HTML 的结构远比它复杂。需要可靠地解析网页时应该使用 HTML 解析库不要只靠一条正则处理整份页面。十三、使用 re.sub() 替换内容re.sub()会把符合规则的内容替换掉。处理手机号脱敏时可以保留前三位和后四位importre phone13812345678maskedre.sub(r(\d{3})\d{4}(\d{4}),r\1****\2,phone,)print(masked)输出138****5678替换字符串中的\1和\2引用了第一个、第二个分组。分组较多时可以改用命名分组patternr(?Pstart\d{3})\d{4}(?Pend\d{4})maskedre.sub(pattern,r\gstart****\gend,phone,)替换逻辑需要计算时可以把函数传给sub()text商品价格 299 元优惠价 199 元defadd_currency(match):pricematch.group()returnf¥{price}resultre.sub(r\d,add_currency,text)print(result)十四、使用 re.split() 拆分内容普通字符串的split()只能方便地处理一种固定分隔符。文本中混用了逗号、分号和空格时可以使用re.split()textPython,Java;Go Rustlanguagesre.split(r[,;\s],text)print(languages)输出[Python,Java,Go,Rust][,;\s]表示一个或多个逗号、分号或空白字符。实际数据结尾可能还带分隔符可以顺手去掉空字符串languages[itemforiteminre.split(r[,;\s],text)ifitem]十五、重复使用规则时先编译同一条规则需要使用多次时可以用re.compile()创建正则对象importre order_patternre.compile(r[A-Z]\d{3})texts[新订单 A102 已创建,B205 等待付款,没有订单号,]fortextintexts:resultorder_pattern.search(text)ifresult:print(result.group())编译后的对象也有search()、findall()、sub()等方法。这样写能把规则集中放在一个位置名称也能说明它是用来匹配什么的。十六、常用匹配标志匹配标志可以改变正则的工作方式。忽略英文字母大小写时使用re.IGNORECASEtextError: file not foundresultre.search(rerror,text,flagsre.IGNORECASE,)print(result.group())处理多行文本时re.MULTILINE会让^和$匹配每一行的开头和结尾logsINFO: 启动成功 ERROR: 数据库连接失败 INFO: 开始重试errorsre.findall(r^ERROR:.*$,logs,flagsre.MULTILINE,)print(errors).默认不匹配换行。需要让它跨行匹配时可以使用re.DOTALL。多个标志能用|组合flagsre.IGNORECASE|re.MULTILINE十七、写正则时容易踩的坑最常见的问题是规则写得太宽。比如.*什么都能接短文本里看不出问题换一批数据就可能吞掉多余内容。能写清字符范围时尽量使用[A-Z]、\d{4}这类具体规则。第二个问题是忘记处理None。search()和fullmatch()都有匹配失败的时候调用group()以前先判断结果。还要留意捕获分组对findall()返回值的影响textA102 B205print(re.findall(r[A-Z]\d,text))print(re.findall(r([A-Z])\d,text))第二条规则包含捕获分组结果会变成[A, B]。如果括号只用来控制范围改成(?:[A-Z])。正则也不适合承担所有格式校验。邮箱地址、URL 和 HTML 都有许多边界情况。教程里常见的邮箱规则可以拿来提取简单地址不能当作完整标准的验证器。十八、综合示例解析日志并导出 CSV现在把正则和上一章的 CSV 连起来。我们准备一份app.log2026-09-19 10:15:32 INFO userivan actionlogin ip192.168.1.10 2026-09-19 10:16:03 WARNING useralice actiondownload ip10.0.0.5 这是一行无法识别的内容 2026-09-19 10:18:45 ERROR userbob actionupload ip172.16.3.8项目结构如下regex_demo/ ├─ parse_log.py ├─ data/ │ └─ app.log └─ output/在parse_log.py中写入frompathlibimportPathimportcsvimportre base_dirPath(__file__).resolve().parent log_filebase_dir/data/app.logoutput_dirbase_dir/outputcsv_fileoutput_dir/log_report.csvoutput_dir.mkdir(parentsTrue,exist_okTrue)log_patternre.compile(r^(?Ptime\d{4}-\d{2}-\d{2} r\d{2}:\d{2}:\d{2})\sr(?PlevelINFO|WARNING|ERROR)\sruser(?Puser\w)\sraction(?Paction\w)\srip(?Pip(?:\d{1,3}\.){3}\d{1,3})$)records[]failed_lines[]withlog_file.open(r,encodingutf-8)asfile:forline_number,lineinenumerate(file,start1):lineline.strip()ifnotline:continueresultlog_pattern.fullmatch(line)ifresult:records.append(result.groupdict())else:failed_lines.append((line_number,line))fieldnames[time,level,user,action,ip,]withcsv_file.open(w,encodingutf-8-sig,newline,)asfile:writercsv.DictWriter(file,fieldnamesfieldnames,)writer.writeheader()writer.writerows(records)print(f成功解析{len(records)}行)print(f解析失败{len(failed_lines)}行)print(fCSV 文件{csv_file})forline_number,contentinfailed_lines:print(f第{line_number}行无法识别{content})这里使用命名分组直接生成字典再交给csv.DictWriter()写入文件。无法匹配的行没有被悄悄丢掉程序会保存行号和原内容方便回头检查规则或原始数据。IP 的规则(?:\d{1,3}\.){3}\d{1,3}只检查“由四段一到三位数字组成”的外形没有继续判断每一段是否处于0到255。需要严格验证 IP 时可以把匹配结果交给标准库ipaddress再检查。生成的log_report.csv如下time,level,user,action,ip 2026-09-19 10:15:32,INFO,ivan,login,192.168.1.10 2026-09-19 10:16:03,WARNING,alice,download,10.0.0.5 2026-09-19 10:18:45,ERROR,bob,upload,172.16.3.8!10-log-pipeline.png十九、练习练习一提取所有价格准备字符串键盘 299 元鼠标 129 元显示器 1599 元使用正则提取所有价格并把结果转换成整数列表。练习二检查订单号订单号由两个大写字母和六位数字组成。使用fullmatch()分别检查AB123456、A123456和ab123456。练习三提取邮箱从一段文本中提取形如nameexample.com的简单邮箱地址。先处理常见英文字母、数字、点、下划线和连字符不需要覆盖所有邮箱标准。练习四隐藏身份证号把十八位身份证号的中间八位替换成星号只保留前六位和后四位。使用捕获分组保留两端内容。练习五解析文件名从report_2026-09-19.csv中取出文件类型和日期分别保存到命名分组date和extension中。练习六统计错误日志读取一份日志文件使用re.MULTILINE找出所有以ERROR开头的行。再从每一行中提取时间和错误内容并写入 CSV 文件。学正则时没必要先背完所有符号。先把目标文本找出来标清哪些内容固定、哪些内容会变再把它们换成字符规则和数量词。规则写完后多准备几条正常数据和异常数据测试比盯着一条样例反复修改更容易发现问题。
RELATED

相关推荐

Spring AI 接入 DeepSeek Chat 模型

Spring AI 接入 DeepSeek Chat 模型

本文介绍如何在 Spring Boot 项目中使用 Spring AI 接入 DeepSeek Chat 模型,实现一个简单的对话接口。示例包含 Maven 依赖、application.yml 配置、ChatClient 调用代码以及常见问题排查。示例基于当前 Spring AI 2.0.x 的自动配置方式。Spring AI 和 DeepSeek 的…

📅 2026/9/30 2:46:39
SPFA 算法简介及经典实例

SPFA 算法简介及经典实例

● SPFA 算法 (1)SPFA 算法,即最短路径快速算法,是基于 Bellman-Ford 算法优化而来的单源最短路径算法,适用于带负权边、无负权环的有向图或无向图,在算法竞赛中应用广泛。 (2)SPFA …

📅 2026/9/30 2:46:39
GCC 参数记不住?这份语法速查 + 多参数组合示例,收藏就够

GCC 参数记不住?这份语法速查 + 多参数组合示例,收藏就够

代码写得没问题,一编译却蹦出一堆 undefined reference to xxx。改了半天,最后发现是命令里库的顺序写反了。今天把 GCC 语法、核心参数、多参数组合,以及最容易踩的链接顺序坑,一篇讲清。 一、gcc 基本语法 一条 gcc 命令长这样…

📅 2026/9/30 2:46:39
MORE NEWS

更多资讯

📰

Univer 在线表格实战:Canvas 渲染、插件架构与协同编辑

1. 从“univer”这个名字说起:它到底想解决什么问题第一次看到“univer”这个词,很多人会下意识联想到“universe”或者“universal”,觉得它是不是又一个想做大而全的万能工具。我最初接触它的时候也是这个反应,但真正翻完文档、…

📰

基于MCP协议的LLM Agent长期记忆系统设计与Docker部署实战

1. 从“hindsight”说起:为什么我们需要给Agent装上“后视镜”第一次看到“hindsight”这个词,是在一个做LLM Agent的朋友群里。有人丢了一张截图,说他们的Agent在连续对话到第37轮的时候,突然把用户三小时前说过的偏好设置忘得一…

📰

Agent记忆系统实战:基于MCP协议与pgvector实现hindsight记忆架构

1. 从“hindsight”说起:为什么我们需要给Agent装上“后视镜”第一次看到“hindsight”这个词,是在做一个多轮对话Agent的复盘工具时。当时团队里有个争论:Agent到底需不需要“记住”上一次任务失败的原因?有人觉得每次请求都是独…

📰

Agent Memory实战:基于MCP与Docker构建LLM长期记忆系统

1. 从“hindsight”说起:为什么我们需要给Agent装上“后视镜”第一次看到“hindsight”这个词,我脑子里蹦出来的不是词典里的“事后聪明”,而是开车时那面后视镜。你往前开,眼睛盯着前方路况,但真正让你敢变道、敢超车…

📰

hindsight视角下的Agent Memory:三层记忆架构与检索优化实践

1. 从“hindsight”说起:为什么我们需要给Agent装上“后视镜”第一次看到“hindsight”这个词,我脑子里蹦出来的不是词典释义,而是每次调完一个LLM Agent之后拍大腿的瞬间——刚才那轮对话它明明已经拿到了正确线索,结果下一轮又像…

📰

Vue3 + Nginx 在 Windows 部署的三大核心冲突与配置原理

1. 为什么Vue3项目在Windows上用Nginx部署,不是“能用就行”,而是“必须这样搭”你肯定试过:npm run build打包完,把dist文件夹拖进 Nginx 的html目录,双击nginx.exe启动,浏览器打开http://localhost—— 页…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬