电子发票太多,想统计下总额异常困难,网上工具不好用,花了2个小时实现一份,测试过中石油、京东开具的电子发票还行,部分发票名称失败有问题不影响统计,有需要的小伙伴自己拿去改吧。


import cmd
import sys
import json
import pdfplumber
import os
from pprint import pprint class FapiaoShell(cmd.Cmd):
""" 发票 """ intro = '欢迎使用发票提取工具,输入?(help)获取帮助消息和命令列表,CTRL+C退出程序。\n'
prompt = '\n输入命令: '
doc_header = "详细文档 (输入 help <命令>):"
misc_header = "友情提示:"
undoc_header = "没有帮助文档:"
nohelp = "*** 没有命令(%s)的帮助信息 " def __init__(self):
super().__init__() def do_load(self, arg):
""" 加载发票 例如:load D:\ """
if not os.path.isdir(arg):
print('参数必须是目录!')
return os.chdir(os.path.dirname(arg))
pdfs = []
for root, _, files in os.walk(arg):
for fn in files:
ext = os.path.splitext(fn)[1].lower()
if ext != '.pdf':
continue
fpth = os.path.join(root, fn)
fpth = os.path.relpath(fpth)
print(f'发现pdf文件: {fpth}')
pdfs.append(fpth) pdf_ctxs = self._parse_pdfs(pdfs)
total = {
'内容': pdf_ctxs,
'发票数': len(pdf_ctxs),
'总计': 0,
}
for fpth, info in pdf_ctxs:
total['总计'] += float(info['总计']) print('\n保存到 结果.json...') with open("结果.json", 'w', encoding='utf-8') as json_file:
json.dump(total,
json_file,
ensure_ascii=False,
sort_keys=True,
indent=4,
separators=(', ', ': ')) print('完成!') def _parse_pdfs(self, pdfs):
""" 分析 """
result = []
for fpth in pdfs:
info = {}
with pdfplumber.open(fpth) as pdf:
page = pdf.pages[0] if '增值税电子普通发票' not in ''.join(page.extract_text()):
result.append((fpth, {})) inf = self._extrace_from_words(page.extract_words())
info.update(inf) inf = self._extrace_from_table(page.extract_tables()[0])
info.update(inf) result.append((fpth, info))
return result def _extrace_from_words(self, words):
""" 从单词中提取 """
info = {} lines = {}
for word in words:
top = int(word['top'])
bottom = int(word['bottom'])
pos = (top + bottom) // 2
text = word['text']
if pos not in lines:
lines[pos] = [text]
else:
lines[pos].append(text) lines_pack = []
last_pos = None
for pos in sorted(lines):
arr = lines[pos] if len(lines_pack) > 0 and pos - last_pos <= 10:
lines_pack[-1] += arr
continue lines_pack.append(arr)
last_pos = pos
continue for pack in lines_pack:
for idx, line in enumerate(pack):
if '电子普通发票' in line:
info['标题'] = line
continue if '发票代码:' in line:
info['发票代码'] = line.split(':')[1]
continue if '发票号码:' in line:
info['发票号码'] = line.split(':')[1]
continue if '开票日期:' in line:
year = line.split(':')[1]
month = [ln for ln in pack if ln.isdigit()][0]
day = [ln[:2] for ln in pack if '日' in ln][0]
info['开票日期'] = f'{year}-{month}-{day}'
continue if '机器编号:' in line:
info['机器编号'] = [ln for ln in pack if ln.isdigit()
and len(ln) > 10][0]
continue if '码:' in line:
c1 = pack[idx].split(':')[1]
c2 = pack[idx+1]
c3 = pack[idx+2]
c4 = pack[idx+3]
info['校验码'] = f'{c1} {c2} {c3} {c4}'
continue if '收款人:' in line:
info['收款人'] = line.split(':')[1]
continue if '开票人:' in line:
info['开票人'] = line.split(':')[1]
continue return info def _extrace_from_table(self, table):
""" 从表中提取 """
info = {}
if len(table) != 4:
return None # 购买方
for cell in table[0]:
if not cell:
continue lines = cell.splitlines()
for line in lines:
if '名 称:' in line:
info['购买方名称'] = line.split(':')[1]
continue if len(line) == 18 and line.isalnum():
info['购买方税号'] = line
continue if len(line) == 27:
if '密码' not in info:
info['密码'] = []
info['密码'].append(line)
continue # 详细
for cell in table[1]:
if not cell:
continue lines = cell.splitlines()
for line in lines:
if '货物或应税劳务、服务名称' in line:
info['商品'] = lines[1:-1]
break if '金 额' in line:
info['总金额'] = lines[-1][1:]
break if '税 额' in line:
info['总税额'] = lines[-1][1:]
break # 合计
for cell in table[2]:
if not cell:
continue lines = cell.splitlines()
for line in lines:
if '¥' in line:
info['总计'] = line[1:] # 销售方
for cell in table[3]:
if not cell:
continue lines = cell.splitlines()
for line in lines:
if '名 称:' in line:
info['销售方名称'] = line.split(':')[1]
continue if len(line) == 18 and line.isalnum():
info['销售方税号'] = line
continue return info if __name__ == '__main__':
try:
FapiaoShell().cmdloop()
except KeyboardInterrupt:
print('\n\n再见!')

分享一个电子发票信息提取工具(Python)的更多相关文章

  1. 分享一个获取代理ip的python函数

    分享一个获取代理ip的python函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #coding:utf-8 from bs4 import Beaut ...

  2. 分享一个刷网页PV的python小脚本

    下面分享一个小脚本,用来刷网页PV. [root@huanqiu ~]# cat www.py #!/usr/bin/python# coding: UTF-8import webbrowser as ...

  3. 分享一个内网穿透工具frp

    首先简单介绍一下内网穿透: 内网穿透:通过公网,访问局域网里的IP地址与端口,这需要将局域网里的电脑端口映射到公网的端口上:这就需要用到反向代理,即在公网服务器上必须运行一个服务程序,然后在局域网中需 ...

  4. 工具类分享之获取Request/Response工具类《RequestContextHolderUtil》

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/aiyaya_/article/details/78975893前言在开发spring web项目时, ...

  5. 用 Python 制作一个艺术签名小工具,给自己设计一个优雅的签名

    生活中有很多场景都需要我们签字(签名),如果是一些不重要的场景,我们的签名好坏基本无所谓了,但如果是一些比较重要的场景,如果我们的签名比较差的话,就有可能给别人留下不太好的印象了,俗话说字如其人嘛,本 ...

  6. 利用 Python 写一个颜值测试小工具

    我们知道现在有一些利用照片来测试颜值的网站或软件,其实使用 Python 就可以实现这一功能,本文我们使用 Python 来写一个颜值测试小工具. 很多人学习python,不知道从何学起.很多人学习p ...

  7. 分享一个开源的网盘下载工具BaiduPCS-Go

    大家在使用网盘的时候,一定忍受不了限速下载的速度.今天给大家分享一个开源的网盘下载项目BaiduPCS-Go.Go语言编写,仿 Linux shell 文件处理命令的百度网盘命令行客户端.多平台支持, ...

  8. 分享一个Snackbar工具类 SnackbarUtils;

    分享一个Snackbar工具类,源代码也是在Github上面找的,自己做了一下修改: 功能如下: 1:设置Snackbar显示时间长短                 1.1:Snackbar.LEN ...

  9. [W3bsafe]分享一个爬SQL注入漏洞的工具

    分享一个爬SQL注入的工具 本文转自:i春秋社区由团队核心成员若间开发把工具放到E盘的一个文件夹 他会自动生成一个文本文件 Result.txt  最大页数 自己想弄填多少就填多少关键词 注入点关键词 ...

随机推荐

  1. c++ template 判断是否为类类型

    /* The following code example is taken from the book * "C++ Templates - The Complete Guide" ...

  2. Mybatis之是如何执行你的SQL的(SQL执行过程,参数解析过程,结果集封装过程)

    Myabtis的SQL的执行是通过SqlSession.默认的实现类是DefalutSqlSession.通过源码可以发现,selectOne最终会调用selectList这个方法. @Overrid ...

  3. kbmMWtable for XE5 接近尾声

    为了支持多平台开发的delphi XE5,kbmmwtable 做了非常大的改动. 目前已经可以在ios 和android 上建立和查询数据表了,但是众说周知,在ios 和android 上 使用Li ...

  4. event.preventDefault() vs. return false

    使用jquery方式的话,以下是等效的 return false === event.stopPropagation + event.preventDefault() //1. event.preve ...

  5. Linux设备驱动模型底层架构及组织方式

    1.什么是设备驱动模型? 设备驱动模型,说实话这个概念真的不好解释,他是一个比较抽象的概念,我在网上也是没有找到关于设备驱动模型的一个定义,那么今天就我所学.所了解 到的,我对设备驱动模型的一个理解: ...

  6. Shell编程-03-Shell中的特殊变量和扩展变量

    目录 特殊变量 变量扩展 特殊变量     在Shell中的特殊变量主要分别两种位置参数变量.状态变量两种. 位置参数变量     Shell中的位置参数变量主要是指$0.$1.$#等,主要用于从命令 ...

  7. .NET 匿名方法的BUG,请专家解答

    匿名方法是.NET 3.5之后的一个好东东,很多人使用,但是我在最近的工作当中发现了一个问题. 请专家解答 //list里存放10个数字 List<); ; i < ; i++) { li ...

  8. unity 序列化和反序列化

    什么是序列化和反序列化(1)序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程:.  (2)序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可 ...

  9. UDP通讯

    上一篇有说到TCP通讯,这篇来谈谈UDP通讯方式 基于Udp协议是无连接模式通讯,占用资源少,响应速度快,延时低.至于可靠性,可通过应用层的控制来满足.(不可靠连接) (1).建立一个套接字(Sock ...

  10. 【OCP认证12c题库】CUUG 071题库考试原题及答案(27)

    27.choose two The SQL statements executed in a user session are as follows: SQL> CREATE TABLE pro ...