Python文本文件操作与正则表达式结合使用
Python文本文件操作基础
在Python中,操作文本文件是一项常见任务。使用内置的open()
函数可以打开一个文本文件,该函数接受文件名和打开模式作为参数。打开模式有多种,常见的包括:
'r'
:只读模式,这是默认模式。如果文件不存在,会抛出FileNotFoundError
异常。'w'
:写入模式,如果文件已存在,会清空文件内容;如果文件不存在,会创建新文件。'a'
:追加模式,在文件末尾添加内容。如果文件不存在,会创建新文件。
例如,以只读模式打开一个文件:
try:
file = open('example.txt', 'r')
content = file.read()
print(content)
file.close()
except FileNotFoundError:
print("文件未找到")
在上述代码中,首先尝试打开example.txt
文件,如果成功打开,就读取文件内容并打印,最后关闭文件。若文件不存在,捕获FileNotFoundError
异常并打印提示信息。
关闭文件很重要,因为操作系统对同时打开的文件数量有限制,并且及时关闭文件可以确保数据被正确写入磁盘。为了简化文件操作并自动处理文件关闭,可以使用with
语句:
try:
with open('example.txt', 'r') as file:
content = file.read()
print(content)
except FileNotFoundError:
print("文件未找到")
with
语句会在代码块结束时自动关闭文件,无需显式调用file.close()
。
读取文件内容除了使用read()
方法读取整个文件外,还可以使用readline()
方法逐行读取:
try:
with open('example.txt', 'r') as file:
line = file.readline()
while line:
print(line.strip()) # strip()方法用于去除每行末尾的换行符
line = file.readline()
except FileNotFoundError:
print("文件未找到")
上述代码通过readline()
方法逐行读取文件内容,并使用strip()
方法去除每行末尾的换行符。
readlines()
方法则可以一次性读取文件的所有行,并返回一个包含每行内容的列表:
try:
with open('example.txt', 'r') as file:
lines = file.readlines()
for line in lines:
print(line.strip())
except FileNotFoundError:
print("文件未找到")
写入文件也很简单。以写入模式打开文件后,可以使用write()
方法写入内容:
with open('new_file.txt', 'w') as file:
file.write("这是新写入的内容\n")
file.write("这是第二行内容\n")
上述代码创建了一个新文件new_file.txt
,并写入两行内容,每行末尾添加了换行符\n
。
在追加模式下写入文件:
with open('new_file.txt', 'a') as file:
file.write("这是追加的内容\n")
这样就在new_file.txt
文件的末尾追加了一行内容。
正则表达式基础
正则表达式是一种用于匹配和处理文本的强大工具。在Python中,通过re
模块来支持正则表达式操作。
一个简单的正则表达式模式示例是匹配数字。例如,要匹配字符串中的所有数字,可以使用\d
字符类,它代表任意一个数字字符(0 - 9):
import re
text = "我的电话号码是1234567890,房间号是101"
result = re.findall(r'\d+', text)
print(result)
在上述代码中,re.findall()
函数用于在文本中查找所有匹配的内容。r'\d+'
是正则表达式模式,r
前缀表示这是一个原始字符串,在Python中,正则表达式通常使用原始字符串,以避免对反斜杠进行额外的转义。\d+
表示匹配一个或多个数字字符。re.findall()
函数返回一个列表,包含所有匹配的内容,运行上述代码会输出['1234567890', '101']
。
正则表达式中的字符类还有很多,例如:
\w
:匹配任意一个字母、数字或下划线字符(等价于[a-zA-Z0-9_]
)。\s
:匹配任意一个空白字符(包括空格、制表符、换行符等)。.
:匹配除换行符之外的任意一个字符。
量词用于指定前面的字符或字符类出现的次数:
*
:匹配前面的字符或字符类零次或多次。+
:匹配前面的字符或字符类一次或多次。?
:匹配前面的字符或字符类零次或一次。{n}
:匹配前面的字符或字符类恰好n
次。{n,}
:匹配前面的字符或字符类至少n
次。{n,m}
:匹配前面的字符或字符类至少n
次,至多m
次。
例如,要匹配邮箱地址,可以使用如下正则表达式模式:r'\w+@\w+\.\w+'
。这个模式的含义是:首先匹配一个或多个字母、数字或下划线字符(\w+
),接着匹配@
符号,然后再匹配一个或多个字母、数字或下划线字符,最后匹配一个点号和一个或多个字母、数字或下划线字符。示例代码如下:
import re
text = "我的邮箱是example@example.com,他的邮箱是test@test.cn"
result = re.findall(r'\w+@\w+\.\w+', text)
print(result)
运行上述代码会输出['example@example.com', 'test@test.cn']
。
分组在正则表达式中非常有用,它可以将多个字符组合成一个单元,并可以对这个单元应用量词或进行单独的匹配操作。在正则表达式中,使用圆括号()
来表示分组。例如,要匹配日期格式YYYY - MM - DD
,可以使用模式r'(\d{4})-(\d{2})-(\d{2})'
:
import re
text = "今天的日期是2023 - 10 - 05,明天是2023 - 10 - 06"
result = re.findall(r'(\d{4})-(\d{2})-(\d{2})', text)
print(result)
这里的(\d{4})
、(\d{2})
和(\d{2})
分别是三个分组,re.findall()
函数返回的结果是一个列表,列表中的每个元素是一个元组,元组中的元素分别对应各个分组匹配到的内容,运行上述代码会输出[('2023', '10', '05'), ('2023', '10', '06')]
。
Python文本文件操作与正则表达式结合应用
在实际应用中,常常需要从文本文件中提取特定格式的数据,这就需要将文本文件操作与正则表达式结合使用。
假设我们有一个日志文件log.txt
,内容如下:
2023 - 10 - 01 12:00:00 INFO 系统启动
2023 - 10 - 01 12:05:00 ERROR 数据库连接失败
2023 - 10 - 02 08:30:00 INFO 服务正常运行
我们想要提取日志中的日期、时间和日志级别。可以使用如下代码:
import re
try:
with open('log.txt', 'r') as file:
for line in file.readlines():
pattern = r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+)'
match = re.search(pattern, line)
if match:
date = match.group(1)
time = match.group(2)
level = match.group(3)
print(f"日期: {date}, 时间: {time}, 日志级别: {level}")
except FileNotFoundError:
print("文件未找到")
在上述代码中,首先使用open()
函数以只读模式打开log.txt
文件,并逐行读取。对于每一行,使用re.search()
函数在该行中查找匹配的内容。re.search()
函数会在字符串中查找第一个匹配的位置,如果找到匹配项,返回一个匹配对象,否则返回None
。这里的正则表达式模式r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+)'
使用了三个分组,分别用于匹配日期、时间和日志级别。如果找到匹配项,通过match.group(1)
、match.group(2)
和match.group(3)
分别获取各个分组匹配到的内容,并打印出来。
再比如,有一个包含大量邮件地址的文本文件emails.txt
,我们要验证并提取出有效的邮件地址,并将其写入到另一个文件valid_emails.txt
中。假设邮件地址的格式要求为:用户名部分可以包含字母、数字、下划线、点号,域名部分可以包含字母、数字、点号,且域名至少包含一个点号。代码如下:
import re
try:
with open('emails.txt', 'r') as infile, open('valid_emails.txt', 'w') as outfile:
for line in infile.readlines():
pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
if re.match(pattern, line.strip()):
outfile.write(line)
except FileNotFoundError:
print("文件未找到")
在上述代码中,re.match()
函数用于从字符串的开头开始匹配正则表达式。^
表示字符串的开头,$
表示字符串的结尾,这样可以确保整个字符串都匹配指定的邮件地址格式。对于emails.txt
中的每一行,先使用strip()
方法去除行两端的空白字符,然后使用re.match()
函数进行匹配。如果匹配成功,说明这是一个有效的邮件地址,将其写入到valid_emails.txt
文件中。
有时候,需要在文本文件中替换符合特定正则表达式模式的内容。例如,有一个文本文件text.txt
,内容如下:
原文本:今天是2023年10月5日,天气不错。
我们要将日期格式从YYYY年MM月DD日
替换为YYYY - MM - DD
。代码如下:
import re
try:
with open('text.txt', 'r') as file:
content = file.read()
new_content = re.sub(r'(\d{4})年(\d{2})月(\d{2})日', r'\1-\2-\3', content)
with open('text.txt', 'w') as file:
file.write(new_content)
except FileNotFoundError:
print("文件未找到")
在上述代码中,re.sub()
函数用于在字符串中替换匹配正则表达式的内容。第一个参数是正则表达式模式r'(\d{4})年(\d{2})月(\d{2})日'
,其中使用了三个分组分别匹配年份、月份和日期。第二个参数r'\1-\2-\3'
是替换后的字符串,\1
、\2
、\3
分别表示第一个、第二个和第三个分组匹配到的内容。首先读取text.txt
文件的内容,进行替换操作后,再将新的内容写回到text.txt
文件中。
处理复杂文本结构与多行匹配
在处理一些复杂的文本文件时,可能会遇到需要跨行匹配的情况。例如,有一个HTML文件example.html
,内容如下:
<html>
<head>
<title>示例页面</title>
</head>
<body>
<p>这是一段文本。</p>
<p>这是另一段文本,其中包含一个链接:<a href="https://example.com">点击这里</a>。</p>
</body>
</html>
假设我们要提取HTML文件中的所有链接。由于链接可能分布在不同的行,单纯逐行匹配可能会遗漏一些情况。可以使用re.DOTALL
标志来使.
字符匹配包括换行符在内的所有字符。代码如下:
import re
try:
with open('example.html', 'r') as file:
content = file.read()
pattern = r'<a href="([^"]+)">'
links = re.findall(pattern, content, re.DOTALL)
for link in links:
print(link)
except FileNotFoundError:
print("文件未找到")
在上述代码中,re.findall()
函数的第三个参数re.DOTALL
使得.
字符可以匹配换行符。正则表达式模式r'<a href="([^"]+)">'
用于匹配<a href="链接地址">
这样的格式,并通过分组提取出链接地址。
对于一些更复杂的文本结构,如XML文件,也可以使用正则表达式结合文本文件操作来提取信息。假设我们有一个XML文件example.xml
,内容如下:
<bookstore>
<book category="fiction">
<title lang="en">哈利·波特与魔法石</title>
<author>J.K.罗琳</author>
<price>29.99</price>
</book>
<book category="non-fiction">
<title lang="en">人类简史</title>
<author>尤瓦尔·赫拉利</author>
<price>39.99</price>
</book>
</bookstore>
要提取出每本书的标题和价格,可以使用如下代码:
import re
try:
with open('example.xml', 'r') as file:
content = file.read()
pattern = r'<book.*?><title lang="en">(.*?)</title><author>(.*?)</author><price>(.*?)</price></book>'
books = re.findall(pattern, content, re.DOTALL)
for book in books:
title = book[0]
author = book[1]
price = book[2]
print(f"标题: {title}, 作者: {author}, 价格: {price}")
except FileNotFoundError:
print("文件未找到")
在上述代码中,正则表达式模式r'<book.*?><title lang="en">(.*?)</title><author>(.*?)</author><price>(.*?)</price></book>'
使用了.*?
这种非贪婪匹配方式,以确保能够正确匹配每本书的信息,即使在XML文件格式不太规范(如标签之间可能有多余的空白或换行)的情况下也能准确提取。通过re.findall()
函数结合re.DOTALL
标志,获取所有匹配的书籍信息,并进行打印。
性能优化与注意事项
在使用文本文件操作与正则表达式结合时,性能是一个需要考虑的因素。正则表达式的复杂度对性能有很大影响。例如,使用过于复杂的正则表达式,如包含大量嵌套的分组和量词,可能会导致匹配速度变慢。尽量使用简单的正则表达式来完成任务,如果可能,预先编译正则表达式可以提高性能。在Python中,可以使用re.compile()
函数来编译正则表达式,示例如下:
import re
pattern = re.compile(r'\d+')
text = "123 abc 456 def"
result = pattern.findall(text)
print(result)
编译后的正则表达式对象pattern
在多次使用时会比直接使用re.findall(r'\d+', text)
更高效,尤其是在循环中多次匹配相同模式的情况下。
另外,在处理大文件时,逐行读取并处理比一次性读取整个文件内容到内存中更节省内存。例如,在处理一个非常大的日志文件时,逐行读取并匹配正则表达式:
import re
pattern = re.compile(r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+)')
try:
with open('large_log.txt', 'r') as file:
for line in file:
match = pattern.search(line)
if match:
date = match.group(1)
time = match.group(2)
level = match.group(3)
print(f"日期: {date}, 时间: {time}, 日志级别: {level}")
except FileNotFoundError:
print("文件未找到")
这样可以避免一次性将大文件的所有内容读入内存,从而减少内存消耗。
同时,在使用正则表达式时要注意边界条件。例如,在匹配邮件地址时,要考虑到各种合法和非法的情况,如邮箱地址不能以点号开头或结尾,域名部分不能全是数字等。通过完善正则表达式模式,可以提高匹配的准确性,避免误匹配或漏匹配。
在处理文本文件时,还要注意文件编码问题。不同的文本文件可能采用不同的编码方式,如UTF - 8、GBK等。在打开文件时,可以指定编码方式,例如:
try:
with open('file.txt', 'r', encoding='utf - 8') as file:
content = file.read()
# 后续处理
except FileNotFoundError:
print("文件未找到")
except UnicodeDecodeError:
print("编码解码错误,请检查文件编码")
如果不指定编码,Python会根据系统默认编码来读取文件,这可能会导致在处理非默认编码的文件时出现UnicodeDecodeError
异常。
在实际应用中,还可以结合其他Python库来进一步增强文本处理能力。例如,pandas
库在处理结构化文本数据(如CSV文件)方面非常强大,它可以与正则表达式结合,更方便地进行数据清洗和分析。假设我们有一个CSV文件data.csv
,内容如下:
name,age,email
John,25,John@example.com
Jane,30,Jane@example.net
我们可以使用pandas
库读取CSV文件,并使用正则表达式验证邮箱地址:
import pandas as pd
import re
pattern = re.compile(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')
data = pd.read_csv('data.csv')
for index, row in data.iterrows():
email = row['email']
if not pattern.match(email):
print(f"无效的邮箱地址: {email}")
在上述代码中,首先使用pandas
的read_csv()
函数读取CSV文件内容到一个DataFrame对象中。然后通过iterrows()
方法逐行遍历DataFrame,获取每一行中的邮箱地址,并使用预先编译的正则表达式进行验证。如果邮箱地址无效,打印提示信息。
通过合理结合文本文件操作、正则表达式以及其他相关库,可以高效准确地处理各种文本数据,满足不同的业务需求。无论是数据提取、清洗还是分析,这些技术的综合应用都能发挥重要作用。