【第四范式】实习职位:AI产品研发

  • 项目一:要素提取
  • 项目二:文本聚类功能测试
  • 项目三:爬虫
  • 项目四:word和PDF内容对比
  • 项目五:去除页眉页脚
  • 项目六:基于OCR返回的json结果去除图片水印
  • 项目七:Word内容关键字段抽取
  • 项目八:文件比对项目服务开发
    • Airflow,Minio相关概念和作用原理
      • Airflow
      • Minio
    • git相关

项目一:要素提取

描述:根据接口API需求文档,实现创建任务,启动任务,查看任务状态以及下载任务等业务功能

  • 工具:Postman,Pycharm,Navicat,Git
  • 干货:
    • Postman使用方法速记
    • Gitlab合作,克隆项目,创建分支,上传代码方法
  • 写代码中get的经验
    1. Flask项目中的BluePoint()蓝图使用:

      job = Blueprint('job', __name__)
      

      主要是分担路由压力,把路由逻辑分散到各个业务功能模块文件中,不然所有的路由都在一个文件中,后期不方便维护。
      之后需要注册蓝图,告知给主应用对象去注册蓝图对象

      app.register_blueprint(job, url_prefix='/elements-extraction/v1/job')
      

      参考链接: https://www.cnblogs.com/wf-skylark/p/9306789.html

    2. python的对象深拷贝copy.deepcopy()似乎不能拷贝文件流,报错:

       cannot serialize '_io.BufferedRandom' object
      
    3. 异步执行任务之ThreadPoolExecutor使用API

      from concurrent.futures import ThreadPoolExecutor
      executor = ThreadPoolExecutor(10)
      result = executor.submit(方法名,参数1,参数2,...) #异步执行任务
      result.add_done_callback(回调方法名)  # 回调函数将返回的结果存入数据库,回调方法不能传参def 方法名(参数1,参数2,...):业务逻辑return resdef 回调方法名(res): #这里的res其实是上面方法名的返回值,只是被封装成了对象,需要调用.result()方法重现print(res.result())
      
    4. 带变量的动态路由url,根据不同的变量值执行不同的业务逻辑

      #在job.py的业务文件中,创建job蓝图
      job = Blueprint('job', __name__)@job.route('/<string:server_name>/upload', methods=['POST'])
      def upload(server_name):print(server_name)if(server_name=="XXX"):执行业务逻辑1......
      
    5. logger报错:PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问,以及日志输出中文乱码的解决办法:直接在logger对应的handler方法里添加encoding='utf-8’参数

  • 将Flask项目部署到服务器Docker上,参考本人另一篇博文的第4部分

项目二:文本聚类功能测试

描述:主要是在GPU服务器上黑盒测试一个已开发完成的功能模块,保证交付给客户时能够正常运行。

  • 文本聚类包括特征提取和聚类模型

    • 特征提取:tfidf、doc2vec、bert
    • 聚类:kmeans,dbscan,birch
  • 代码需要在服务器上跑起来,但是服务器用的是python2.7,很多库和代码不兼容,而且不允许使用root安装python3.x运行代码,怎么办?——非root用户安装anaconda
    • 安装好anaconda之后怎么使用?

      1. conda create -n py37 python=3.7
      2. 激活python环境:
        conda activate [虚拟环境名,如py37]
        
      3. 离开环境:
        conda deactivate
        
    • 添加国内清华镜像源加速下载包
      pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas
  • 跑通的代码需要加上requirements.txt
    -在package目录下 pip freeze > requirements.txt

项目三:爬虫

描述:基于Google爬虫,爬取页面搜索结果内容

  • 工具:Pycharm,Monocloud

  • 必要条件:挂VPN(使用MonoCloud)

  • 可用的开源项目:Magic-Google

    • 里面的示例项目中PROXIES的配置需要修改为

    • PROXIES = [{ 'https': 'http://127.0.0.1:7078' }]
      其中的7078端口是MonoCloud的默认流量端口(即Setting页中的Local port),MonoCloud中的Mode设置为Global:

      主要原理:(MonoCloud官网截图)

  • python如何测试request访问Google配置信息:

    import requests
    print(requests.get('https://www.google.com',proxies={'https':'http://127.0.0.1:7078'}).content)
    
  • 写入csv文件发现每两行中间空一行,open()添加newline参数

    • 解决:with open(‘xxx.csv’, ‘w’,newline =’’ ) as fcsv:
  • 读取txt文件每行会换行+空行输出

    f = open(file, encoding='utf-8')
    lines = f.readlines()  # 读取全部内容 ,并以列表方式返回
    for line in lines:line = line.strip('\n')
    
  • 多线程爬虫
    在主线程中使用join,让主线程挂起等待所有子线程结束,代码如下:

    import threading
    import timedef run():time.sleep(2)print('当前线程的名字是: ', threading.current_thread().name)time.sleep(2)if __name__ == '__main__':start_time = time.time()print('这是主线程:', threading.current_thread().name)thread_list = []for i in range(5):t = threading.Thread(target=run)thread_list.append(t)for t in thread_list:t.setDaemon(True)t.start()for t in thread_list:t.join()print('主线程结束了!' , threading.current_thread().name)print('一共用时:', time.time()-start_time)
    
  • 记录程序运行时间

    start = time.time()
    time.sleep(3) #执行相关逻辑
    end = time.time()
    print("主程序执行结束,总消耗时间 {} s".format(end-start))
    
  • 遍历文件夹读取文件

    import os
    # 遍历文件夹
    def walkFile(file):file_group = []for root, dirs, files in os.walk(file):# root 表示当前正在访问的文件夹路径# dirs 表示该文件夹下的子目录名list# files 表示该文件夹下的文件listfor f in files:file_group.append(f)# 遍历所有的文件夹for d in dirs:print(os.path.join(root, d))return file_groupPATH = './province_road/' #读取同级目录文件夹province_road下的所有文件
    files = walkFile(PATH)
    for f in files:print(f)...#do something```

项目四:word和PDF内容对比

描述:对比word和pdf中不同的内容,然后把更改信息以批注的形式添加到word文件中

  • 向Word添加批注

  • 安装win32com报错:No matching distribution found for win32com

    • 解决方法:python -m pip install pypiwin32
  • 去除PDF中的红色印章

    import sys, fitz, os, datetime
    import time
    from PIL import Image
    import numpy as np
    # import tabula'''
    fitz库是什么,它是pymupdf中的一个模块,操作PDF非常舒服,只需要pip安装即可:
    pip install pymupdf
    '''def pyMuPDF_fitz(pdfPath, imagePath, zoomNum):startTime_pdf2img = datetime.datetime.now()  # 开始时间print("imagePath=" + imagePath)pdfDoc = fitz.open(pdfPath)for pg in range(pdfDoc.pageCount):page = pdfDoc[pg]rotate = int(0)# 每个尺寸的缩放系数为1.3,这将为我们生成分辨率提高2.6的图像。# 此处若是不做设置,默认图片大小为:792X612, dpi=96# zoom_x = 1.33333333 #(1.33333333-->1056x816)   (2-->1584x1224)zoom_x = zoomNum  # (1.33333333-->1056x816)   (2-->1584x1224)# zoom_y = 1.33333333zoom_y = zoomNummat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)pix = page.getPixmap(matrix=mat, alpha=False)if not os.path.exists(imagePath):  # 判断存放图片的文件夹是否存在os.makedirs(imagePath)  # 若图片文件夹不存在就创建if pg < 10:pg_str = '00' + str(pg)elif 10 <= pg < 100:pg_str = '0' + str(pg)else:pg_str = str(pg)pix.writePNG(imagePath + '/' + '%s.png' % pg_str)  # 将图片写入指定的文件夹内endTime_pdf2img = datetime.datetime.now()  # 结束时间print('pdf2img时间=', (endTime_pdf2img - startTime_pdf2img).seconds, '秒')def pyMuBinaryzation(binaryzationpath):startTime_pdfbinaryzation = datetime.datetime.now()  # 开始时间file_list = os.listdir(binaryzationpath)pic_name = []for x in file_list:if "jpg" in x or 'png' in x or 'jpeg' in x:pic_name.append(x)for i in pic_name:image = Image.open(binaryzationpath + '/' + i)new_file_name = binaryzationpath + '/' + ipicArray = np.array(image)  # 解析图片成 numpy矩阵'''以下是加深150以下的 并且不相等的'''red_data = picArray[..., 0]green_data = picArray[..., 1]blue_data = picArray[..., 2]red_green = red_data - green_datared_blue = red_data - blue_datared_150_1 = np.int64(red_data < 160)green_150_1 = np.int64(green_data < 150)blue_150_1 = np.int64(blue_data < 150)red_green_1 = np.int64(red_green != 0)black_150_index = np.where(red_150_1 + green_150_1 + blue_150_1 + red_green_1 == 4)picArray[black_150_index] = [0, 0, 0]'''以下是加深100以下 并且相等值的'''red_data = picArray[..., 0]green_data = picArray[..., 1]blue_data = picArray[..., 2]red_green = red_data - green_datared_blue = red_data - blue_datared_100_1 = np.int64(red_data < 100)red_green_100 = np.int64(red_green == 0)red_blue_100 = np.int64(red_blue == 0)black_100_index = np.where(red_100_1 + red_green_100 + red_blue_100 == 3)picArray[black_100_index] = [0, 0, 0]'''以下是红色情况的去除'''red_data = picArray[..., 0]green_data = picArray[..., 1]blue_data = picArray[..., 2]red_green = red_data - green_datared_blue = red_data - blue_datared_green1 = np.int64(red_green > 10)red_blue1 = np.int64(red_blue > 10)red_index = np.where(red_green1 + red_blue1 == 2)picArray[red_index] = [255, 255, 255]'''以下是去除200以上的'''red_data = picArray[..., 0]green_data = picArray[..., 1]blue_data = picArray[..., 2]red_green = red_data - green_datared_blue = red_data - blue_datared_200_1 = np.int64(red_data > 200)green_200_1 = np.int64(green_data > 200)blue_200_1 = np.int64(blue_data > 200)white_200_index = np.where(red_200_1 + green_200_1 + blue_200_1 == 3)picArray[white_200_index] = [255, 255, 255]im = Image.fromarray(picArray)im.save(new_file_name)endTime_pdfbinaryzation = datetime.datetime.now()  # 结束时间print('pdfpdfbinaryzation时间=', (endTime_pdfbinaryzation - startTime_pdfbinaryzation).seconds, '秒')def pyMuPicToPdf(picDir, outfilepath):startTime_PicToPdf = datetime.datetime.now()  # 开始时间file_list = os.listdir(picDir)pic_name = []im_list = []print(file_list)for x in file_list:if "jpg" in x or 'png' in x or 'jpeg' in x:pic_name.append(x)pic_name.sort()im1 = Image.open(picDir + '/' + pic_name[0])pic_name.pop(0)for i in pic_name:img = Image.open(picDir + '/' + i)# im_list.append(img)if img.mode == "RGBA":img = img.convert('RGB')im_list.append(img)else:im_list.append(img)im1.save(outfilepath, "PDF", resolution=100.0, save_all=True, append_images=im_list)endTime_PicToPdf = datetime.datetime.now()  # 结束时间print('PicToPdf时间=', (endTime_PicToPdf - startTime_PicToPdf).seconds, '秒')def deleteDir(path):try:for i in os.listdir(path):path_file = os.path.join(path, i)if os.path.isfile(path_file):os.remove(path_file)if os.path.exists(path):  # 如果文件夹# 删除文件,可使用以下两种方法。os.rmdir(path)# os.unlink(path)else:print('no such file:%s' % path)  # 则返回文件不存在except Exception as e:print(e)def mainProcess(fileName, outfilepath='', zoomNum=8):  # 2或8'''主函数入口:param fileName:需要去章的文件路径:param outfilepath:需要输出的文件路径,包括文件名,默认在同目录下生成 xxx_out.pdf 文件:param zoomNum:转换成图片的比例,影响运行速度,数值越大,去章效果越好,执行时间越长,默认为2:return:'''startTime = datetime.datetime.now()  # 开始时间(filepath, tempfilename) = os.path.split(fileName)  # 解析fileName(filename, extension) = os.path.splitext(tempfilename)  # 解析文件名 文件类型if not outfilepath:outfilepath = filepath + '/' + filename + '_out' + extensionoutdir = filepath + '/' + filenamepyMuPDF_fitz(fileName, outdir, zoomNum)  # 将pdf文件解析出图片pyMuBinaryzation(outdir)  # 对图片进行去章pyMuPicToPdf(outdir, outfilepath)  # 将图片合成pdfdeleteDir(outdir)  # 删除图片文件夹endTime = datetime.datetime.now()  # 结束时间print('总耗时=', (endTime - startTime).seconds, '秒')if __name__ == '__main__':fileName = r'./documents/2-技术协议.pdf'mainProcess(fileName)  # 入口函数
    
  • 基于pickle文件的sequence内容对比解析

  • 文件内容对比详细流程:

     1. 输入是一个word文件,一个pdf文件,需要将二者的文本差异信息,以批注的形式插入到word的对应位置2. 将word转换成pdf3. 将两个pdf通过OCR文字识别,转换成txt4. 经过difflib对比生成对比结果,得到pickle文件中保存的list5. 根据list中的信息将更改内容批注到word文件中
    

    思路:其实难点就是解析difflib库的文本比对结果,对difflib不明白的可以看DifferExample,返回的结果是类似这样的列表:

    from pprint import pprint
    pprint(result)
    ['    1. Beautiful is better than ugly.\n','-   2. Explicit is better than implicit.\n','-   3. Simple is better than complex.\n','+   3.   Simple is better than complex.\n','?     ++\n','-   4. Complex is better than complicated.\n','?            ^                     ---- ^\n','+   4. Complicated is better than complex.\n','?           ++++ ^                      ^\n','+   5. Flat is better than nested.\n']
    

    解析:有两个文本txt1,txt2

    1. 开头是’-’,说明是txt1中独有的句子
    2. 开头是’+’,说明是txt2中独有的句子
    3. 开头是’?’,说明上一句中对应哪一部分是特有的,^表示对应上面一句所在的文本txt1(txt2)的下标的字符相对于另一个文本txt2(txt1)是更新的,
    4. 其他情况,说明是txt1和txt2共有的

    根据比较结果出现的情况,合法的排列组合可包括:
    {"-?+?","-?+","-+?","-","+"," "},在下面的代码中为了便于分析,将空格改成了“x”。
    然后提取每一行的第一个字符,利用最大匹配原则得到匹配结果的排列组合。

    [’-?+’, ‘-+?’, ‘-’, ‘x’, ‘x’, ‘x’, ‘-?+?’, ‘x’, ‘x’, ‘x’, ‘x’, ‘x’,
    ‘x’, ‘-?+’, ‘-?+’, ‘+’, ‘-?+?’, ‘+’]

    最后就是对每种出现的排列组合进行处理。

    源代码:

    import win32com  #python -m pip install pypiwin32
    from win32com.client import Dispatch
    import sys
    import pickle
    from pprint import pprintimport copyword_dict=["-?+?","-?+","-+?","-","+","x"] #构建diff的合法排列组合
    def positive_max_matching(words):for word in words:#分别对每行进行正向最大匹配处理max_length = 4word_list = []len_hang = len(word)while len_hang>0 :tryWord = word[0:max_length]while tryWord not in word_dict:if len(tryWord)==1:breaktryWord=tryWord[0:len(tryWord)-1]word_list.append(tryWord)word = word[len(tryWord):]len_hang = len(word)return word_listdef prepare_result(predict_list):contents = []header_list = []word_str = ""for diff in predict_list:if(diff[0]=='-' or diff[0]=='+' or diff[0]=='?'):word_str += diff[0]if(diff[0]=='?'):contents.append(diff[2:-1])continueelse:#遇到空格,说明该句是公共句word_str += 'x'contents.append(diff[2:])header_list.append(word_str)return header_list,contentsdef process_result(predict_list,contents,header_combination):index = 0ans = []  # ans=[{word1_sentence,word1_sentence_to_add_comments,comments}]for hc in header_combination:# print(hc)if(hc=='-?+?'):str1 = contents[index]str1_diff = list(contents[index+1])str1_diff_cp = copy.deepcopy(str1_diff)str2 = contents[index+2]str2_diff = list(contents[index+3])str2_diff_cp = copy.deepcopy(str2_diff)for i,c in enumerate(str1_diff):# print(i,"***",c)if(c=='-'):str1_diff[i] = str1[i] #字符串不支持通过下标的方式直接赋值修改,需要把字符串转listif(c=='^'):str1_diff[i] = str1[i]for i,c in enumerate(str2_diff):if(c=='+'):str2_diff[i] = str2[i]#逆向记录+前面的内容,最多逆向扫描之前5个字符tmp = i-1add_str1 = []times = 0while(tmp>=0 and str2_diff_cp[tmp]==' '):times += 1add_str1.insert(0,str2[tmp])tmp -= 1if(times>=5):breakif(add_str1!=[]):add_str1 = ''.join(add_str1)# print('add_str1:', add_str1)str1_diff.append(' '+add_str1)#加空格是为了后面好分割str1_diff_cp.append(' '+'+')if (c == '^'):str2_diff[i] = str2[i]str1_diff = ''.join(str1_diff)  # 用空串将列表中的所有字符重新连接为字符串str1_diff_cp = ''.join(str1_diff_cp)words1 = str1_diff.strip().split(' ')cmds1 = str1_diff_cp.strip().split(' ')print(hc,words1, cmds1)str2_diff = ''.join(str2_diff)str2_diff_cp = ''.join(str2_diff_cp)words2 = str2_diff.strip().split(' ')cmds2 = str2_diff_cp.strip().split(' ')print(hc,words2, cmds2)#构造批注jsonfor i,c in enumerate(cmds1):if(c[0]=='^'):ans.append({"word1_sentence":str1,"word1_sentence_to_add_comments":words1[i],"comments":"信息更改\n","info":"PDF中在此处更改的内容:"+words2[i]})elif(c[0]=='+'):ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": words1[i],"comments": "信息添加\n","info": "PDF中在'{}'此处之后添加了内容:{}".format(words1[i],words2[i])})elif (c[0] == '-'):ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": words1[i],"comments": "信息删除\n","info": "PDF中在此处删除的内容:" + words1[i]})index += 4elif(hc=='-?+'):str1 = contents[index]str1_diff = list(contents[index + 1])str1_diff_cp = copy.deepcopy(str1_diff)str2 = contents[index + 2]for i,c in enumerate(str1_diff):# print(i,"***",c)if(c=='-'):str1_diff[i] = str1[i] #字符串不支持通过下标的方式直接赋值修改,需要把字符串转listif(c=='^'):str1_diff[i] = str1[i]str1_diff = ''.join(str1_diff)  # 用空串将列表中的所有字符重新连接为字符串str1_diff_cp = ''.join(str1_diff_cp)words1 = str1_diff.strip().split(' ')cmds1 = str1_diff_cp.strip().split(' ')print(hc,words1, cmds1)# 构造批注jsonfor i, c in enumerate(cmds1):if (c[0] == '^'):ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": words1[i],"comments": "信息更改\n","info": "PDF中在此处更改了内容"})elif (c[0] == '-'):ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": words1[i],"comments": "信息删除\n","info": "PDF中在此处删除的内容:" + words1[i]})index += 3elif (hc == '-+?'):str1 = contents[index]str1_diff = list([])str1_diff_cp = copy.deepcopy(str1_diff)str2 = contents[index + 1]str2_diff = list(contents[index + 2])str2_diff_cp = copy.deepcopy(str2_diff)for i,c in enumerate(str2_diff):if(c=='+'):str2_diff[i] = str2[i]#逆向记录+前面的内容,最多逆向扫描之前5个字符tmp = i-1add_str1 = []times = 0while(tmp>=0 and str2_diff_cp[tmp]==' '):times += 1add_str1.insert(0,str2[tmp])tmp -= 1if(times>=5):breakif(add_str1!=[]):add_str1 = ''.join(add_str1)# print('add_str1:', add_str1)str1_diff.append(' '+add_str1)#加空格是为了后面好分割str1_diff_cp.append(' '+'+')if (c == '^'):str2_diff[i] = str2[i]str1_diff = ''.join(str1_diff)  # 用空串将列表中的所有字符重新连接为字符串str1_diff_cp = ''.join(str1_diff_cp)words1 = str1_diff.strip().split(' ')cmds1 = str1_diff_cp.strip().split(' ')print(hc, words1, cmds1)str2_diff = ''.join(str2_diff)str2_diff_cp = ''.join(str2_diff_cp)words2 = str2_diff.strip().split(' ')cmds2 = str2_diff_cp.strip().split(' ')print(hc, words2, cmds2)# 构造批注jsonfor i, c in enumerate(cmds1):if (c[0] == '^'):ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": words1[i],"comments": "信息更改\n","info": "PDF中在此处更改的内容:" + words2[i]})elif (c[0] == '+'):ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": words1[i],"comments": "信息添加\n","info": "PDF中在'{}'此处之后添加了内容:{}".format(words1[i],words2[i])})index += 3elif(hc=='-'):str1 = contents[index]ans.append({"word1_sentence": str1,"word1_sentence_to_add_comments": str1,"comments": "信息删除\n","info": "PDF中在此处删除的内容:" + str1})index += 1elif(hc=='+'):tmp = index-1while(tmp>=0 and (predict_list[tmp][0]=='+'or predict_list[tmp][0]=='?'or predict_list[tmp][0]=='^')):tmp -= 1pre_str1 = contents[tmp]str1 = contents[index]ans.append({"word1_sentence": pre_str1,"word1_sentence_to_add_comments": pre_str1[-5:],"comments": "信息添加\n","info": "PDF中在'{}'此处之后添加了内容:{}".format(pre_str1[-5:],str1)})index += 1elif(hc=='x'):index += 1continuereturn ansdef addComment2docx(file_path,ans,isVisible):word = Dispatch('Word.Application')word.Visible = isVisiblepath = sys.path[0] + file_pathdoc = word.Documents.Open(FileName=path, Encoding='gbk')# print(doc.paragraphs[0]) # 打印文档第一段s = word.Selections.Start = s.End = 0for obj in ans:res = s.Find.Execute(obj['word1_sentence_to_add_comments'])if (res):doc.Comments.Add(Range=word.Selection.Range, Text=obj['comments'] + obj['info'])print('添加批注成功!')else:print('未在文件中找到对应文本:{},添加批注失败.'.format(obj['word1_sentence_to_add_comments']))s.Start = s.End  # 光标向前推进def run(pickle_file,add_comments_file_path,isVisible):with open(pickle_file, 'rb') as f:predict_list = pickle.load(f, encoding='bytes')header_list,contents = prepare_result(predict_list)header_combination = positive_max_matching(header_list)ans = process_result(predict_list,contents,header_combination)addComment2docx(add_comments_file_path,ans,isVisible)if __name__ == '__main__':pickle_file = 'compare_results.pickle'file_path = '/documents/diff_test1.docx'isVisible = 1 #是否可视化添加批注流程run(pickle_file,file_path,isVisible)print("finished.")
    
  • 第二版的需求需要在Linux上运行,但是Win32com这个包只能在windows系统上运行,因此需要重新设计向word插入批注的方法

    • 主要依赖包:python-docx, bayoo-docx
    • 步骤:
    pip install python-docx
    pip install bayoo-docx
    

    插入批注的样例代码:

    import docxdocument = docx.Document()
    paragraph1 = document.add_paragraph('text') # create new paragraph
    comment = paragraph.add_comment('comment',author='Obay Daba',initials= 'od') # add a comment on the entire paragraph
    paragraph2 = document.add_paragraph('text') # create another paragraph
    run = paragraph2.add_run('texty') add a run to the paragraph
    run.add_comment('comment') # add a comment only for the run text
    run.add_comment('comment2')
    run_comments = run.comments
    paragraph.add_footnote('footnote text') # add a footnote
    
    • 但是bayoo-docx插入批注的方法只能是通过新建paragraph或者新建run,然后add_comment(“xxxx”),而且word的paragraph中的run并不统一,通过遍历原文档的方法不能随心所欲的在paragraph中的某一部分run加批注,也就是需要自定义run模块。
    • 思路:
      • 原文档按行遍历,将每行按照批注分割,再依次插入新的word文档

      • 获取word文档中的段落,表格等信息:

        import docx
        # document = docx.Document()
        # document.save('demo.docx')
        def get_docx_content(file):para_contents = []table_contents = []total_comtents = []doc = docx.Document(file)# 获取段落的内容for para in doc.paragraphs:try:para_contents.append(para.text.replace(u'\xa0', u' '))total_comtents.append(para.text.replace(u'\xa0', u' '))# runs = para.runs# print("****para:", para.text)# for i, item in enumerate(runs):#     print(i, item.text)except:continue# 获取表格的内容tbs = doc.tablesfor tb in tbs:tb_contents = []# print(len(tb.rows),len(tb.columns))for row in tb.rows:  # 行for cell in row.cells:  # 列if (cell.text == ''):continuetb_contents.append(cell.text.replace(u'\xa0', u' '))total_comtents.append(cell.text.replace(u'\xa0', u' '))table_info = {'rows': len(tb.rows), 'cols': len(tb.columns), 'contents': tb_contents}table_contents.append(table_info)return para_contents, table_contents, total_comtentsif __name__ == '__main__':file = './documents/xxx.docx'para_contents, table_contents, total_contents = get_docx_content(file)print(para_contents)print()print(table_contents)
        
      • 获取word中对象(包括表格)的相对位置:

        
        from docx.document import Document
        from docx.oxml.table import CT_Tbl
        from docx.oxml.text.paragraph import CT_P
        from docx.table import _Cell, Table
        from docx.text.paragraph import Paragraph
        import docxdef iter_block_items(parent):if isinstance(parent, Document):parent_elm = parent.element.bodyelif isinstance(parent, _Cell):parent_elm = parent._tcelse:raise ValueError("something's not right")for child in parent_elm.iterchildren():if isinstance(child, CT_P):yield Paragraph(child, parent)elif isinstance(child, CT_Tbl):yield Table(child, parent)# table = Table(child, parent)# for row in table.rows:#     for cell in row.cells:#         for paragraph in cell.paragraphs:#             yield paragraphfrom collections import defaultdictdef get_object_location_in_word(file):doc = docx.Document(file)index_dict = defaultdict(list)for i,block in enumerate(iter_block_items(doc)):# print(block.style.name)if block.style.name == 'Normal Table':print("表格")index_dict['table'].append(i)elif block.style.name == 'Normal':print("自然段")elif block.style.name == 'List Paragraph':print("List Paragraph")elif block.style.name == 'Normal (Web)':print("Normal (Web)")return index_dictif __name__ == '__main__':file = './documents/xxxx.docx'index_dict = get_object_location_in_word(file)print(index_dict)
        
      • Linux平台实现Word转为PDF

      • 子项目:Gitlab cicd运用之.git-ci.yml文件配置持续集成

项目五:去除页眉页脚

参考本人另一篇博客,传送门

项目六:基于OCR返回的json结果去除图片水印

描述:针对公司的OCR识别系统可能会将水印也识别出来的问题,通过json结果去掉水印的识别框和对应的文本内容。

方法步骤:
1.在json中解析绿色框的格式:

2.根据结果可以发现,绿色框一定是矩形,通过坐标计算每个框的斜率。

list=[X坐标值,Y坐标值]
def cal_slope(list1,list2):if(list2[0]==list1[0]):return None #x坐标相等,斜率不存在else:return (list2[1]-list1[1])/(list2[0]-list1[0])

3.通过所有框斜率进行KMeans聚类(k=2),即可分为两个簇

from sklearn.cluster import KMeans #pip install sklearn
box_slope_list = [0.1,0.2,0.4,0.5,...] #存入的斜率值list,待进行一维数据聚类
def cluster_slope(box_slope_list):box_slope_array = np.array(box_slope_list, dtype=int).reshape(-1, 1)km = KMeans(n_clusters=2) #k=2聚类km.fit(box_slope_array)cluster_result = km.predict(box_slope_array)return cluster_result

4.计算两个簇对应文本的平均相似度(文本两两比较),将簇内文本相似度较大的一类识别为水印

import difflib
def string_similar(s1, s2):return difflib.SequenceMatcher(None, s1, s2).quick_ratio()

5.根据簇的索引,对水印进行去除

收获:

1.将json字符串存为文件:

import json
target_json_path = '../../upload/result.json'
with open(target_json_path,'w',encoding='utf-8') as file_obj:json.dump(rst,file_obj,ensure_ascii=False,indent=4)

2.通过索引遍历dataframe的每行:

for i in range(0,len(df)):print(df.iloc(i,df['列名']))

项目七:Word内容关键字段抽取

需求:基于word转为txt的结果,将字符串中的关键信息提取出来
比如提取出甲方地址,该信息占txt中的一行:

地址:[XX市XX区XX街道XX路88号]

普通语法:

dic = {}
if('甲方地址' not in dic.keys() and '地址' in line and ':' in line):owner_addr = line.split(':')[1]dic['甲方地址'] = owner_addr.replace('[', '').replace(']', '')

正则表达式的运用:

import re
if ('甲方联系地址' not in dic.keys()):pattern = re.compile(r'^.*?地址:(.*?)$')matchobj = pattern.match(line)if (matchobj and len(matchobj.groups()) >= 1):#大于等于后面的数字取决于正则表达式中括号的数量owner_addr = matchobj.group(1)dic['甲方联系地址'] = owner_addr.replace('[', '').replace(']', '')

知识点:
Python 正则表达式用法

  1. re.compile 函数

compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。
语法格式为:
re.compile(pattern[, flags])

 参数:pattern : 一个字符串形式的正则表达式flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:re.I 忽略大小写re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境re.M 多行模式re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库re.X 为了增加可读性,忽略空格和 # 后面的注释
  1. ^ 匹配字符串的开头
  2. $ 匹配字符串的末尾。
  3. re中的 (): 对正则表达式分组并记住匹配的文本,比如上述例子中地址:(.*?)的括号,方便直接取出匹配成功后想要的子串。
  4. m.group(0) : 返回匹配成功的整个子串
  5. m.group(1) : 返回第一个分组匹配成功的子串
  6. m.groups() : 等价于 (m.group(1), m.group(2), …)

项目八:文件比对项目服务开发

需求:根据用户传入的两份文件,后端需要通过转PDF,OCR识别,去文本、页眉页脚、印章等流程得到文本字符串和对应的OCR定位box框组合成Json,需要通过Airflow封装成API服务,结合minio文件上传服务提供给前端。

收获:

Airflow,Minio相关概念和作用原理

Airflow

Airflow入门原理讲解

关键概念:

  • Airflow 是一个使用 python 语言编写的 data pipeline 调度和监控工作流的平台。 Airflow 核心原理是通过 DAG(Directed acyclic graph 有向无环图)来管理任务流程的任务调度工具, 不需要知道业务数据的具体内容,设置任务的依赖关系即可实现任务调度。
  • 在一个可扩展的生产环境中,Airflow 含有以下组件:
    • 元数据库:这个数据库存储有关任务状态的信息。
    • 调度器:Scheduler 是一种使用 DAG 定义结合元数据中的任务状态来决定哪些任务需要被执行以及任务执行优先级的过程。 调度器通常作为服务运行。
    • 执行器:Executor 是一个消息队列进程,它被绑定到调度器中,用于确定实际执行每个任务计划的工作进程。 有不同类型的执行器,每个执行器都使用一个指定工作进程的类来执行任务。 例如,LocalExecutor 使用与调度器进程在同一台机器上运行的并行进程执行任务。 其他像 CeleryExecutor 的执行器使用存在于独立的工作机器集群中的工作进程执行任务。
    • Workers:这些是实际执行任务逻辑的进程,由正在使用的执行器确定。
    • Airflow架构:
  • 一个DAG里有一个或者多个Task每一个Task都负责执行一段业务功能完整的核心代码,比如一个Dag表示做高考卷子,那么定义的task包括「TaskA:做语文卷子,Task B:做数学卷子…」
  • 使用位移符号来表示一个Dag中的各个task执行顺序:task1 >> task2 >> task3等价于
    task1.set_downstream(task2)
    task2.set_downstream(task23)
    
  • 定义Dag和Task的执行顺序Demo:
    from datetime import datetime, timedelta
    from airflow import DAG
    from airflow.utils import dates
    from airflow.utils.helpers import chain
    from airflow.operators.bash_operator import BashOperator
    from airflow.operators.python_operator import PythonOperatordef default_options():default_args = {'owner': 'airflow',  # 拥有者名称'start_date': dates.days_ago(1),  # 第一次开始执行的时间,为 UTC 时间'retries': 1,  # 失败重试次数'retry_delay': timedelta(seconds=5)  # 失败重试间隔}return default_args# 定义DAG
    def test1(dag):t = "pwd"# operator 支持多种类型, 这里使用 BashOperatortask = BashOperator(task_id='test1',  # task_idbash_command=t,  # 指定要执行的命令dag=dag  # 指定归属的dag)return taskdef hello_world_1():current_time = str(datetime.today())print('hello world at {}'.format(current_time))def test2(dag):# PythonOperatortask = PythonOperator(task_id='test2',python_callable=hello_world_1,  # 指定要执行的函数dag=dag)return taskdef test3(dag):t = "date"task = BashOperator(task_id='test3',bash_command=t,dag=dag)return taskwith DAG('test_task',  # dag_iddefault_args=default_options(),  # 指定默认参数schedule_interval="20 8 * * *"  # 执行周期
    ) as d:task1 = test1(d)task2 = test2(d)task3 = test3(d)chain(task1, task2, task3)  # 指定执行顺序
    

Minio

个人理解:专用于对象云端存储的系统,配合前后端解耦开发,如果有复杂类型文件(比如文件、视频、图片)的前后端存取交互,minio相当于一个中间池,后端指定一个minio的bucket传上去,前端再从这个相同的bucket取就行了。

官方定义:高性能、分布式的对象存储系统,MinIO与传统的存储和其他的对象存储不同的是:它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。 所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,失去MinIO的易用性、高效性。 这样的结果所带来的好处是:它能够更简单的实现局有弹性伸缩能力的原生对象存储服务。

Minio架构分析
Minio采用去中心化的无共享架构,对象数据被打散存放在不同节点的多块硬盘,对外提供统一命名空间访问,并通过负载均衡或者DNS轮询在各个服务器之间实现负载均衡。

Minio集群部署方式:一种是本地分布式集群部署,一种是联盟模式部署。

分布式锁管理

与分布式数据库类似,Minio也会存在面临数据一致性的问题:一个客户端在读取一个对象的同时,另一个客户端可能正在修改或者删除这个对象。为了避免出现不一致的情况。Minio专门设计并实现了dsync分布式锁管理器,来控制数据一致性。

  • 任何一个节点的锁请求都会广播给集群内的所有在线节点
  • 如果收到N/2+1个节点的同意,则获取锁成功
  • 没有主节点,每个节点互相对等,节点间通过stale lock检测机制,判断节点的状态及持有锁情况
  • 由于设计简单,比较粗糙。有一定的缺陷性,最多支持32个节点。无法避免锁丢失的场景。不过基本满足可用需求。

数据结构:
Minio对象存储系统把存储资源组织为租户-桶-对象的形式。

  • 对象:类似于hash表中的表项,名字是关键字,内容相当于值
  • 桶(bucket):是若干个对象的逻辑抽象,是盛装对象的容器
  • 租户:用于隔离存储资源。在租户下可以建立桶、存储对象
  • 用户:在租户下面创建的用于访问不同桶的账号。可以使用minio提供的mc命令设置不同用户访问各个桶的权限

git相关

图解git

  • git checkout – files 把文件从暂存区域复制到工作目录,用来丢弃本地修改。

【4Paradigm】第四范式实习经验与收获整理相关推荐

  1. 【MSRA】微软亚洲研究院实习经验收获整理

    [MSRA]微软亚洲研究院实习经验收获整理 干货相关 VScode连接远程服务器进行开发 SCP上传文件到服务器上或从服务器上下载文件 解决vscode中引入python包出现"Import ...

  2. 未明学院:找实习是门“玄学”?学姐这份券商、咨询、500强企业实习经验收藏好!

    作者: X同学,未明学院训练营优秀学员 我是一名商科大四在读的学生,从大二开始自己摸索着接触了很多种类的实习,前前后后有券商营业部.科创板投行.英国咨询公司.本土咨询公司.券商行业研究部门等不同类型的 ...

  3. 没有实习经验,没有项目经验,简历怎么写?

    链接:https://www.jianshu.com/p/2d0a3a2f0e9c 一般来说,应届生简历要包含个人信息.求职意向.实习经历.项目经历.校内经历.教育背景.证书荣誉.技能特长.自我评价等 ...

  4. 【实习经验】从平安科技到商汤科技,谈谈我的实习经历

    点击上方,选择星标或置顶,每天给你送干货! 阅读大概需要10分钟 跟随小博主,每天进步一丢丢 转载自:九点澡堂子TM 春招蠢蠢欲动,秋招按现在疫情来看情况不容乐观,陆续有人问我相关的问题,并且从大家的 ...

  5. 摩根IT实习经验谈及其他

    发信人: WuGuiKe (马甲), 信区: Job_Intern 标 题: 摩根IT实习经验谈及其他 发信站: 日月光华 (2007年11月01日21:30:36 星期四), 站内信件 在摩根IT的 ...

  6. 转载:香港实习生微软实习经验分享

    原文地址:SQL Server中国团队博客     2009年夏天,微软中国研发集团服务器与开发工具事业部引来了第三批香港实习生.  Marcus就是其中一位,土生土长于香港.半谙普通话的二十岁小伙子 ...

  7. 在广州找前端开发实习经验分享2

    记录一下我面试过的所有公司,春招前面试的所有的公司了! 一.广州华资软件技术有限公司(2020.11.18) 工资很低,hr最后问有什么想问她的吗?说完这句话就秒接"没有也没有关系--&qu ...

  8. Java 开发实习经验

    半年的实习结束了,总结了一下自己实习经验,希望对即将入职场的同学有些启发. 1.勤记录 进入一个项目组,有一系列需要熟悉的东西,比如文件名,函数,环境配置,或者是分配的任务. 这些基础的东西反复问别人 ...

  9. 大数据在线实习项目能收获什么呢?

    大数据在线实习项目提供在线大数据项目,参与真实企业项目,可提供实习证明. 项目实习过程会根据项目背景.项目目标.项目数据来逐一展开,一个项目的项目周期大概为4周时间,同时也可以根据自身时间条件来调整. ...

最新文章

  1. 三国演义人物出场统计代码含义_实例2之《三国演义》人物出场统计
  2. 《iBATIS 实战》——国内第一本iBATIS巨著(iBATIS之父撰写)
  3. 阿里云支撑马来西亚数字自由贸易区落地 帮助马来西亚中小企业参与全球贸易...
  4. Arcface v1 论文翻译与解读
  5. 词性标注与命名实体识别
  6. Facebook的bigpipe
  7. 漫画:凌晨2点,老板在工作群@了我...
  8. python1000行代码_用好这3行代码,可以让你的Python脚本速度提升5倍!
  9. gradle 失败 编译项目_maven常见问题处理(3-3)Gradle编译时下载依赖失败解决方法...
  10. 新浪的wap网站,发现原来我们的head存在着这样的差异
  11. WMI 错误 10的解决
  12. android键盘表情流畅切换实现
  13. java设置查看源文件路径
  14. 计算机基础知识如何打钩,Word的方框中怎么输入√号?教你快速在Word中输入对号(打钩)-电脑自学网...
  15. Intel SGX开发者参考书(五)—— Intel SGX SDK示例代码(二)
  16. 悼念蓝色理想FLASH区版主:手工感情==生命如此之脆弱
  17. 关于ElementUI 图标字体无法正常显示异常问题处理
  18. 【吐槽】B站大量番剧下架,程序猿们这时都在干什么?
  19. 宿骆氏亭寄怀崔雍崔衮
  20. 英文美文欣赏之《小王子》

热门文章

  1. Flink运行时架构 完整使用 (第四章)
  2. 一头扎进Shiro-自定义Realm
  3. PMP考试从报名到取证一条龙流程都在这里!文末有免费资料~
  4. 数据结构 第七章 图的作作业
  5. 判断一个日期是一年中的第几天
  6. cocos creator 微信小游戏接口
  7. 无尺度网络的鲁棒性和脆弱(小世界网络)
  8. Nvidia-docker2 for ubuntu18.04初试!
  9. 国内免费发布信息平台哪个好?
  10. 全国城市根据首字母分类json格式