提取pdf里关键信息到mysql

  • 背景
  • 问题分析
  • 操作实施
    • pdf转图片
    • 调用ocr接口图片转txt
    • 正则提取txt并写入mysql
  • 效果预览
  • 总结

背景

本次公司来了一批有关上海物业招标信息文件,原文件是pdf格式的,其中包含可编辑的pdf格式(鼠标能够点进pdf里面)和不可编辑的pdf格式(鼠标点不进pdf里面),现在,要把这些pdf文件里面关键文字信息提取出来,以结构化数据存入mysql里面形成一张招标物业信息表。

问题分析

pdf文件里面是非结构化文字数据,而MySQL存储的事结构化数据,整个项目简而言之就是非结构化数据的结构化,针对这类问题没有放之四海皆准的办法, 只能针对特定问题特定处理,相对来说,可编辑的pdf文件好处理,pdfplumber是一个不错的可用模块,然而对于不可编辑的pdf文件,pdfplumber是无可奈何的,会返回None, 于是设计了如下方案

(1) pdf转图片;
(2)调用OCR接口图片转txt;
(3)正则提取txt并写入mysql。

操作实施

有了设计方案,接下来我们来一步一步的往前推进

pdf转图片

经过搜索,比较流行的大致有3个可调的模块,分别是fitz,pdf2image和wand.image,三个实验下来效果都还不错,其中fitz比较简单,pdf2image也还好,wand.image比较麻烦,且fitz比较清晰,pdf2image灰度比较大,而wand.image的对比度比较大,一般都会有如下步骤

(1)读取本地pdf文件(将pdf文件名形成一个列表);
(2)打开pdf文件(对(1)pdf文件列表循环打开),获取每个pdf的总页数和每一页的像素流对象,将每一页像素流对象转为图片格式;
(3)将同一个pdf文件转过来的图片进行拼接,剪切;
(4)保存同一个pdf文件对应的长图;

这里给出fitz模块调用代码

# -*- coding: utf-8 -*-
"""
Project_name:pdf2pic
Description:
Created on Tue Dec  8 08:59:21 2020
@author: 帅帅de三叔
"""
import os
import os.path
import fitz
from PIL import Imagepdfpath =  r"D:\项目\pdf提取信息\pdf源文件" #原pdf文件路径 D:\项目\pdf提取信息\pdf转图片
temp_imagepath = r"D:\项目\pdf提取信息\pdf转图片\临时图片" #用来存放临时图片路径
imagepath = r"D:\项目\pdf提取信息\合并后的图片" #用来存放转化后的图片路径def mergePic(m, temp_imagepath): #合并分割后的png图片形成一张长图img_list = [] #用来存放png图片名称for parent, dirname, filenames in os.walk(temp_imagepath):for filename in filenames:if ".png" in filename:img_list.append(filename)print(img_list[0:m])if img_list:img_name = img_list[0] #用第一张图片建模获取色彩模式,长宽像素color_mod = 'RGBA' if img_name.endswith('.png') else 'RGB'  # jpeg格式不支持RGBAfirst_img = Image.open(temp_imagepath+os.sep+img_list[0]) #打开第一张图total_width = first_img.size[0] #第一张图的宽度像素height_size = first_img.size[1] #第一张图的高度像素total_height = height_size * mleft = 0right = height_sizetarget = Image.new(color_mod, (total_width, total_height))  # 最终拼接的图像的大小for img in img_list[0:m]:target.paste(Image.open(temp_imagepath+os.sep+img), (0, left, total_width, right)) #黏贴进模板left += height_sizeright += height_sizetarget.save(imagepath + os.sep + pdfname[:-4] + '_fitz.png', quality=100)return img_namedef pdf2pic(): #将pdf一页一页切割转为一页一页的png图片pdf = fitz.open(pdfpath+os.sep+pdfname) #打开pdf文件for pg in range(0, pdf.pageCount): #pdf页数 page = pdf[pg] # 获得每一页的对象trans = fitz.Matrix(2.0, 2.0).preRotate(0)pm = page.getPixmap(matrix=trans, alpha=False) # 获得每一页的流对象pm.writePNG(temp_imagepath + os.sep + '{:0>3d}.png'.format(pg + 1))  # 保存到临时图片文件夹下pagecount = pdf.pageCount #pdf总页数#print(pagecount)if pagecount > 4:pagecut = 4else:pagecut =pagecountpdf.close() #关闭pdf文件return pagecutif __name__=="__main__":pdfnames = [] # 用来存放pdf源文件名称for parent, dirname, filenames in os.walk(pdfpath):#pdf源文件路径for filename in filenames:if ".pdf" in filename: #只找pdf后缀的pdfnames.append(filename)for idx, pdfname in enumerate(pdfnames): print("正在处理第 %d(2022)  张名为 %s 文件"%(idx, pdfname))pagecut = pdf2pic()mergePic(pagecut, temp_imagepath)

调用ocr接口图片转txt

本人之前调用过pytesseract 模块进行OCR过,但是发现其自己玩玩还行,要想提高精度还需要做很多预处理工作,比较麻烦,这次是公司文件,对精度和效率要求都比较高,故这次采用大平台的OCR接口来做,对比了各大OCR在线效果,对比结果如下表所示

平台 连接 效果
腾讯OCR(通用印刷体) https://cloud.tencent.com/act/event/ocrsale?fromSource=gwzcw.3816145.3816145.3816145&utm_medium=cpc&utm_id=gwzcw.3816145.3816145.3816145 支持pdf, 腾讯OCR识别准确率在93%左右
腾讯OCR(通用印刷体高精度版) 不支持pdf,腾讯OCR识别准确率在96%左右
百度OCR(标准版) https://cloud.baidu.com/product/ocr/general 不支持pdf,仅支持图片,每日 50000 次免费调用量,百度在85%
百度OCR(高精度版) https://cloud.baidu.com/product/ocr/general 不支持pdf,仅支持图片,百度在90%
阿里云OCR(高精版) https://duguang.aliyun.com/experience?type=universal&subtype=general#intro 不支持pdf,仅支持图片,阿里云97%
合合OCR https://ai.intsig.com/api/vision/text_recog_ch_en_coordinate 99%
迅捷OCR https://app.xunjiepdf.com/ocrpdf/ 测试支持pdf,调用的时候仅支持图片
搜狗OCR https://ai.sogou.com/product/vision/osr/ 无试用,提交商务合作
有道OCR http://ai.youdao.com/product-ocr.s 文字有丢失,逗号错转英文句号了

兼顾精准和效率推荐阿里云OCR或者腾讯OCR, 综合各方面考虑,最后选择了阿里OCR,这里给出阿里OCR调用代码,请自己申请appcode

# -*- coding: utf-8 -*-
"""
Project_name:png2txt_by_ali
Description:调用阿里ocr将图片文字信息存到txt文档
Created on Mon Dec 14 16:04:53 2020
@author: 帅帅de三叔
"""
import re
import os,os.path
import urllib.request
import urllib.parse
import json
import csv
import time
import random
import base64parent_path = r"D:\项目\pdf提取信息"
image_path = r"D:\项目\pdf提取信息\合并后的图片" #图片路径
txt_path = r"D:\项目\pdf提取信息\阿里txt源文件" #用来存放的txt路径
if not os.path.exists(txt_path):os.mkdir(txt_path)url_request= "https://ocrapi-document-structure.taobao.com/ocrservice/documentStructure" #"https://ocrapi-advanced.taobao.com/ocrservice/advanced"
AppCode = "您的appcode"
headers = {'Authorization': 'APPCODE ' + AppCode,'Content-Type': 'application/json; charset=UTF-8'
}def posturl(url,data={}):try:params=json.dumps(dict).encode(encoding='UTF8')req = urllib.request.Request(url, params, headers)r = urllib.request.urlopen(req)html = r.read()r.close();return html.decode("utf8")except urllib.error.HTTPError as e:print(e.code)print(e.read().decode("utf8"))time.sleep(1)if __name__=="__main__":image_names = [] # 用来存放图片源文件名称for parent, dirname, filenames in os.walk(image_path):for filename in filenames:if ".png" in filename:image_names.append(filename)#sample_image_names = random.sample(image_names, 100) #随机取100个for idx, image_name in enumerate(image_names):print("正在处理第 %d(2021) 张 %s"%(idx, image_name))with open(image_path+os.sep+image_name, 'rb') as f: #以二进制读取本地图片data = f.read()encodestr = str(base64.b64encode(data),'utf-8')dict = {'img': encodestr}html = posturl(url_request, data=dict)response = json.loads(html) #转化为字典格式words = [] #用来存放文字块for line in range(len(response["prism_wordsInfo"])):#print(response["prism_wordsInfo"][line]["word"].strip().replace(" ", "").replace(",", ","))words.append(response["prism_wordsInfo"][line]["word"].strip().replace(" ", "").replace(",", ","))longstr = " ".join(words) #所有文字块拼接,以空格分开with open(txt_path+os.sep+image_name[0:-9]+".txt","w+", newline = "\n", encoding='utf-8') as txt_file: txt_file.write(longstr)

正则提取txt并写入mysql

在利用OCR将图片的信息笼统的存入txt文本里面,形成一个长的字符串,现在就要利用一些关键线索去这个长字符串提取关键信息,由于要提取的字段比较多,代码比较长,故封装成parserFunc函数

# -*- coding: utf-8 -*-
"""
Project_name:wuyezaobiao
Description:招标物业信息加工处理
Created on Tue Dec 22 14:30:55 2020
@author: 帅帅de三叔
"""
import requests
import pandas as pd
import pymysql
from sqlalchemy import create_engine
from datetime import datetime
import fitz
from PIL import Image
import re
import os,os.path
import urllib.request
import urllib.parse
import json
import csv
import time
import random
import base64
import parserFunctemp_imagepath = r"D:\项目\物业招标\图片文件夹\临时图片文件夹" #用来存放临时图片路径,公用一个临时图片文件夹### 下载pdf文件并返回待处理的pdf表
def downloadPdf(): #定义下载pdf文件db = pymysql.connect(host='101.132.152.32',  port=9696, user='j4eJuTlh', database='lansi_data_collection', charset='utf8')sql = 'select a.id, a.swf_url from lansi_data_collection.zb_tender a\left join lansi_data_collection.zb_tender_message b on b.id= a.id\where b.id is null and instr(a.title, "招标公告")>0 and a.id!="131216164406430"'#查询a表id不在b表id中的announce = pd.read_sql(sql, db, coerce_float = True) #获取urlsnum = len(announce) #新增条数print("一共%d条招标公告"%num)if num!=0:announce["name"] = [swf_urf.split(".")[-2].split("/")[-1] for swf_urf in announce["swf_url"]] #从swf_url截取文件名pdf_path = "D:\项目\物业招标\pdf文件夹"+os.sep + str(datetime.now().date()) #当日pdf文件路径#print(pdf_path)if not os.path.exists(pdf_path):os.mkdir(pdf_path)header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47"}    urls = announce["swf_url"] #url列表for url in urls:res = requests.get(url, headers = header)filename = pdf_path+os.sep+url.split(".")[-2].split("/")[-1]+".pdf"with open(filename, 'wb+') as f:f.write(res.content)f.close()return num, announce    ### pdf2png
def fetch_pdf(): #获取pdf文件pdf_path = "D:\项目\物业招标\pdf文件夹"+os.sep + str(datetime.now().date()) #当日pdf文件路径pdf_names = [] # 用来存放pdf源文件名称for parent, dirname, filenames in os.walk(pdf_path):#pdf源文件路径for filename in filenames:if ".pdf" in filename: #只找pdf后缀的pdf_names.append(filename)return pdf_namesdef pdf2pic(): #将pdf一页一页切割转为一页一页的png图片pdf_path = "D:\项目\物业招标\pdf文件夹"+os.sep + str(datetime.now().date()) #当日pdf文件路径pdf = fitz.open(pdf_path+os.sep+pdf_name) #打开pdf文件for pg in range(0, pdf.pageCount): #pdf页数 page = pdf[pg] # 获得每一页的对象trans = fitz.Matrix(2.0, 2.0).preRotate(0)pm = page.getPixmap(matrix=trans, alpha=False) # 获得每一页的流对象#print(temp_imagepath + os.sep + '{:0>3d}.png'.format(pg + 1))pm.writePNG(temp_imagepath + os.sep + '{:0>3d}.png'.format(pg + 1))  # 保存到临时图片文件夹下pagecount = pdf.pageCount #pdf总页数#print(pagecount)if pagecount > 4:pagecut = 4else:pagecut = pagecountpdf.close() #关闭pdf文件return pagecutdef mergePic(m, temp_imagepath): #合并分割后的png图片形成一张长图image_path = "D:\项目\物业招标\图片文件夹"+os.sep+str(datetime.now().date()) #保存当日图片if not os.path.exists(image_path):os.mkdir(image_path)img_list = [] #用来存放png图片名称for parent, dirname, filenames in os.walk(temp_imagepath):for filename in filenames:if ".png" in filename:img_list.append(filename)#print(img_list[0:m])if img_list:img_name = img_list[0] #用第一张图片建模获取色彩模式,长宽像素color_mod = 'RGBA' if img_name.endswith('.png') else 'RGB'  # jpeg格式不支持RGBAfirst_img = Image.open(temp_imagepath+os.sep+img_list[0]) #打开第一张图total_width = first_img.size[0] #第一张图的宽度像素height_size = first_img.size[1] #第一张图的高度像素total_height = height_size * mleft = 0right = height_sizetarget = Image.new(color_mod, (total_width, total_height))  # 最终拼接的图像的大小for img in img_list[0:m]:target.paste(Image.open(temp_imagepath+os.sep+img), (0, left, total_width, right)) #黏贴进模板left += height_sizeright += height_sizetarget.save(image_path + os.sep + pdf_name[:-4] + '.png', quality=100)return img_name### OCR
def fetch_png():#获取图片文件image_path = "D:\项目\物业招标\图片文件夹"+os.sep+str(datetime.now().date()) #保存当日图片image_names = [] # 用来存放图片源文件名称for parent, dirname, filenames in os.walk(image_path):for filename in filenames:if ".png" in filename:image_names.append(filename)return image_names### 正则提取存入mysql
def png2txt(): #url_request= "https://ocrapi-document-structure.taobao.com/ocrservice/documentStructure"AppCode = '您的appcode'headers = {'Authorization': 'APPCODE ' + AppCode,'Content-Type': 'application/json; charset=UTF-8'}image_path = "D:\项目\物业招标\图片文件夹"+os.sep+str(datetime.now().date()) #保存当日图片message = []    with open(image_path+os.sep+image_name, 'rb') as f: #以二进制读取本地图片data = f.read()encodestr = str(base64.b64encode(data),'utf-8')dict = {'img': encodestr}params=json.dumps(dict).encode(encoding='UTF8')req = urllib.request.Request(url_request, params, headers)r = urllib.request.urlopen(req)time.sleep(1)html = r.read()r.close()response = json.loads(html.decode("utf8")) #转化为字典格式words = [] #用来存放文字块for line in range(len(response["prism_wordsInfo"])):#print(response["prism_wordsInfo"][line]["word"].strip().replace(" ", "").replace(",", ","))words.append(response["prism_wordsInfo"][line]["word"].strip().replace(" ", "").replace(",", ","))longstr = " ".join(words) #所有文字块拼接,以空格分开new_id = [image_name[0:-4]]row_data = parserFunc.parser(longstr) #调用正则解析函数#print(new_id+row_data)message_row = new_id+row_datamessage.append(message_row)message = pd.DataFrame(message) #数据框化message.columns = ["new_id", "project_name", "area", "operator", "whichtype", "location", "faces", "landarea", "building_area",\"building_constitute", "building_density", "volume", "buildings_num", "dwelling_num", "green_ratio", "green_area", "mass_green_ratio", "mass_green_area",\"parking_ratio", "carport", "ground_carport", "underground_carport", "non_motor_vehicle", "complete_date", "dwelling_fee", "notdwelling_fee", "contract_period",\"bidder", "bidder_address", "bidder_contact", "bidder_mobile", "bid_agent", "bid_agent_address", "agent_contact", "agent_mobile", "bid_date", "longstr"]announce_data = pd.merge(left = message, right= announce, how = "left", left_on = "new_id", right_on = "name")announce_data = announce_data.iloc[:, 0:-2]#print(announce_data.columns)connect=create_engine("mysql+pymysql://j4eJuTlh:@101.132.152.32:9696/lansi_data_collection?charset=utf8")pd.io.sql.to_sql(announce_data, "zb_tender_message", connect, schema="lansi_data_collection", index=False, if_exists="append")print("写入成功")if __name__=="__main__":num, announce = downloadPdf()if num !=0:pdf_names = fetch_pdf()print("开始pdf转图片\n")for pdf_name in pdf_names:print("正在处理 %s"%pdf_name)pagecut = pdf2pic()mergePic(pagecut, temp_imagepath)print("pdf转图片完成,下面开始解析图片信息\n")image_names = fetch_png()announce = downloadPdf()[1]for image_name in image_names:print("正在存入%s"%image_name)png2txt()else:print("没有新增招标公告")

效果预览

一共2021个pdf文件,每个文件提取36个关键字段,形成一张2021*36的表,抽查了首尾20行一共2个单元格有出入,出入率不超3‰,整体效果还不错。

总结

(1)在项目设计的时候可以进行天花板分析,明确哪一环节最有可能提升的,哪些很难提升,到时候重点下大气力解决能够明显提升的环节;

(2)正则提取的时候尽量用一些在线正则测试;

提取pdf里关键信息到mysql相关推荐

  1. 盘点一个批量提取pdf文件目标信息的实用案例

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 你若盛开,清风自来. 大家好,我是 ...

  2. 提取PDF里面的EXCEL表格

    提取PDF里面的EXCEL表格 1. 批量提取PDF中的表格,其中一些常见的工具包括: Tabula:Tabula 是一款免费的开源工具,可以轻松地从 PDF 中提取表格并将其导出为 CSV 格式的文 ...

  3. 个人永久性免费-Excel催化剂功能第88波-批量提取pdf文件信息(图片、表格、文本等)...

    日常办公场合中,除了常规的Excel.Word.PPT等文档外,还有一个不可忽略的文件格式是pdf格式,而对于想从pdf文件中获取信息时,常规方法将变得非常痛苦和麻烦.此篇给大家送一pdf文件提取信息 ...

  4. 利用Python提取PDF文件中的文本信息

    如何利用Python提取PDF文件中的文本信息 日常工作中我们经常会用到pdf格式的文件,大多数情况下是浏览或者编辑pdf信息,但有时候需要提取pdf中的文本,如果是单个文件的话还可以通过复制粘贴来直 ...

  5. 用 Python 批量提取 PDF 的图片,并存储到指定文件夹

    今天给大家分享如何用 Python 批量提取 PDF 的图片,并存储到指定文件夹. 一.实现效果图 二.基于 fitz 库和正则搜索提取图片 fitz 库是 pymupdf 中的一个模块,用它来提取 ...

  6. pdfplumber批量提取PDF重点内容并保存到csv数据表

    同一个文件夹中保存了1000多份PDF文档,需要从每份PDF文档中提取关键的信息,将每一条信息存入csv表格作为一个单元格内容,同一份PDF文档中的多条信息生成一行数据,保存为1000多行固定格式的数 ...

  7. 用python提取PDF表格内容保存到excel

    一 提取pdf方法介绍 任务是用python提取PDF里的表格文件到excel里面去.做为一个 学了一个周python的人来说当然像尝试一下看能不能做到,事实证明是可以的只是可能代码有点烂...... ...

  8. Python爬取百度百科,BeautifulSoup提取关键信息

    本文主要爬取演员杨幂的百度百科,用到的python库有:requests和BeautifulSoup 主要内容共分为以下两个方面: 1. 用requests爬取网页内容 2. 用BeautifulSo ...

  9. java tika 解析pdf,tika提取pdf信息异常,tika提取pdf信息

    tika提取pdf信息异常,tika提取pdf信息org.apache.tika.sax.WriteOutContentHandler$WriteLimitReachedException: Your ...

最新文章

  1. mysql 5.5 主从双向同步,请教mysql 定时 双向 主从同步問題
  2. 值得关注!一种新型脑机接口--集成光子芯片的脑机接口是否可行?
  3. Oracle查询一个用户的所有表的结构信息的SQL语句
  4. 阿里云大学课程学习有奖征文活动现在开始
  5. 【结论】【dfs】费解的开关(joyoi-tyvj 1266)
  6. 谈谈对MVC的理解(View+Model+Controller)
  7. sun工作站linux,LINUX SUN Solaris 8操作系统安装指导书.doc
  8. 【linux】linux一次杀死多个进程
  9. Linux (Ubuntu) 命令
  10. 急性中耳炎的治疗行业调研报告 - 市场现状分析与发展前景预测
  11. 考研数据结构--严版图相关代码 自用
  12. 在一个字符串中找到第一个只出现一次的字符。
  13. OLAP引擎——Kylin介绍
  14. springboot高校学生健康打卡系统的设计与实现毕业设计源码021009
  15. 编辑栏不见了_微信图文编辑器集合和使用技巧
  16. 模拟京东商城登陆HttpRequest
  17. 平面设计师okr_还在头疼怎么写OKR,这可能是最全的落地指导手册了
  18. 2.使用RNN做诗歌生成
  19. 什么是图形加速卡(二)
  20. 世界知名洋酒种类及酒具的使用

热门文章

  1. JS中常用的几种设计模式
  2. 互联网标准技术架构图
  3. 关于直通车与万相台的区别,你知道多少
  4. 深度学习: ILSVRC竞赛(ImageNet竞赛)
  5. 打开安装文件提示错误,错误内容全是问号,见图
  6. 读书《Python数据挖掘课程》
  7. jmeter—使用插件测试并发
  8. JAVA简单实现扫雷小游戏
  9. 微信小程序图片自适应高度问题
  10. 为什么百度知道章子怡的老公是汪峰?点击教你AI智能技术!