看到办公室财务同事审核发票报销单,一张一张的手动填进表格里,每次一弄就是一天。 领导问我有没有什么办法。查了一些资料,用百度云OCR文字识别接口+Python做了一套程序。

实现了拍摄的发票照片和保存的发票PDF批量识别,

目前稳定运行了两个月,

效率从原来耗时1-2天,提升到5分钟左右

程序打包后的exe文件,需要可以自取

 程序下载链接:https://pan.quark.cn/s/8de38589e9be

一、背景:财务同事的"发票地狱"

在我们公司,财务部每月都要处理大量发票报销单。每次报销季,财务同事都要:

  1. 手动录入:一张一张查看发票信息

  2. Excel填表:把发票号、金额、日期等数据敲进表格

  3. 人工核对:检查金额、税率是否匹配

  4. 重复劳动:每月重复相同流程,耗时1-2天

痛点:

  • 效率低:几百张发票手动录入,费时费力

  • 易出错:人工输入难免手误,导致后续对账麻烦

  • 重复劳动:每月都在做同样的事情,毫无技术含量


二、解决方案:Python + OCR自动化识别

1. 技术选型

✅ 百度云OCR(高精度发票识别API)
✅ Python(自动化处理+Excel操作)

2. 系统架构

📂 发票自动化系统
├── 📜 main.py         # 主程序(OCR调用+数据处理)
├── 📜 config.txt      # API密钥等配置

3. 核心功能

  1. OCR识别:自动提取发票信息

  2. 数据校验:自动检查金额、税率是否合规

  3. Excel导出:生成结构化数据,可直接导入财务系统

  4. 日志记录:记录处理结果,方便后续排查

三、效果对比

指标传统方式自动化系统提升效果
处理时间1-2天5分钟95%+
准确率人工易出错99%+大幅提高
人力成本需专人处理无人值守解放人力
可扩展性难以批量处理支持1000+张弹性扩容

实际运行效果:

  • 2个月0出错,财务同事再也不用加班录发票

  • 5分钟处理500+张发票,效率提升近百倍

  • 领导点赞,准备推广到全公司使用

四、关键技术实现

1. OCR发票识别(百度云API)

def process_image(self, image_path):
    max_retries = 10
    retry_delay = 0.2

    for attempt in range(max_retries):
        try:
            with open(image_path, 'rb') as f:
                image_data = base64.b64encode(f.read()).decode()

            if not self.access_token:
                self.get_access_token()

            current_time = time.time()
            elapsed_time = current_time - self.last_request_time
            if elapsed_time < 0.2:
                time.sleep(0.2 - elapsed_time)
            self.last_request_time = time.time()

            response = requests.post(
                url='https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice',
                headers={'Content-Type': 'application/x-www-form-urlencoded'},
                params={'access_token': self.access_token},
                data={'image': image_data},
                timeout=15
            )
            response.raise_for_status()

            result = response.json()
            if 'error_code' in result:
                if result['error_code'] == 18:
                    time.sleep(retry_delay)
                    continue
                else:
                    logger.error(f"API错误 {result['error_code']}: {result['error_msg']}")
                    return []

            return self.parse_ocr_result(result)
        except Exception as e:
            logger.error(f"处理图片失败 {image_path}: {str(e)}")
            time.sleep(retry_delay)
            continue

    logger.error(f"处理图片失败 {image_path}: 达到最大重试次数")
    return []

2. 数据清洗 & Excel导出

def save_to_excel(self, data, output_path, export_detail):
    try:
        book = xlwt.Workbook(encoding='utf-8')
        sheet = book.add_sheet('发票数据')

        style = xlwt.XFStyle()
        font = xlwt.Font()
        font.name = '微软雅黑'
        font.height = 220
        style.font = font

        headers = ['源文件名', '发票类型', '发票号码', '开票日期', '购买方名称', '购买方纳税人识别号', '销售方名称',
                   '销售方纳税人识别号']
        if export_detail:
            headers += ['项目名称', '规格型号', '单位', '数量', '单价', '金额', '税率/征收率', '税额']
        headers += ['金额合计', '税额合计', '价税合计', '备注', '开票人']

        for col, header in enumerate(headers):
            sheet.write(0, col, header, style)
            sheet.col(col).width = 8000

        for row_idx, record in enumerate(data, 1):
            col = 0
            sheet.write(row_idx, col, record.get('source_file', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('invoicetype', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('invoice_no', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('invoice_date', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('purchaser', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('purchaserRegisterNum', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('seller', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('sellerRegisterNum', ''), style)
            col += 1

            if export_detail:
                sheet.write(row_idx, col, record.get('name', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('type', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('unit', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('num', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('price', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('amount', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('rate', ''), style)
                col += 1
                sheet.write(row_idx, col, record.get('tax', ''), style)
                col += 1

            sheet.write(row_idx, col, record.get('total_amount', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('total_tax', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('amount_infiguers', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('remarks', ''), style)
            col += 1
            sheet.write(row_idx, col, record.get('noteDrawer', ''), style)

        book.save(output_path)
        logger.info(f"Excel文件保存成功: {output_path}")
    except Exception as e:
        logger.error(f"保存Excel失败: {str(e)}")
        raise

3. 日志记录

def setup_logger():
    try:
        log_dir = os.getcwd()

        logger = logging.getLogger("PDFTool")
        logger.setLevel(logging.INFO)

        formatter = logging.Formatter(
            '%(asctime)s - %(levelname)s - %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )

        file_handler = logging.handlers.TimedRotatingFileHandler(
            filename=os.path.join(log_dir, 'pdf_tool.log'),
            when='midnight',
            backupCount=7,
            encoding='utf-8'
        )
        file_handler.setFormatter(formatter)

        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)

        logger.addHandler(file_handler)
        logger.addHandler(console_handler)
        return logger

    except PermissionError:
        logging.basicConfig(level=logging.INFO)
        logger = logging.getLogger("PDFTool")
        logger.warning("无法创建日志目录,将日志输出到控制台")
        return logger

程序链接:https://pan.quark.cn/s/8de38589e9be

更多推荐