乐于分享
好东西不私藏

用 OpenClaw 做「发票管家」:每月报销不再烦躁,AI 自动搞定一切

用 OpenClaw 做「发票管家」:每月报销不再烦躁,AI 自动搞定一切

本文作者 qwk2009-39348 是阿里云天池”千模百炼”全球 AI 开发者系列锦标赛 —— OpenClaw 养虾挑战赛的参赛者。其用 OpenClaw 做了「发票管家」自动化系统,让每月报销“不再烦恼”。

报销有多烦,谁报谁知道。 发票满天飞、名字对不上、分类乱成一锅粥、月底对账对到怀疑人生……直到我用阿里云的轻量应用服务器的 OpenClaw 把这一切自动化了。

一、发票报销的痛苦,每个职场人都懂

每到月底,报销就成了噩梦:

  • 邮件里全是发票附件 — 打开邮箱,发票 PDF 一个接一个,根本不知道哪些是新报销、哪些已经报过了;

  • 重命名全靠手 — “发票_20240326_001.pdf”、”发票_V2_改.pdf”……改到最后自己都分不清哪个是哪个;

  • 分类对账靠肉眼 — 办公费、交通费、餐饮费……一张张核对,眼睛都快瞎了;

  • 月底汇总要命 — 几十张发票,Excel里一行行录,录到半夜还出错。

我就想问问:为什么赚钱不容易,报销却这么难?

于是,我用 OpenClaw 做了这个 ——「发票管家」自动化系统

二、系统架构:四步走,自动化到飞起

        ┌─────────────────┐        │   IMAP邮箱监控  │        │  自动抓取发票邮件│        └────────┬────────┘                 │        ┌────────▼────────┐        │  PDF内容解析    │        │  提取关键信息   │        └────────┬────────┘                 │        ┌────────▼────────┐        │  自动重命名归档  │        │日期+金额+类型  │        └────────┬────────┘                 │        ┌────────▼────────┐        │  月底一键汇总   │        │生成报销明细表  │        └─────────────────┘

技术栈:

  • 邮件监控:Python imaplib + 阿里云企业邮箱 IMAP

  • PDF 解析:PyPDF2 / pdfplumber

  • 文件管理:Python shutil + os

  • 表格生成:openpyxl

三、核心代码:手把手详解

步骤1:登录邮箱,搜索发票邮件

importimaplibimportemailfromemail.headerimportdecode_headerdefconnect_email():    """连接阿里云企业邮箱"""    mail=imaplib.IMAP4_SSL('imap.qiye.aliyun.com',993)    mail.login('noreply@qianwk.cn','your-password')    mail.select('INBOX')    returnmaildefsearch_invoice_emails(mail,days=30):    """搜索最近30天内的发票相关邮件"""    # 搜索包含"发票"关键词的邮件    status,messages=mail.search(None,        'SUBJECT "发票" SINCE "-30d" UNSEEN')    email_ids=messages[0].split()    print(f'找到 {len(email_ids)} 封发票相关邮件')    returnemail_ids

步骤2:下载并解析 PDF 发票内容

importpoplibimportosfromdatetimeimportdatetimedefdownload_invoice_attachment(mail,email_id,save_dir='/tmp/invoices'):    """下载发票附件"""    os.makedirs(save_dir,exist_ok=True)    status,msg_data=mail.fetch(email_id,'(RFC822)')    raw_email=msg_data[0][1]    msg=email.message_from_bytes(raw_email)    forpartinmsg.walk():        ifpart.get_content_type()=='application/pdf':            filename=part.get_filename()            iffilenameand'发票'in filename:                filepath=os.path.join(save_dir,filename)                withopen(filepath,'wb')as f:                    f.write(part.get_payload(decode=True))                print(f'下载发票: {filename}')                return filepath    return None

步骤3:解析 PDF,提取发票关键信息

importpdfplumberdefparse_invoice_pdf(pdf_path):    """从PDF发票中提取关键信息"""    withpdfplumber.open(pdf_path)as pdf:        text=''        for page in pdf.pages:            text+=page.extract_text()or''    # 用正则表达式提取关键字段    info= {}    # 发票号码    match=re.search(r'发票号码[::]\s*(\d+)',text)    if match:        info['invoice_no']=match.group(1)    # 开票日期    match=re.search(r'(\d{4})年(\d{1,2})月(\d{1,2})日',text)    if match:        info['date']=f"{match.group(1)}-{match.group(2).zfill(2)}-{match.group(3).zfill(2)}"    # 购买方名称    match=re.search(r'购买方[::]\s*([^\n]+)',text)    if match:        info['buyer']=match.group(1).strip()    # 销售方名称    match=re.search(r'销售方[::]\s*([^\n]+)',text)    if match:        info['seller']=match.group(1).strip()    # 价税合计金额    match=re.search(r'价税合计[((]小写[))][::\s]*[¥¥]?\s*([\d.]+)',text)    if match:        info['total']=float(match.group(1))    # 税率    match=re.search(r'税率[::]\s*(\d+)%',text)    if match:        info['tax_rate']=int(match.group(1))    # 项目名称    match=re.search(r'项目名称[::]\s*([^*\n]+)',text)    if match:        info['item']=match.group(1).strip()    returninfo

步骤4:自动重命名,按规则归档

importshutilfrompathlibimportPathdefrename_and_archive(invoice_info,pdf_path,archive_base='/tmp/invoices_archive'):    """    根据发票信息自动重命名并归档    命名规则:日期_金额_购买方_发票号码.pdf    """    date=invoice_info.get('date','未知日期').replace('-','')    total=int(invoice_info.get('total',0)*100)  # 转为分,避免浮点    buyer=invoice_info.get('buyer','未知')[:10]  # 限制长度    invoice_no=invoice_info.get('invoice_no','未知')[-8:]    # 生成新文件名    new_name=f"{date}_{total}_{buyer}_{invoice_no}.pdf"    # 按年月分类归档    year_month=date[:6]    archive_dir=Path(archive_base)/year_month    archive_dir.mkdir(parents=True,exist_ok=True)    dest_path=archive_dir/new_name    shutil.copy2(pdf_path,dest_path)    print(f'归档完成: {new_name}')    return dest_path

步骤5:月底一键生成报销汇总表

importopenpyxlfromopenpyxl.stylesimportFont,Alignment,PatternFillfromdatetimeimportdatetimedefgenerate_monthly_report(archive_base='/tmp/invoices_archive',year_m):    """生成月度报销汇总Excel表格"""    archive_dir=Path(archive_base)/year_month    ifnotarchive_dir.exists():        print(f'目录不存在: {archive_dir}')        return    wb = openpyxl.Workbook()    ws = wb.active    ws.title = f'{year_month}报销明细'    # 表头样式    headers= ['序号''日期''发票号码''购买方''销售方''项目''金额''税率''税额''价税合计']    forcol,headerinenumerate(headers,1):        cell=ws.cell(row=1,column=col,value=header)        cell.font=Font(bold=True,color='FFFFFF')        cell.fill=PatternFill(start_color='4CAF50',end_color='4CAF50',fill_type='solid')        cell.alignment=Alignment(horiz)    # 填充数据    row=2    total_amount=0    forpdf_fileinsorted(archive_dir.glob('*.pdf')):        info=parse_invoice_pdf(str(pdf_file))        tax=round(info.get('total',0)*info.get('tax_rate',0)/100,2)        total=info.get('total',0)+tax        ws.cell(row=row,column=1,value=row-1)        ws.cell(row=row,column=2,value=info.get('date',''))        ws.cell(row=row,column=3,value=info.get('invoice_no',''))        ws.cell(row=row,column=4,value=info.get('buyer',''))        ws.cell(row=row,column=5,value=info.get('seller',''))        ws.cell(row=row,column=6,value=info.get('item',''))        ws.cell(row=row,column=7,value=info.get('total',0))        ws.cell(row=row,column=8,value=f"{info.get('tax_rate',0)}%")        ws.cell(row=row,column=9,value=tax)        ws.cell(row=row,column=10,value=total)        total_amount+=total        row+=1    # 合计行    ws.cell(row=row,column=1,value='合计')    ws.cell(row=row,column=10,value=total_amount)    ws.cell(row=row,column=10).font=Font(bold=True)    # 设置金额格式    forrinrange(2,row+1):        forcolin [7910]:            ws.cell(row=r,column=col).number_format='¥#,##0.00'    # 调整列宽    ws.column_dimensions['A'].width=8    ws.column_dimensions['B'].width=12    ws.column_dimensions['C'].width=18    ws.column_dimensions['D'].width=20    ws.column_dimensions['E'].width=20    ws.column_dimensions['F'].width=25    ws.column_dimensions['G'].width=12    ws.column_dimensions['H'].width=8    ws.column_dimensions['I'].width=12    ws.column_dimensions['J'].width=14    output_path=f'{archive_base}/{year_month}_报销汇总.xlsx'    wb.save(output_path)    print(f'报销汇总表已生成: {output_path}')    print(f'本月共 {row-2} 张发票,总金额:¥{total_amount:.2f}')    returnoutput_path

四、运行效果

发票归档效果

/tmp/invoices_archive/├──202603/│   ├──20260324_19700_上海xxxxxx_8905066.pdf│   ├──20260324_21400_上海xxxxxx_1129182.pdf│   └──...

报销汇总表效果

五、核心价值:时间就是生命

六、进阶玩法

  • 关键词过滤:只抓取特定主题的发票(如”项目报销”、”差旅费”)

  • 微信推送:汇总表生成后自动推送到微信

  • OCR识别:对扫描件图片发票也支持识别

  • 多邮箱支持:同时监控多个邮箱,统一汇总

报销本不该占用这么多时间。 用 OpenClaw 把繁琐的事交给 AI,你的时间应该用在更有价值的事情上。

技术栈:阿里云轻量应用服务器 OpenClaw + 阿里云企业邮箱 IMAP + Python PDF解析 + openpyxl


想要亲自上手搭建 Agent ?一键部署 Hermes & OpenClaw 传送门:

https://www.aliyun.com/activity/ecs/clawdbot

更多优秀作品场景化展示,将陆续发布,敬请期待。

/END/