总任务
针对“发票样例”中的以压缩文件形式存放的发票文件
- 通过python 解压成单独的pdf文件格式的电子发票文件\
- 读取pdf文件中的信息,在mysql数据库中建立相应的数据表,将读取的信息存入数据表中\
- 建立web服务器,连接第2步得到的mysql数据库,为用户提供发票的查询服务与下载服务,将满足查询条件的多张发票打包下载\
- 批量打印满足查询条件的多张发票
(1)解压zip文件
通过python 解压成单独的pdf文件格式的电子发票文件。
通过手动解压缩发现该压缩文件里面还有压缩文件,所以需要递归的进行解压缩。
由于我们系统中只有zip格式的压缩文件,所以下面的代码只能用于解压zip格式的压缩文件,但是其他压缩类型的文件可以以此类推。
在这里面需要学会几个函数的使用:
- zipfile.ZipFile():传入文件路径,获取压缩文件内的信息
- zipfile.namelist():获取该目录下的文件夹名和文件名
- zipfile.extract():解压文件到指定目录,包括文件夹
- str.endswith():匹配文件后缀
- list_dir():列出指定路径下的文件夹名和文件名
代码笔记:https://www.jianshu.com/p/77786894f40d
(2.1)读取pdf文件并提取信息
参考博客:https://www.jianshu.com/p/65eae86116c9
读取pdf文件,使用到pdfplumber库。读取出的文本内容使用正则匹配来获取信息。使用之前需要使用pip命令安装该库。
这里匹配的是invoice文件夹中的发票信息。没有匹配invoiceDetail里面的信息。
!pip install pdfplumber
关于PDF文件的读取:
- pdfplumber.open():打开pdf文件
- pdf.pages[0]:查看第一页的内容
- first_page.extract_text():读取文本信息
这里对PDF文件信息的提取使用的是正则匹配,会用到re
库。
关于库里的函数可以参考:https://blog.csdn.net/qq_39962271/article/details/123884585
对于表格里面的信息可以使用extract_table
提取。
代码笔记:https://www.jianshu.com/p/a8a572dd73ef
(2.2)将信息写入MySQL中
在连接数据库之前,我们需要使用到一个模块pymysql
,使用pip命令安装该库:
pip install pymysql
还需要开启MySQL服务,在命令行中输入:net start mysql 即可
如果报错,有两种可能:
- mysql没有加入环境变量,加入环境变量再执行下一步即可
- MySQL不在服务列表中,在管理员模式下的命令行中输入
mysqld -install
代码笔记:https://www.jianshu.com/p/f6afa5b735a2
(3.1) web服务器设想
整体采用前后端分离的架构(VUE + Flask + MySQL)。
前端服务和后端服务之间使用JSON格式的数据进行交互。
后端
Flask实现简单接口和路由,完成和数据库的交互。
- /:主页面
- /query: 查询
- /download: 下载,多发票打包下载
单个文件可以直接发送,批量文件的话需要对多个文件进行打包后再发送。
前端
使用Vue框架进行开发,使用element UI做组件渲染。
使用axios插件发送HTTP请求(GET,POST)。
实现简单的文件下载功能。
(3.2)Flask 后端框架
flask是一个非常轻量化的后端框架,与django相比,它拥有更加简洁的框架。django功能全而强大,它内置了很多库包括路由,表单,模板,基本数据库管理等。flask框架只包含了两个核心库(Jinja2 模板引擎和 Werkzeug WSGI 工具集),需要什么库只需要外部引入即可,让开发者更随心所欲的开发应用。
使用之前需要先安装Flask库:pip install flask
flask项目快速构建,似乎只有pycharm企业版能够自动帮你构建项目,其他编程软件只能通过手动创建。因为flask框架对项目目录没有要求,所以项目的目录我们可以根据自己的需求设计,即使是单个文件也可以执行。
在项目根目录下构建:
- webapp包目录,存放flask代码,包内有init.py文件
- templates目录,存放模板文件
- static目录,存放js,css等静态文件。其下建立js目录,放入jquery、echarts的js文
- app.py入口文件
使用pip freeze >requirements.txt
可以记录所有依赖包和精确的版本号,以便在新环境中进行操作部署。
使用pip install -r requirements.txt
可以在新的环境中安装所有依赖包。
快速入门传送门:https://www.bilibili.com/video/BV17W41177oE?p=1&vd_source=9e5b81656aa2144357f0dca1094e9cbe
flask:
# 先用一个文件启动Flask服务
# -*- coding:utf-8 -*-
# 1.导入flask扩展
from flask import Flask, send_file
from flask import make_response
from flask import request
from flask import send_from_directory
from flask import g
import pymysql
import json
import zipfile
import random
import shutil
import os
import time
# 2.创建flask应用程序实例
# 需要传入__name__,作用是为了确定资源所在的路径
app = Flask(__name__)
# app.config['ENV'] = "development"
app.config['SECRET_KEY']="demo"
# 连接数据库
connect = pymysql.connect(
host='localhost',
user='root',
passwd="",
charset="utf8",
autocommit=True,
database="pdf_info"
)
cur = connect.cursor() # 创建游标,用于读取数据
# 3. 定义路由和视图函数
# Flask中定义路由是通过装饰器实现的
# 这是主页返回所有的数据
@app.route('/',methods=["GET","POST"])
def index():
"""主页返回所有文件列表"""
try:
query_info = "select * from info;"
cur.execute(query_info)
res = cur.fetchall()
except Exception as e:
info = {
"data":[],
"status":400,
"info":"数据表获取失败:"+e
}
return json.dumps(info)
else:
info = {
"data":[],
"status":200,
"info":"数据查找成功!"
}
for data in res:
dic = {
't1': '','pro_name': '','code': '','num': '','date': '','year': '',
'month': '','day': '','client_name': '','client_itin': '',
'seller_name': '','seller_itin': '','car_num': '','car_type': '',
'total_price': '','price': '','tax_rate': '','tax_price': '','dir': ""
}
item = list(data)
for i,key in enumerate(dic.keys()):
dic[key] = item[i]
info['data'].append(dic)
#设置响应头
resp = make_response(json.dumps(info))
resp.status = "200" # 设置状态码
resp.headers["Content-Type"] = "application/json" # 设置响应头
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
return resp
@app.route('/query',methods=["GET","POST"])
def query():
"""根据键值对对数据进行查找 参数列表为:(key=字段, value=值)"""
info = {
"data":[],
"status":200,
"info":"数据查找成功!"
}
if request.method == 'POST':
key = request.form.get("key","")
value = request.form.get("value","")
if key == "" or value == "":
info["info"] = "数据为空"
info["status"] = 400
#设置响应头
resp = make_response(json.dumps(info))
resp.status = "400" # 设置状态码
resp.headers["Content-Type"] = "application/json" # 设置响应头
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
return resp
query_sql = "select * from info where {}='{}';".format(key,value)
cur.execute(query_sql)
res = cur.fetchall()
for data in res:
dic = {
't1': '','pro_name': '','code': '','num': '','date': '','year': '',
'month': '','day': '','client_name': '','client_itin': '',
'seller_name': '','seller_itin': '','car_num': '','car_type': '',
'total_price': '','price': '','tax_rate': '','tax_price': '','dir': ""
}
item = list(data)
for i,key in enumerate(dic.keys()):
dic[key] = item[i]
info['data'].append(dic)
#设置响应头
resp = make_response(json.dumps(info))
resp.status = "200" # 设置状态码
resp.headers["Content-Type"] = "application/json" # 设置响应头
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
return resp
else:
info["info"] = "查询失败"
info["status"] = 400
#设置响应头
resp = make_response(json.dumps(info))
resp.status = "400" # 设置状态码
resp.headers["Content-Type"] = "application/json" # 设置响应头
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
return resp
@app.route('/download',methods=["GET","POST"])
def send():
"""批量下载文件"""
info = {
"data":[],
"status":200,
"info":"数据查找成功!"
}
if request.method == "POST":
downloadlist = request.form.get("num","")
if downloadlist !="":
downloadlist = json.loads(downloadlist)
# 批量文件打包发送和单文件发送
if len(downloadlist) == 1:
# single file
query_sql = "select path,file from info where num={};".format(downloadlist[0])
cur.execute(query_sql)
res = cur.fetchall()[0]
# response = make_response(send_from_directory(res[0],res[1],as_attachment=True))
response = make_response(send_file(res[0]+res[1],as_attachment=True))
# 如果 response.header 中没有添加 Access-Control-Expose-Headers 这个参数(代表:服务器允许浏览器访问的头(headers)的白名单),vue中就无法获取 content-disposition,即 res.headers['content-disposition'];无法找到
response.headers["content-disposition"] = "attachment;filename=test.pdf"
response.headers["FileName"] = "test.pdf"
response.headers[" Access-Control-Expose-Headers"] = "FileName"
response.headers["content-type"] = "application/pdf"
response.headers["access-control-allow-origin"] = "*"
return response
else:
# multiple file https://www.cnblogs.com/hahaa/p/16512432.html
query_sql = "select dir from info where num in{};".format(tuple(downloadlist))
cur.execute(query_sql)
res = cur.fetchall()
times = str(int(time.time())+random.randint(0,100000))# 防止冲突,因为有可能有人同时下载文件
des_path = "./temp/"+times+"/"
if not os.path.exists("./temp/"):
os.mkdir("./temp/")
mkdir_path = os.path.join(os.getcwd(),"temp",times)
os.mkdir(mkdir_path)
zip = zipfile.ZipFile(des_path+"/test.zip","w",zipfile.ZIP_DEFLATED)
# 将指定文件复制到临时文件夹下
for i,data in enumerate(res):
path = des_path + str(i) + ".pdf"
f = open(path,"wb")
src_file = open(data[0],"rb")
f.write(src_file.read())
f.close()
src_file.close()
# 压缩批量文件
for file in os.listdir(des_path):
if file.endswith('.pdf'):
zip.write(des_path+file)
zip.close()
resp = make_response(send_from_directory(des_path,"test.zip",as_attachment=True))
resp.headers["Content-Disposition"] = "attachment; filename=test.zip"
resp.headers["Content-Type"] = "application/zip"
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
g.dir = des_path
# # 发送之后需要删除文件夹
# @response.call_on_close
# def on_close():
# shutil.rmtree(des_path)
return resp
else:
info["info"] = "内容而为空"
info["status"] = 400
#设置响应头
resp = make_response(json.dumps(info))
resp.status = "400" # 设置状态码
resp.headers["Content-Type"] = "application/json" # 设置响应头
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
return resp
else:
info["info"] = "下载失败"
info["status"] = 400
#设置响应头
resp = make_response(json.dumps(info))
resp.status = "400" # 设置状态码
resp.headers["Content-Type"] = "application/json" # 设置响应头
resp.headers["Access-Control-Allow-Origin"] = "*" # 设置响应头
return resp
# @app.after_request
# def on_close(res):
# # PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。
# shutil.rmtree(g.dir)
# return res
# 4. 启动服务
if __name__ == '__main__':
app.run(port=5000)
前端:
实例1-ETC电子发票管理
实例1 - ETC电子发票管理
查询
下载