当前位置:   article > 正文

提取pdf里关键信息到mysql_pdf导入mysql

pdf导入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 Image

pdfpath =  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格式不支持RGBA
        first_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 * m
        left = 0
        right = height_size
        target = 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_size
            right += height_size
        target.save(imagepath + os.sep + pdfname[:-4] + '_fitz.png', quality=100)
        return img_name

def 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 = 4
    else:
        pagecut =pagecount
    pdf.close() #关闭pdf文件
    return pagecut

if __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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

调用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%
合合OCRhttps://ai.intsig.com/api/vision/text_recog_ch_en_coordinate99%
迅捷OCRhttps://app.xunjiepdf.com/ocrpdf/测试支持pdf,调用的时候仅支持图片
搜狗OCRhttps://ai.sogou.com/product/vision/osr/无试用,提交商务合作
有道OCRhttp://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 base64

parent_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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

正则提取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 parserFunc

temp_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) #获取urls
    num = 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_names

def 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 = 4
    else:
        pagecut = pagecount
    pdf.close() #关闭pdf文件
    return pagecut

def 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格式不支持RGBA
        first_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 * m
        left = 0
        right = height_size
        target = 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_size
            right += height_size
        target.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_data
        message.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("没有新增招标公告")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182

效果预览

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

总结

(1)在项目设计的时候可以进行天花板分析,明确哪一环节最有可能提升的,哪些很难提升,到时候重点下大气力解决能够明显提升的环节;
在这里插入图片描述
(2)正则提取的时候尽量用一些在线正则测试;

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/881043
推荐阅读
相关标签
  

闽ICP备14008679号