用 Python 自动解析药品规格并计算包装总容量 —— pandas + 正则实战

在医疗药房管理、药品库存统计等业务场景中,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 读取时的重复表头问题(如文件内存在两行表头、导出异常导致数据行与表头重复),避免写回文件时出现冗余标题行。

二、实现思路概览

  1. 数据读取:通过 pandas 读取 Excel 文件并转换为 DataFrame 结构;
  2. 重复表头处理:检测并删除与列名完全一致的首行数据,解决重复表头问题;
  3. 规格解析:利用正则表达式提取"单剂量数值+容量单位"及"包装数量";
  4. 单位标准化:统一转换 mg/g/ml 相关的中英文/大小写写法,过滤粒、片等包装单位;
  5. 总容量计算:基于解析的数值和单位计算总容量,并格式化输出结果;
  6. 结果输出:将计算结果写入新的 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 数据读取中的格式异常问题。脚本具备良好的扩展性,可根据实际业务需求调整单位转换规则、扩充规格解析范围。

相关推荐
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-10-装饰器模式
java·开发语言·装饰器模式
zzoood2 小时前
【PHP】富文本编辑器图片自动追加域名
开发语言·php
林shir2 小时前
Java基础1.4-运算符
java·开发语言
serendipity_hky2 小时前
【go语言 | 第6篇】Go Modules 依赖解决
开发语言·后端·golang
python机器学习ML2 小时前
论文复现-以动物图像分类为例进行多模型性能对比分析
人工智能·python·神经网络·机器学习·计算机视觉·scikit-learn·sklearn
沃斯堡&蓝鸟2 小时前
DAY30 函数专题1:函数定义与参数
python
小oo呆2 小时前
【学习心得】Python的TypedDict(简介)
开发语言·python
文洪涛2 小时前
VS Code Python “第一次运行失败 / 先执行 python 再激活 Conda” 问题定位与解决
开发语言·python·conda
wanghowie3 小时前
01.08 Java基础篇|设计模式深度解析
java·开发语言·设计模式