在医疗药房管理、药品库存统计等业务场景中,Excel 表格里的药品规格常以"0.32g72粒""20mg 40片""100ml1瓶"等字符串形式存储。若需完成库存盘点、剂量汇总等工作,需将这类规格解析为"总容量+单位"的标准化形式(如 0.32g72粒 → 23.04g)。

本文基于 pandas 与正则表达式实现一套实用的 Python 脚本,可自动读取 Excel 中的药品规格、解析并计算总容量,最终将结果写回 Excel;同时针对 Excel 导出异常导致的重复表头问题,脚本也做了针对性处理,适配 mg、g、ml 等常见容量单位及粒、片、袋等包装单位。
一 、需求
以 Excel 文件 data20251223.xlsx 为例,数据格式如下:
| 药品名称 | 药品规格 |
|---|---|
| 药1 | 0.52g*72粒 |
| 药2 | 0.36g*45片 |
| 药3 | 36mg*40片 |
| 药4 | 5g*12袋 |
| 药5 | 100ml*1瓶 |
| 药6 | 5g*12袋 |
| 药7 | 5g*12袋 |
| 药8 | 0.3g*100片 |
| 药9 | 20ml*10支 |
| 药10 | 0.3g*60粒 |
核心需求是新增"容量/单位包装(计算后)"列,将每条规格转换为总容量格式:
- 0.32g*72粒 → 23.04g
- 20mg*40片 → 800mg
- 100ml*1瓶 → 100ml
此外,需解决 Excel 读取时的重复表头问题(如文件内存在两行表头、导出异常导致数据行与表头重复),避免写回文件时出现冗余标题行。
二、实现思路概览
- 数据读取:通过 pandas 读取 Excel 文件并转换为 DataFrame 结构;
- 重复表头处理:检测并删除与列名完全一致的首行数据,解决重复表头问题;
- 规格解析:利用正则表达式提取"单剂量数值+容量单位"及"包装数量";
- 单位标准化:统一转换 mg/g/ml 相关的中英文/大小写写法,过滤粒、片等包装单位;
- 总容量计算:基于解析的数值和单位计算总容量,并格式化输出结果;
- 结果输出:将计算结果写入新的 Excel 文件,保证数据格式规范。
三、关键点说明
1) 单位规范化
针对药品规格中单位写法不统一的问题(如"毫克""MG""g"),normalize_unit 函数将其统一标准化为 mg/g/ml;若仅包含包装单位(如"72粒"),则返回 None,明确区分容量单位与包装单位。
2) 正则解析
采用多套正则模式组合,覆盖不同规格写法的解析场景:
pattern:匹配"数值+单位+分隔符(*、× 等)+数量"的标准格式;single_pattern:当标准格式匹配失败时,兜底匹配任意"数值+单位"组合;count_pattern:单独提取分隔符后的包装数量,补充解析缺失的数量信息。
3) 处理重复表头
读取 DataFrame 后,将列名与首行数据转为字符串列表并对比;若二者完全一致,判定为重复表头,自动删除该行并重置索引,确保后续计算不受冗余表头影响。
4) 单位保留或转换
脚本默认保留原始单位(mg 仍为 mg、g 仍为 g、ml 仍为 ml);若需实现单位自动转换(如 1500mg → 1.5g),可在 compute_total 函数中新增 mg ≥ 1000 时的单位转换逻辑,扩展灵活度。
四、效果与示例
对示例数据(data20251223.xlsx)执行脚本后,可得到标准化的总容量结果:
| 药品名称 | 药品规格 | 容量/单位包装(计算后) |
|---|---|---|
| 药1 | 0.52g*72粒 | 37.44g |
| 药2 | 0.36g*45片 | 16.2g |
| 药3 | 36mg*40片 | 1440mg |
| 药4 | 5g*12袋 | 60g |
| 药5 | 100ml*1瓶 | 100ml |
| 药6 | 5g*12袋 | 60g |
| 药7 | 5g*12袋 | 60g |
| 药8 | 0.3g*100片 | 30g |
| 药9 | 20ml*10支 | 200ml |
| 药10 | 0.3g*60粒 | 18g |
若原始 Excel 存在重复表头行(某行数据与列名完全一致),脚本会自动剔除该行,避免写回文件时出现双表头问题。
五 、完整源代码,可直接运行
运行前需安装依赖:
pip install pandas openpyxl
python
import re
import pandas as pd
# 读取Excel文件,默认第一行为列名
df = pd.read_excel('data20251223.xlsx')
# 检测并删除重复表头行(若首行数据与列名完全一致)
if not df.empty:
cols = list(df.columns.astype(str))
first_row = df.iloc[0].astype(str).tolist()
if first_row == cols:
df = df.iloc[1:].reset_index(drop=True)
# 定义正则匹配模式
pattern = re.compile(r'([0-9]+(?:\.[0-9]+)?)\s*([a-zA-Z\u4e00-\u9fa5μμgmgmlMG]+)\s*[×xX\*\·]?\s*([0-9]+)')
single_pattern = re.compile(r'([0-9]+(?:\.[0-9]+)?)\s*([a-zA-Zμμ\u4e00-\u9fa5]+)')
count_pattern = re.compile(r'[\*×xX\·]\s*([0-9]+)')
def normalize_unit(u):
"""标准化单位:转换为mg/g/ml,包装单位返回None"""
if not isinstance(u, str):
return None
u = u.lower().replace('.', '.').replace('/', '/').strip()
if 'mg' in u or '毫克' in u:
return 'mg'
if 'g' in u or '克' in u:
return 'g'
if 'ml' in u or '毫升' in u:
return 'ml'
if any(x in u for x in ['粒', '片', '袋', '支', '瓶', '盒']):
return None
return u
def compute_total(spec):
"""解析药品规格字符串,返回计算后的总容量字符串"""
if not isinstance(spec, str):
return ''
# 统一分隔符格式
spec = spec.replace('×', '*').replace('X', '*').replace('x', '*')
# 提取包装数量
m_count = count_pattern.search(spec)
count = int(m_count.group(1)) if m_count else None
# 匹配标准规格格式
m = pattern.search(spec)
if m:
value = float(m.group(1))
raw_unit = m.group(2)
unit = normalize_unit(raw_unit)
# 若匹配到的是包装单位,兜底匹配单剂量+容量单位
if unit is None:
m2 = single_pattern.search(spec)
if m2:
value = float(m2.group(1))
unit = normalize_unit(m2.group(2))
# 单位仍无效则返回空值
if unit is None:
return ''
# 补充包装数量(未提取到时从数字中兜底)
if count is None:
nums = re.findall(r'([0-9]+)', spec)
count = int(nums[-1]) if len(nums) >= 2 else 1
# 计算总容量并格式化
total_val = value * count
if unit in ['mg', 'g', 'ml']:
# 整数去小数位,非整数保留6位精度
display_val = int(total_val) if total_val.is_integer() else round(total_val, 6)
return f"{display_val}{unit}"
return f"{int(total_val) if total_val.is_integer() else round(total_val, 6)}{unit}"
else:
# 标准格式匹配失败,兜底匹配单剂量+单位
m2 = single_pattern.search(spec)
if m2:
value = float(m2.group(1))
raw_unit = m2.group(2)
unit = normalize_unit(raw_unit)
if unit is None:
return ''
# 补充包装数量
if count is None:
nums = re.findall(r'([0-9]+)', spec)
count = int(nums[-1]) if len(nums) >= 2 else 1
total_val = value * count
display_val = int(total_val) if total_val.is_integer() else round(total_val, 6)
return f"{display_val}{unit}"
return ''
# 应用计算函数生成新列
df['容量/单位包装(计算后)'] = df['药品规格'].apply(compute_total)
# 写回Excel文件(不保留索引)
df.to_excel('data20251223_with_total_fixed.xlsx', index=False)
print("处理完成,已生成文件:data20251223_with_total_fixed.xlsx")
本脚本聚焦医药仓储场景的实际痛点,将非结构化的药品规格字符串转化为标准化的可计算格式,同时解决了 Excel 数据读取中的格式异常问题。脚本具备良好的扩展性,可根据实际业务需求调整单位转换规则、扩充规格解析范围。