用fitz+pandas实现PDF发票批量解析(含字段清洗与差额计算)(附源码|白嫖救发)财务小姐姐秃力觉醒!

99 阅读9分钟

🎯《讨三大科班檄文》🔥 「科班编程圈视我为异端🤖,科班中医圈骂我叛经离道🌿,今天本怪蜀黎剑指财务圈💸—— 尔等守规矩的绵羊🐑,岂知野狼屠场的快意🐺?

老夫乃:

⚕️编程界的赤脚医生——不用设计模式,只开偏方代码💊

🛡️中医界的码农哨兵——敢把《黄帝内经》编译成Python🐍

💰财务界的ERP土匪——抢发票数据如探囊取物🏴‍☠️

懵逼否?😵 愤怒否?💢 要的就是你们看不懂又干不掉我的样子!😎」
[-------------------------------------------------------------------------------]
💥「财务小姐姐们别再被发票收割了!本怪蜀黎开仓放粮!🎁源码复制就能用」

10年ERP老兵教你用野路子羞辱科班财务,小姐姐速来白嫖救发神器!

——By 冷溪虎山|ERP老兵·中药医疗仓幽灵哨兵 「10年ERP特种兵用野路子拯救你的发际线👴,以《神农本草经》的哲学根治数字便秘💩」

⚔️战术核心:fitz库直捣PDF黄龙🐉,正则表达式如游击弩箭🎯精准截杀数据


[-------------------------------------------------------------------------------]
🐍野路子精髓:

文件名暗藏金额玄机(如人参185元.pdf),一招re.search破敌于无形🕵️

税率、公司名、货物名称三路包抄,专治发票排版「玄学阵法」🧙
[-------------------------------------------------------------------------------]
🤵ERP老兵的狡猾:

自动计算「文件名金额vs发票金额」差额📉,防偷梁换柱

过滤银行关键词🏦,避免误伤友军

⚠️「此术仅限PDF屠龙🐉,图片版乃倚天剑⚔️——待本怪蜀黎淬火打磨后再献与卿! (目前图片OCR识别如中医把脉🩺,需人工干预方能药到病除💊)(图片识别收费API和AI识别稍好)」 坐标方法抓捕我测试过,代码调试难度大,后期
[-------------------------------------------------------------------------------]
😂名场面来了: 老板出差回来捣鼓发票🧾,我看得脑壳疼☠️,又怕伤他自尊…结果他整错俩😂! 为啥我验证快?🐍Python识别发票登场了!

Python源码 我的Python版本3.12.7

import fitz
import re
import pandas as pd
import glob
from pathlib import Path


def extract_amount_from_filename(filename):
    """从文件名中提取金额(如'人参185元.pdf'返回185)"""
    match = re.search(r'(\d+(?:\.\d+)?)元', filename)
    return float(match.group(1)) if match else None


def process_single_pdf(pdf_path):
    """处理单个PDF文件"""
    # 初始化列表
    dates = []
    company_names1 = []  # 包含银行
    goods_services = []
    prices = []
    tax_rates = []

    # 从文件名提取金额
    filename_amount = extract_amount_from_filename(Path(pdf_path).name)

    doc = fitz.open(pdf_path)
    for page in doc:
        text = page.get_text()

        # 1. 提取日期   逻辑可以自己拓展
        date_match = re.search(r"开票日期[::]\s*(\d{4}[年\-/]\d{1,2}[月\-/]\d{1,2}日?)", text) or \
                     re.search(r"\s*(\d{4}[年\-/]\d{1,2}[月\-/]\d{1,2}日?)", text)
        if date_match:
            dates.append(date_match.group(1))

        # 2. 提取公司名称(保持不变) 逻辑可以自己拓展
        companies = re.findall(r"([^\n::]*[公司经营部][^\n::]*)", text) or \
                    re.findall(r"名\s*称[::]\s*([^\n]+)", text)
        if companies:
            company_names1.extend([c.strip() for c in companies if len(c) > 2])

        # 3. 提取货物或服务名称(保持不变)逻辑可以自己拓展
        goods = re.findall(r"\*[\w\u4e00-\u9fa5]+\*[\w\u4e00-\u9fa5]+", text) or \
                re.findall(r"\s*([^\n]+)", text)
        if goods:
            goods_services.extend(goods)

        # 4. 提取价格(获取发票中最大的金额作为对比基准)逻辑可以自己拓展
        amount_matches = re.finditer(r"[$¥¥]\s*(\d+\.?\d*)", text) or \
                         re.finditer(r"¥\s*(\d+\.?\d*)", text)
        if amount_matches:
            for match in amount_matches:
                amount = float(match.group(1))
                if amount > 1:  # 过滤掉小于等于1的金额
                    prices.append(amount)

        # 5. 提取税率(保持不变)逻辑可以自己拓展
        taxes = re.findall(r"\s*(\d+\.?\d*)%", text) or \
                re.findall(r"税额[::]\s*¥?\d+\.?\d*\s*\((\d+)%\)", text)
        if taxes:
            tax_rates.extend([f"{tax}%" for tax in taxes])

    doc.close()

    # 数据清洗(保持不变) 逻辑可以自己拓展
    company_names = list(filter(lambda x: "银行" not in x and "其他" not in x, company_names1))
    company_names = list(set(company_names))  # 去重
    prices = sorted(prices, reverse=True)  # 金额从大到小排序

    # 计算差额(文件名金额 - 发票最大金额) 逻辑可以自己拓展
    price_diff = None
    if filename_amount and prices:
        price_diff = round(filename_amount - max(prices), 2)

    # 转换为DataFrame 逻辑可以自己拓展
    max_len = max(len(dates), len(company_names), len(goods_services), len(prices), len(tax_rates))
    data = {
        "文件路径": str(pdf_path),
        "发票命名金额提取": [filename_amount] + [None] * (max_len - 1),
        "差额验证": [price_diff] + [None] * (max_len - 1),
        "日期": dates + [None] * (max_len - len(dates)),
        "公司名称": company_names + [None] * (max_len - len(company_names)),
        "货物或服务名称": goods_services + [None] * (max_len - len(goods_services)),
        "价格": prices + [None] * (max_len - len(prices)),
        "税率": tax_rates + [None] * (max_len - len(tax_rates))
    }

    return pd.DataFrame(data)


# 主程序 逻辑可以自己拓展
if __name__ == "__main__":
    pdf_files = glob.glob("./BOSS7月出差/7月发票/*.pdf")  #PDF路径设置改这行,这里用显式相对路径,你们可改成绝对路径
    if not pdf_files:
        print("未找到PDF文件!")
        exit()

    base_df = process_single_pdf(pdf_files[0])
    print(f"已处理: {Path(pdf_files[0]).name}")

    for pdf_file in pdf_files[1:]:
        try:
            new_df = process_single_pdf(pdf_file)
            base_df = pd.concat([base_df, new_df], ignore_index=True)
            print(f"已处理: {Path(pdf_file).name}")
        except Exception as e:
            print(f"处理失败: {pdf_file} - {str(e)}")

    # 保存结果  输出路径 改成自己路径单反斜杆\记得加r,否则必加双反斜杆\\,我这里为了省事用了相对路径
    base_df.to_excel("批量提取BOSS出差发票数据.xlsx", index=False)
    print(f"\n处理完成!共处理 {len(pdf_files)} 个PDF")

[--------------------------------------------------------------]以下是运行截图 源码扫描发票 文件名扫描金额提取,其他发票项目提取 excel扫描结果

💥关键错的那个还是人参😂!老板气虚买药材,莫名撞我野路子领域🌿… (老板买的什么药,我反向快速推断🔍…这里不展开了,老板自尊心受30倍暴击💔) ——本怪蜀黎凭借《神农本草经》野路子,一眼看穿他买的啥药方子🧪 (但我不敢说,怕他下次给我穿小鞋👞)」

人参

⛑️温馨提示: AI写的复杂正则准确率不高❌,建议自己夯实基础📚! (普通正则➡️国际正则,末尾有我的正则文章📖👇)

🏢实力晒图: 以下是我亲自操刀做账的公司💼,学会精髓后10几家量大账随便扛🦾! (但建议挑账不混乱的公司接✅,太乱的纠错费时间⏳)

做账截图

👩💼财务小姐姐需特别注意的亮点👇:

price_diff = round(filename_amount - max(prices), 2)  # 金额差额自检,防财务失血🩸
company_names = list(filter(lambda x: "银行" not in x, company_names1))  # 剔除银行干扰🏦

[------------------------------------------------------------------]
⚠️重要提示一:Python坐标抓捕法实测劝退 「本人亲测:用坐标定位提取发票数据(如page.search_for()+矩形框抓捕)🧭 ——代码调试难度极大🔧,肉眼对齐像素点看到头晕👁️,且发票格式一变直接崩盘💥 结论:正则虽野,但专治各种排版花里胡哨!」

[------------------------------------------------------------------]
📊重要提示二:Excel终极懒人包 「本脚本已实现: ✅ 文件名自动解析(如人参185元.pdf → 提取185) ✅ 文件名金额 vs 发票金额自动比对(防错差) ✅ 价税分离(税率单独提取) ✅ 公司名+货物名清洗(过滤银行干扰项) 👉输出Excel后——筛选即可用!零手工操作!」 👉若遇复杂发票,优先用文件名金额+最大金额双校验,人工兜底!

💡终极建议: 「财务人先用本脚本跑批量✅,再人工抽查关键大额发票🔍 ——效率提升90%!发际线保住了!💇‍♀️」 [------------------------------------------------------------------]

🎯嘲讽梗三连击:

对科班编程圈:「你们用设计模式,我用偏方代码——但老板只认结果✅」

对科班财务圈:「证书堆成山,不如我代码跑一遍⏰」

对科班中医圈:「把脉看气虚?我直接看发票就知道老板买了人参枸杞😂」

🚀「本怪蜀黎野生野长🌱,专治各种『科班不服』🎓」 (欢迎跨界追杀🏃,横竖你们也追不上我的发际线👴)」

[------------------------------------------------------------------]
🌟本蜀黎功德量化报告(走心加料版):
✅ 拯救发际线:100+条(每避免手动录入1张发票=挽救0.01根头发💇‍♀️,按此推算已守护一片黑森林🌳)

✅老肝能量释放:200+小时 (财务圈集体提前下班⏰,陪对象/娃/猫时间++,家庭和谐度直接拉满🏡)

✅拯救职场爱情:N对 (程序猿借代码秀操作撩动财务妹❤️,成就“发票姻缘”佳话—— “那年报销季,TA用Python帮我验票…”)

✅欢乐普渡:功德无量 (各位打工人看完本座故事,欢乐指数++++📈,摸鱼时间笑声溢出屏幕😂, 建议HR纳入员工心理健康福利方案)
————————————————

⚠️ 免责声明(附因果律警告)

本代码已注入中医玄学能量,请谨慎使用:

  • ✅ 允许白嫖,但白嫖不点赞可能导致:
    • 下次面试官恰好问到这个算法
    • 键盘自动打出//这里感谢冷溪虎山老中医
    • 奶茶精准洒在刚写好的代码上
  • ✅ 允许商用,但商用不注明出处可能触发:
    • 产品上线前夜突然出现递归栈溢出
    • 数据库莫名存储君臣佐使字段
  • ✅ 允许吐槽,但吐槽不带改进建议可能引发:
    • 终生与边界条件相爱相杀

🚀 现在立即行动:

  1. 点赞 → 吸收本篇算法精华+怪蜀黎脑洞思维
  2. 收藏 → 避免日后求医无门
  3. 关注 → 接收更多「中医+代码」脑洞
  4. 评论区留言 → 领取你的专属「算法药方」

如有不对之处,欢迎评论区批评指出或者留言给我!✅✅

如果这份文章帮到了你,请点赞、收藏、关注三连!你们的支持,就是我继续‘炼丹’的动力🏆🏆!

✨碰到 其他卡顿问题| 其他数据抓取"正则"匹配问题? JetBrains 全家桶性能优化 ,点击以下链接👇👇直达其他爆款指南:

从ASCII到Unicode:"国际正则"|"表达式"跨国界实战指南(附四大语言支持对比+中医HIS类比映射表) - 掘金

"正则"|"表达式"?面试题必考的转义符/量词大全!这张表让我告别面试翻车(附记忆口诀->映射表)正则转义符映射表 “每 - 掘金

[---------------------------------------------------------------------------------------] ✨碰到其他卡顿问题? JetBrains 全家桶性能优化共 6 篇,点击以下链接👇👇直达其他爆款指南

IDEA 性能炸裂!手把手拆解我的 9GB 堆内存+G1GC 调参表(附详细注释,小白慎改)类别 参数 值 作用解析 适 - 掘金

PyCharm 卡成 PPT?Python 开发者必藏的 vmoptions 调优表(9GB 堆内存+JVM 终极配置,效率翻倍) - 掘金

WebStorm 卡成幻灯片?前端大佬都在偷学的 vmoptions 调优表(续集来了!IDEA/PyCharm 飞升后,轮到它起飞) - 掘金

GoLand 卡成幻灯片?Gopher 必藏的 vmoptions 调优表(续集:WebStorm 飞升后,轮到 Go 开发神器起飞) - 掘金

CLion 卡成幻灯片?C/C++ 开发者必看的 vmoptions 调优表(续集:GoLand 飞升后,轮到它起飞) - 掘金

DataGrip 用久了又卡又慢?JetBrains 家的数据库 IDE 怎么调优?看这篇就够了⚠⚠⚠根据电脑配置调整 - 掘金