乐于分享
好东西不私藏

财务/行政狂喜!用 Python 提取 PDF 发票多行明细,十几行材料瞬间「分列」进 Excel!

财务/行政狂喜!用 Python 提取 PDF 发票多行明细,十几行材料瞬间「分列」进 Excel!

全代码:那些被迫手动录入发票的深夜,其实只需要这段代码
直接下载运行:电子普票智能汇总工具 V2 exe 版
做过财务、行政或者负责报销审核的打工人,肯定都有过这样的崩溃瞬间:
收到一张 PDF 电子发票,基础信息(发票号、日期、抬头)倒好搞定,但一看商品列表——好家伙,十几行的材料明细!
项目名称、规格型号、单位、数量、单价……长短不一,有些名字还带空格。如果想把它们挨个提取到 Excel 里,常规的代码往往会把它们糊成一团,最后还是得靠人工去「抠眼珠」重新整理。
今天,就来给之前的发票提取 Python 脚本做个「史诗级升级」!只需修改 3 个核心逻辑,就能让你的代码自动将多行发票明细拆解得明明白白,每一行项目单独生成一条 Excel 数据!

已关注

关注

重播 分享


核心痛点:为何发票明细这么难提?

看一眼发票明细就发现,前面的「项目名称」和「规格型号」字数完全不固定,中间还随时夹杂着空格。用常规「从左到右」的正则表达式去匹配,特别容易翻车。

破局思路:倒着来!(逆向拆解法)

俗话说,只要思想不滑坡,办法总比困难多!仔细观察发票排版,发现无论前面商品名字怎么变,它尾部的数据格式是绝对固定的!
从右往左看,永远依次是:明细税额 -> 明细税率 -> 明细金额 -> 单价 -> 数量(且全是纯数字)。
所以,只要利用 Python 列表的 pop() 方法,从后往前「拔萝卜」,把末尾的数字依次拔走,剩下的部分自然就是项目名称和规格型号啦!


代码升级三步走,直接抄作业

第一步:重写提取逻辑,精准拆分每一列(逆向拆解)

在 extract_projects_full 函数,对每一行合并好的明细文字(combined_line)进行切割,一招「回马枪」完美解决长短不一的难题:
def extract_projects_full(text: str) -> list[dict]:    results = []    lines = [line.strip() for line in text.splitlines() if line.strip()]    i = 0    while i < len(lines):        line = lines[i]        if line.startswith('*'):            main_line = line            extra_text = ""            i += 1            # 1. 收集跨行文字            while i < len(lines) and not lines[i].startswith('*'):                if re.search(r'合\s*计|合計|总计|總計', lines[i]):                    before_total = re.split(r'合\s*计|合計|总计|總計', lines[i])[0].strip()                    extra_text += before_total                    break                extra_text += lines[i]                i += 1            # 2. 清洗主行合计尾巴            main_clean = re.split(r'合\s*计|合計|总计|總計', main_line)[0].strip()            # 3. 接驳跨行文字            if extra_text.strip():                first_space_idx = main_clean.find(" ")                target_idx = first_space_idx                if first_space_idx != -1 and first_space_idx < 15:                    pass                 else:                    second_space_idx = main_clean.find(" ", first_space_idx + 1)                    if second_space_idx != -1:                        target_idx = second_space_idx                if target_idx != -1:                    combined_line = (                        main_clean[:target_idx] +                         extra_text.strip() +                         " " +                         main_clean[target_idx + 1:]                    )                else:                    combined_line = main_clean + " " + extra_text.strip()            else:                combined_line = main_clean            # 4. 统一空格清洗            combined_line = re.sub(r'\s+'' ', combined_line).strip()            # ================= 新增:5. 逆向拆解明细字段 =================            parts = combined_line.split()            item_dict = {                "项目名称""未识别""规格型号""""单位"""                "数量""""单价""""明细金额""""明细税率""""明细税额"""            }            # 发票规范:末尾通常依次是 金额、税率、税额            if len(parts) >= 3:                item_dict["明细税额"] = parts.pop()                item_dict["明细税率"] = parts.pop()                item_dict["明细金额"] = parts.pop()            # 往前找“单价”和“数量”(特征:全数字或带小数点)            if parts and re.match(r'^-?\d+(\.\d+)?$', parts[-1]):                item_dict["单价"] = parts.pop()            if parts and re.match(r'^-?\d+(\.\d+)?$', parts[-1]):                item_dict["数量"] = parts.pop()            # 剩下的部分为:项目名称、规格型号、单位            if parts:                # 最后一个词如果不包含数字,通常是单位(如:套、台、kg、批)                if len(parts) > 1 and not re.search(r'\d', parts[-1]):                    item_dict["单位"] = parts.pop()                item_dict["项目名称"] = parts[0]                # 中间夹着的剩下的就是规格型号                if len(parts) > 1:                    item_dict["规格型号"] = " ".join(parts[1:])            results.append(item_dict)            continue        i += 1    return results

第二步:数据展平(一对多映射)

之前的逻辑是「一张发票 = Excel 里的一行」。现在有明细了,所以要变成「一张发票里的每一个项目 = Excel 里的一行」
在 process_all_invoices 函数,将发票的表头(发票号、日期、抬头等)当做「基础包」。发票有几行项目,就将这个基础包复制几次,跟明细拼在一起写进列表:
def process_all_invoices(self):        pdf_files = self.collect_pdfs()        total = len(pdf_files)        for idx, pdf_path in enumerate(pdf_files, 1):            self.log(f"解析文件: {pdf_path}")            text, tables = self.extract_text_and_tables(pdf_path)            if text:                info = self.parse_invoice_info(text, tables)                # 提取发票公用基础信息                base_info = {                    "文件名": os.path.basename(pdf_path),                    "发票号码": info["发票号码"],                    "开票日期": info["开票日期"],                    "购买方名称": info["购买方名称"],                    "购买方税号": info["购买方税号"],                    "销售方名称": info["销售方名称"],                    "销售方税号": info["销售方税号"],                    "总金额": info["金额"],       # 改名以免与明细金额冲突                    "总税额": info["税额"],       # 改名以免与明细税额冲突                    "价税合计": info["价税合计"]                }                items = info.get('_full_projects', [])                # 如果没识别到任何明细,保留一行基础信息兜底                if not items:                    empty_row = base_info.copy()                    empty_row.update({                        "项目名称": info.get("项目名称""未识别"),                         "规格型号""""单位""""数量""""单价""",                        "明细金额""""明细税率""""明细税额"""                    })                    self.invoice_list.append(empty_row)                else:                    # 将每一个项目明细作为单独的一行写入                    for item in items:                        row = base_info.copy()                        row.update(item)                        self.invoice_list.append(row)            if self.progress_callback:                self.progress_callback(idx, total)

第三步:重组 Excel 列顺序,强迫症福音

提取出来的数据如果乱七八糟可不行。在最后导出 Excel 的 save_to_excel 函数中,利用 pandas 重新定义列的展示顺序,一目了然:
# 更新列顺序:加入拆分出来的规格、单位、数量、单价ordered_cols = [    "文件名""发票号码""开票日期",    "购买方名称""购买方税号",    "销售方名称""销售方税号",    "项目名称""规格型号""单位""数量""单价""明细金额""明细税率""明细税额",    "总金额""总税额""价税合计"]# 按指定顺序排列 DataFrame,然后写入 Exceldf = df[ordered_cols]


效果展示

一键运行升级版的工具,选择发票文件夹。打开生成的 Excel——你会发现,曾经挤在一起的十几行材料,现在乖乖地变成了多行数据,各项金额、单价严丝合缝!
不管是用来做报表、还是做进销存对账,直接复制粘贴就能用,工作效率原地起飞!
【互动时间】大家在日常处理发票或者单据的时候,还遇到过什么让人抓狂的「奇葩格式」?欢迎在评论区留言,说不定下一期就用 Python 搞定它!

参考文章
电子发票 PDF 自动解析:新版代码 vs 旧文实现的深度对比
电子普票PDF自动解析:一篇给一线财务、运营的地气指南
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 财务/行政狂喜!用 Python 提取 PDF 发票多行明细,十几行材料瞬间「分列」进 Excel!

评论 抢沙发

3 + 6 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮