aoi学院

Aisaka's Blog, School of Aoi, Aisaka University

Python-第21篇《正则表达式:文本处理利器》

导语

每天面对海量文本——发票号码、银行账号、合同日期、金额数字…手动一个个复制粘贴?眼睛都看花了!正则表达式就是你的“文本扫描仪”,只要告诉它“找数字”“找日期”这些模式,它能在1秒内从几百页PDF里精准提取所有信息。今天咱们掌握这门“数据提取魔法”,从此告别机械重复的复制粘贴!


本篇目标

  • 理解正则表达式的“模式”概念
  • 掌握常用财务数据匹配规则(金额、日期、发票号)
  • 学会从混乱文本中提取结构化信息
  • 实战:自动解析财务报表并生成Excel
  • 获得正则表达式“小抄”,随时查阅

一、准备工作:无需安装,开箱即用

正则表达式是Python内置的re模块,无需安装:

1
2
3
4
5
import re  # 直接导入就能用

# 测试一下
text = "发票号:INV20250012345,金额:¥15,800.50"
print("✅ 正则表达式模块加载成功!")

二、什么是正则表达式?

正则表达式 = 给文本定的“搜索模板”

就像财务查账:

  • 找发票号 → 模式:字母开头+年份+流水号
  • 找金额 → 模式:¥符号+数字+可能有的逗号+小数点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ========== 第一个正则:找数字 ==========
# 场景:从"报销金额¥3,250.50元"中提取数字

text = "报销金额¥3,250.50元,实报¥3,000.00"

# 模式:\d+ 匹配一个或多个数字
numbers = re.findall(r'\d+', text) # r表示原始字符串,避免转义问题
print(f"找到的数字:{numbers}")
# 输出:['3', '250', '50', '3', '000', '00']

# 改进:匹配带逗号和小数的完整金额
amounts = re.findall(r'\d[\d,]*\.?\d*', text)
print(f"找到的金额:{amounts}")
# 输出:['3,250.50', '3,000.00']

# 继续改进:匹配带货币符号的金额
full_amounts = re.findall(r'¥\d[\d,]*\.?\d*', text)
print(f"带符号的金额:{full_amounts}")
# 输出:['¥3,250.50', '¥3,000.00']

三、财务数据匹配模式详解

3.1 匹配金额(最常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# ========== 金额匹配万能公式 ==========
def extract_money(text: str) -> list:
"""
从文本中提取所有金额
支持格式:¥1,234.56 或 1234.56元 或 1234.56
"""
# 模式解释:
# ¥? → ¥符号可选(?表示0或1个)
# \d+ → 至少一位数字
# (,\d{3})* → 逗号加三位数字,重复0次或多次(匹配千分位)
# \.?\d* → 小数点和数字可选(匹配小数部分)
# 元? → "元"字可选

pattern = r'¥?\d+(,\d{3})*\.?\d*\s*元?'

matches = re.findall(pattern, text)

# 清理结果(去掉逗号和"元")
clean_amounts = []
for match in matches:
# 需要完整匹配,上面findall返回的是元组,这里修正
pass

# 修正后的模式
pattern = r'¥?(\d+(?:,\d{3})*\.?\d*)\s*元?'
matches = re.findall(pattern, text)

# 转换为浮点数
amounts = []
for amount_str in matches:
try:
clean_amount = amount_str.replace(',', '')
amount = float(clean_amount)
if amount > 0: # 只保留正数
amounts.append(amount)
except:
continue

return amounts

# 测试
text = """
本月费用:办公用品¥1,250.00元,交通费800元,招待费¥2,580.50。
借款:备用金5,000.00元。
"""
amounts = extract_money(text)
print(f"提取到的金额:{amounts}")
# 输出:[1250.0, 800.0, 2580.5, 5000.0]

3.2 匹配日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# ========== 日期匹配 ==========
def extract_dates(text: str) -> list:
"""
提取日期(支持YYYY-MM-DD、YYYY/MM/DD、YYYY年MM月DD日)
"""
# 模式:匹配2025-01-26或2025/01/26或2025年01月26日
pattern = r'(\d{4})[-/年](\d{1,2})[-/月](\d{1,2})日?'

matches = re.findall(pattern, text)

dates = []
for year, month, day in matches:
try:
# 验证日期合法性
date_str = f"{year}-{month.zfill(2)}-{day.zfill(2)}"
datetime.strptime(date_str, '%Y-%m-%d')
dates.append(date_str)
except:
continue

return dates

# 测试
text = "合同日期:2025-01-26,付款截止2025/02/15,发票日期2025年03月01日"
dates = extract_dates(text)
print(f"提取到的日期:{dates}")
# 输出:['2025-01-26', '2025-02-15', '2025-03-01']

3.3 匹配发票号/银行账号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ========== 发票号/银行账号匹配 ==========
def extract_invoice_numbers(text: str) -> list:
"""
提取发票号(模式:字母+数字)
"""
# 模式:2-4位大写字母 + 8-12位数字
pattern = r'[A-Z]{2,4}\d{8,12}'

return re.findall(pattern, text)

def extract_bank_accounts(text: str) -> list:
"""
提取银行账号(16-19位数字)
"""
pattern = r'\d{16,19}'

return re.findall(pattern, text)

# 测试
text = """
发票号:INV2025123456,代码:FP202500123456
转账账号:6222021234567890123,备用金卡:95588012345678
"""

invoices = extract_invoice_numbers(text)
accounts = extract_bank_accounts(text)

print(f"发票号:{invoices}")
print(f"银行账号:{accounts}")

四、从混乱文本中提取结构化信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# ========== 财务报表文本解析器 ==========
def parse_financial_report(text: str) -> dict:
"""
解析财务报告的混合文本,提取结构化信息
"""
result = {
'报告日期': None,
'公司名称': None,
'总营收': 0,
'净利润': 0,
'主要收入项': [],
'发票列表': []
}

# 1. 提取日期
dates = extract_dates(text)
if dates:
result['报告日期'] = dates[0]

# 2. 提取公司名称(模式:XX有限公司)
company_pattern = r'[\u4e00-\u9fa5]+有限公司'
company_match = re.search(company_pattern, text)
if company_match:
result['公司名称'] = company_match.group()

# 3. 提取金额(营收和净利润通常在特定关键后)
amounts = extract_money(text)

# 找"营收"或"收入"后的第一个大数字
revenue_match = re.search(r'营收[^\d]*¥?(\d[,\d]*\.?\d*)', text)
if revenue_match:
try:
revenue = float(revenue_match.group(1).replace(',', ''))
result['总营收'] = revenue
except:
pass

# 找"净利润"后的数字
profit_match = re.search(r'净利润[^\d]*¥?(\d[,\d]*\.?\d*)', text)
if profit_match:
try:
profit = float(profit_match.group(1).replace(',', ''))
result['净利润'] = profit
except:
pass

# 4. 提取所有收入项(模式:项目名称+金额)
# 例如:"产品销售收入¥1,250,000.00"
income_items = re.findall(r'([\u4e00-\u9fa5]+收入)[^\d]*¥?(\d[,\d]*\.?\d*)', text)
for name, amount in income_items:
try:
result['主要收入项'].append({
'名称': name,
'金额': float(amount.replace(',', ''))
})
except:
continue

# 5. 提取发票号
result['发票列表'] = extract_invoice_numbers(text)

return result

# 测试解析
report_text = """
2025年01月财务报告
智慧财务科技有限公司

一、总体情况
报告日期:2025-01-26
本月总营收¥2,580,000.00元,净利润¥450,000.00元。

二、收入明细
产品销售收入¥1,250,000.00
技术服务收入¥800,000.00
咨询收入¥530,000.00

三、费用明细
相关发票:INV2025001234、INV2025005678
"""

result = parse_financial_report(report_text)
print("\n结构化提取结果:")
print(json.dumps(result, ensure_ascii=False, indent=2))

五、实战:批量解析财务报表文件

5.1 处理单个PDF/PPT文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# ========== 批量解析文件夹中的报表 ==========
def batch_parse_reports(folder_path: str):
"""
批量解析文件夹中的所有文本文件
假设文件已转换为.txt格式
"""
import glob

# 查找所有txt文件
files = glob.glob(os.path.join(folder_path, "*.txt"))

if not files:
print("⚠️ 未找到txt文件,请先转换PDF/PPT为文本")
return

all_results = []

for filepath in files:
try:
print(f"\n📄 处理: {os.path.basename(filepath)}")

# 读取文件内容
with open(filepath, 'r', encoding='utf-8') as f:
text = f.read()

# 解析
result = parse_financial_report(text)
result['文件'] = os.path.basename(filepath)
result['处理时间'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

all_results.append(result)

print(f"✅ 提取到营收: ¥{result['总营收']:,.2f},净利润: ¥{result['净利润']:,.2f}")

except Exception as e:
print(f"❌ 处理失败: {e}")
continue

# 保存结果
if all_results:
df = pd.DataFrame(all_results)
output_file = f"财务报表汇总_{datetime.now().strftime('%Y%m%d')}.xlsx"
df.to_excel(output_file, index=False)
print(f"\n🎉 汇总完成,保存至: {output_file}")

return df

return None

# 创建测试文件
def create_test_files():
"""创建测试用的报表文件"""
os.makedirs("财务报告", exist_ok=True)

# 报告1
report1 = """
2025年01月财务报告
智慧财务科技有限公司
报告日期:2025-01-26
本月总营收¥2,580,000.00元,净利润¥450,000.00元。
产品销售收入¥1,250,000.00
技术服务收入¥800,000.00
发票:INV2025001234
"""

with open("财务报告/2025年01月报告.txt", 'w', encoding='utf-8') as f:
f.write(report1)

# 报告2
report2 = """
2025年02月财务报告
智慧财务科技有限公司
报告日期:2025-02-26
本月总营收¥3,120,000.00元,净利润¥580,000.00元。
产品销售收入¥1,500,000.00
咨询收入¥620,000.00
发票:INV2025005678
"""

with open("财务报告/2025年02月报告.txt", 'w', encoding='utf-8') as f:
f.write(report2)

print("✅ 测试文件创建完成,在'财务报告'文件夹")

# 测试批量处理
# create_test_files()
# df = batch_parse_reports("财务报告")
# if df is not None:
# print("\n汇总结果预览:")
# print(df[['文件', '报告日期', '总营收', '净利润']].to_string())

六、完整项目:财务报表智能解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# ========== 财务报表智能解析器 - 完整版 ==========
import re
import os
import json
import pandas as pd
from datetime import datetime
import glob

class FinancialReportParser:
"""财务报表解析器"""

def __init__(self):
self.results = []

def extract_money(self, text: str) -> list:
"""提取金额"""
pattern = r'¥?(\d+(?:,\d{3})*\.?\d*)\s*元?'
matches = re.findall(pattern, text)

amounts = []
for match in matches:
try:
clean = match.replace(',', '')
amount = float(clean)
if amount > 0:
amounts.append(amount)
except:
continue
return amounts

def extract_dates(self, text: str) -> list:
"""提取日期"""
pattern = r'(\d{4})[-/年](\d{1,2})[-/月](\d{1,2})日?'
matches = re.findall(pattern, text)

dates = []
for year, month, day in matches:
try:
date_str = f"{year}-{month.zfill(2)}-{day.zfill(2)}"
datetime.strptime(date_str, '%Y-%m-%d')
dates.append(date_str)
except:
continue
return dates

def extract_invoice_numbers(self, text: str) -> list:
"""提取发票号"""
pattern = r'[A-Z]{2,4}\d{8,12}'
return re.findall(pattern, text)

def parse_report(self, text: str, filename: str = "") -> dict:
"""
解析单份财务报告

输出结构:
{
'文件': '文件名',
'报告日期': '2025-01-26',
'公司名称': 'XXX有限公司',
'总营收': 2580000.0,
'净利润': 450000.0,
'主要收入项': [{'名称': 'XX收入', '金额': 1250000.0}, ...],
'发票列表': ['INV2025001234', ...]
}
"""
result = {
'文件': filename,
'报告日期': None,
'公司名称': None,
'总营收': 0,
'净利润': 0,
'主要收入项': [],
'发票列表': []
}

# 提取日期
dates = self.extract_dates(text)
if dates:
result['报告日期'] = dates[0]

# 提取公司名称
company_pattern = r'[\u4e00-\u9fa5]+有限公司'
company_match = re.search(company_pattern, text)
if company_match:
result['公司名称'] = company_match.group()

# 提取营收
revenue_match = re.search(r'营收[^\d]*¥?(\d[,\d]*\.?\d*)', text)
if revenue_match:
try:
result['总营收'] = float(revenue_match.group(1).replace(',', ''))
except:
pass

# 提取净利润
profit_match = re.search(r'净利润[^\d]*¥?(\d[,\d]*\.?\d*)', text)
if profit_match:
try:
result['净利润'] = float(profit_match.group(1).replace(',', ''))
except:
pass

# 提取收入项
income_items = re.findall(r'([\u4e00-\u9fa5]+收入)[^\d]*¥?(\d[,\d]*\.?\d*)', text)
for name, amount in income_items:
try:
result['主要收入项'].append({
'名称': name,
'金额': float(amount.replace(',', ''))
})
except:
continue

# 提取发票号
result['发票列表'] = self.extract_invoice_numbers(text)

return result

def parse_file(self, filepath: str) -> dict:
"""解析单个文件"""
try:
print(f"\n📄 处理: {os.path.basename(filepath)}")

# 读取文件
with open(filepath, 'r', encoding='utf-8') as f:
text = f.read()

# 解析
result = self.parse_report(text, os.path.basename(filepath))
result['处理时间'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

return result

except Exception as e:
print(f"❌ 文件处理失败: {e}")
return None

def parse_folder(self, folder_path: str) -> pd.DataFrame:
"""解析文件夹中所有文件"""
# 查找文件
files = glob.glob(os.path.join(folder_path, "*.txt"))

if not files:
print("⚠️ 未找到txt文件")
return pd.DataFrame()

self.results = []
success = 0

for filepath in files:
result = self.parse_file(filepath)
if result:
self.results.append(result)
success += 1

print(f"\n✅ 成功处理 {success}/{len(files)} 个文件")

# 转换为DataFrame
if self.results:
df = pd.DataFrame(self.results)

# 展平收入项
df['收入项数量'] = df['主要收入项'].apply(len)
df['发票数量'] = df['发票列表'].apply(len)

return df

return pd.DataFrame()

def save_to_excel(self, df: pd.DataFrame, output_path: str = None):
"""保存结果到Excel"""
if df.empty:
print("⚠️ 无数据可保存")
return

if output_path is None:
output_path = f"财务报表汇总_{datetime.now().strftime('%Y%m%d')}.xlsx"

try:
# 创建ExcelWriter以支持多个sheet
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# 汇总表
summary_cols = ['文件', '报告日期', '公司名称', '总营收', '净利润', '收入项数量', '发票数量']
df[summary_cols].to_excel(writer, sheet_name='汇总', index=False)

# 详细收入表
detail_rows = []
for _, row in df.iterrows():
for item in row['主要收入项']:
detail_rows.append({
'文件': row['文件'],
'报告日期': row['报告日期'],
'收入名称': item['名称'],
'金额': item['金额']
})

if detail_rows:
detail_df = pd.DataFrame(detail_rows)
detail_df.to_excel(writer, sheet_name='收入明细', index=False)

# 发票列表
invoice_rows = []
for _, row in df.iterrows():
for inv in row['发票列表']:
invoice_rows.append({
'文件': row['文件'],
'报告日期': row['报告日期'],
'发票号': inv
})

if invoice_rows:
inv_df = pd.DataFrame(invoice_rows)
inv_df.to_excel(writer, sheet_name='发票清单', index=False)

print(f"✅ Excel报告已生成: {output_path}")

except Exception as e:
print(f"❌ 保存Excel失败: {e}")

def create_sample_data():
"""创建示例文件"""
os.makedirs("财务报表", exist_ok=True)

# 创建3个示例报告
reports = [
{
"filename": "2025-01-财务报告.txt",
"content": """
智慧财务科技有限公司 2025年1月财务报告
报告日期:2025-01-26

本月实现总营收¥2,580,000.00元,净利润¥450,000.00元。

收入构成:
产品销售收入¥1,250,000.00
技术服务收入¥800,000.00
咨询收入¥530,000.00

相关发票:INV2025001234、INV2025001235
"""
},
{
"filename": "2025-02-财务报告.txt",
"content": """
智慧财务科技有限公司 2025年2月财务报告
报告日期:2025-02-26

本月实现总营收¥3,120,000.00元,净利润¥580,000.00元。

收入构成:
产品销售收入¥1,500,000.00
技术服务收入¥920,000.00
培训收入¥700,000.00

相关发票:INV2025005678
"""
},
{
"filename": "2025-03-财务报告.txt",
"content": """
智慧财务科技有限公司 2025年3月财务报告
报告日期:2025-03-26

本月实现总营收¥2,950,000.00元,净利润¥520,000.00元。

收入构成:
产品销售收入¥1,400,000.00
技术服务收入¥850,000.00
咨询收入¥700,000.00

相关发票:INV2025009012、INV2025009013、INV2025009014
"""
}
]

for report in reports:
with open(f"财务报表/{report['filename']}", 'w', encoding='utf-8') as f:
f.write(report['content'])

print("✅ 示例数据已创建在'财务报表'文件夹")

def main():
"""主菜单"""
parser = FinancialReportParser()

print("=" * 55)
print("财务报表智能解析器")
print("=" * 55)

while True:
print("\n" + "=" * 45)
print("功能菜单")
print("=" * 45)
print("1. 创建示例数据")
print("2. 解析单个文件")
print("3. 解析整个文件夹")
print("4. 退出")
print("=" * 45)

choice = input("请选择: ").strip()

if choice == "1":
create_sample_data()

elif choice == "2":
filepath = input("文件路径: ").strip()
if not os.path.exists(filepath):
print("❌ 文件不存在")
continue

result = parser.parse_file(filepath)
if result:
print("\n解析结果:")
print(json.dumps(result, ensure_ascii=False, indent=2))

elif choice == "3":
folder = input("文件夹路径(直接回车用'财务报表'): ").strip() or "财务报表"
if not os.path.exists(folder):
print("❌ 文件夹不存在")
continue

df = parser.parse_folder(folder)
if not df.empty:
print("\n汇总预览:")
print(df[['文件', '报告日期', '总营收', '净利润']].to_string())

save = input("\n是否保存为Excel?(y/n): ").strip()
if save.lower() == 'y':
parser.save_to_excel(df)
else:
print("⚠️ 没有解析到有效数据")

elif choice == "4":
print("👋 再见!")
break

else:
print("请输入1-4!")

if __name__ == "__main__":
main()

七、知识点加油站

7.1 正则表达式“小抄”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""
常用元字符:
. 匹配任意字符(除换行符)
\d 匹配数字 [0-9]
\D 匹配非数字
\w 匹配字母/数字/下划线
\W 匹配非字母/数字/下划线
\s 匹配空白(空格/Tab)
\S 匹配非空白

量词:
* 0次或多次
+ 1次或多次
? 0次或1次
{n} 恰好n次
{n,} 至少n次
{n,m} n到m次

边界:
^ 字符串开头
$ 字符串结尾
\b 单词边界

分组与选择:
() 分组,可用于提取
[] 字符集合
| 或(abc|def)

进阶:
(?:...) 非捕获组(不保存匹配结果)
(?P<name>...) 命名分组

财务常用模式:
金额:¥?\d+(,\d{3})*\.?\d*
日期:(\d{4})[-/年](\d{1,2})[-/月](\d{1,2})日?
发票:[A-Z]{2,4}\d{8,12}
银行账号:\d{16,19}
百分比:\d+\.?\d*%
"""

7.2 正则调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""
1. 从简单开始,逐步添加规则
先写 \d+,再写 \d+\.?\d*,最后写 \d+(,\d{3})*\.?\d*

2. 使用https://regex101.com/在线调试
粘贴文本和模式,实时看匹配结果

3. 用re.DEBUG查看编译过程
re.compile(pattern, re.DEBUG)

4. 原始字符串r'pattern'
避免转义问题,推荐总是使用r''

5. 贪婪与非贪婪
\d+ 贪婪(匹配尽可能多)
\d+? 非贪婪(匹配尽可能少)
"""

八、总结

  • ✅ 正则表达式的模式匹配原理
  • ✅ 提取金额、日期、发票号等财务数据
  • ✅ 从混乱文本中提取结构化信息
  • ✅ 批量解析财务报表
  • ✅ 生成Excel汇总报告

下篇预告

第22篇《数据库基础:SQLite入门》——把提取的数据存到数据库,实现高效查询和管理!


🤖 Powered by Kimi K2 Thinking 💻 内容经葵葵🌻审核与修改