convert_pdf.py 94 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262
  1. import copy
  2. import inspect
  3. import io
  4. import logging
  5. import os
  6. import re
  7. import sys
  8. from bs4 import BeautifulSoup
  9. sys.path.append(os.path.dirname(__file__) + "/../")
  10. from pdfplumber import PDF
  11. from pdfplumber.table import TableFinder
  12. from pdfplumber.page import Page as pdfPage
  13. from format_convert.convert_tree import _Document, _Page, _Image, _Sentence, _Table
  14. import time
  15. import pdfminer
  16. import math
  17. from scipy.stats import linregress
  18. from matplotlib import pyplot as plt
  19. from shapely.geometry import LineString, Point
  20. from format_convert import timeout_decorator
  21. from PIL import Image
  22. from format_convert.convert_image import image_process
  23. from format_convert.convert_need_interface import from_ocr_interface, from_office_interface
  24. import traceback
  25. import cv2
  26. import PyPDF2
  27. from PyPDF2 import PdfFileReader, PdfFileWriter
  28. from pdfminer.pdfparser import PDFParser
  29. from pdfminer.pdfdocument import PDFDocument
  30. from pdfminer.pdfpage import PDFPage
  31. from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
  32. from pdfminer.converter import PDFPageAggregator
  33. from pdfminer.layout import LTTextBoxHorizontal, LAParams, LTFigure, LTImage, LTCurve, LTText, LTChar, LTRect, \
  34. LTTextBoxVertical, LTLine, LTTextContainer
  35. from format_convert.utils import judge_error_code, add_div, get_platform, get_html_p, string_similarity, LineTable, \
  36. get_logger, log, memory_decorator, draw_lines_plt, get_garble_code, line_is_cross, get_md5_from_bytes, bytes2np
  37. import fitz
  38. from format_convert.wrapt_timeout_decorator import timeout
  39. @memory_decorator
  40. def pdf2Image(path, save_dir):
  41. log("into pdf2Image")
  42. try:
  43. try:
  44. doc = fitz.open(path)
  45. except Exception as e:
  46. log("pdf format error!")
  47. # print("pdf format error!", e)
  48. return [-3]
  49. # output_image_list = []
  50. output_image_dict = {}
  51. page_count = doc.page_count
  52. for page_no in range(page_count):
  53. # 限制pdf页数,只取前10页后10页
  54. if page_count > 20:
  55. if 10 <= page_no < page_count - 10:
  56. # log("pdf2Image: pdf pages count " + str(doc.page_count)
  57. # + ", only get 70 pages")
  58. continue
  59. try:
  60. page = doc.loadPage(page_no)
  61. output = save_dir + "_page" + str(page_no) + ".png"
  62. rotate = int(0)
  63. # 每个尺寸的缩放系数为1.3,这将为我们生成分辨率提高2.6的图像。
  64. # 此处若是不做设置,默认图片大小为:792X612, dpi=96
  65. # (1.33333333 --> 1056x816) (2 --> 1584x1224)
  66. # (1.183, 2.28 --> 1920x1080)
  67. zoom_x = 3.
  68. zoom_y = 3.
  69. # mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
  70. mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
  71. pix = page.getPixmap(matrix=mat, alpha=False)
  72. pix.writePNG(output)
  73. pdf_image = cv2.imread(output)
  74. print("pdf_image", page_no, pdf_image.shape)
  75. # output_image_list.append([page_no, output])
  76. output_image_dict[int(page_no)] = output
  77. except ValueError as e:
  78. traceback.print_exc()
  79. if str(e) == "page not in document":
  80. log("pdf2Image page not in document! continue..." + str(page_no))
  81. continue
  82. elif "encrypted" in str(e):
  83. log("pdf2Image document need password " + str(page_no))
  84. return [-7]
  85. except RuntimeError as e:
  86. if "cannot find page" in str(e):
  87. log("pdf2Image page {} not in document! continue... ".format(str(page_no)) + str(e))
  88. continue
  89. else:
  90. traceback.print_exc()
  91. return [-3]
  92. return [output_image_dict]
  93. except Exception as e:
  94. log("pdf2Image error!")
  95. print("pdf2Image", traceback.print_exc())
  96. return [-1]
  97. @timeout(10, timeout_exception=TimeoutError)
  98. def pdf_analyze(interpreter, page, device, page_no):
  99. log("into pdf_analyze")
  100. pdf_time = time.time()
  101. # print("pdf_analyze interpreter process...")
  102. interpreter.process_page(page)
  103. # print("pdf_analyze device get_result...")
  104. layout = device.get_result()
  105. log("pdf2text page " + str(page_no) + " read time " + str(time.time() - pdf_time))
  106. return layout
  107. @memory_decorator
  108. def pdf2text(path, unique_type_dir):
  109. log("into pdf2text")
  110. try:
  111. # pymupdf pdf to image
  112. save_dir = path.split(".")[-2] + "_" + path.split(".")[-1]
  113. output_image_dict = pdf2Image(path, save_dir)
  114. if judge_error_code(output_image_dict):
  115. return output_image_dict
  116. output_image_dict = output_image_dict[0]
  117. output_image_no_list = list(output_image_dict.keys())
  118. output_image_no_list.sort(key=lambda x: x)
  119. # 获取每页pdf提取的文字、表格的列数、轮廓点、是否含表格、页码
  120. # page_info_list = []
  121. page_info_dict = {}
  122. has_table_dict = {}
  123. no_table_dict = {}
  124. for page_no in output_image_no_list:
  125. img_path = output_image_dict.get(page_no)
  126. print("pdf page", page_no, "in total", output_image_no_list[-1])
  127. # 读不出来的跳过
  128. try:
  129. img = cv2.imread(img_path)
  130. img_size = img.shape
  131. except:
  132. log("pdf2text read image in page fail! continue...")
  133. continue
  134. # 每张图片处理
  135. text, column_list, outline_points, is_table = image_process(img, img_path, use_ocr=False)
  136. if judge_error_code(text):
  137. return text
  138. # page_info_list.append([text, column_list, outline_points, is_table,
  139. # page_no, img_size])
  140. page_info = [text, column_list, outline_points, is_table, img_size]
  141. page_info_dict[int(page_no)] = page_info
  142. # 包含table的和不包含table的
  143. if is_table:
  144. has_table_dict[int(page_no)] = page_info
  145. else:
  146. no_table_dict[int(page_no)] = page_info
  147. has_table_no_list = list(has_table_dict.keys())
  148. has_table_no_list.sort(key=lambda x: x)
  149. page_no_list = list(page_info_dict.keys())
  150. page_no_list.sort(key=lambda x: x)
  151. # 页码表格连接
  152. table_connect_list, connect_text_list = page_table_connect(has_table_dict)
  153. if judge_error_code(table_connect_list):
  154. return table_connect_list
  155. # 连接的页码
  156. table_connect_page_no_list = []
  157. for area in connect_text_list:
  158. table_connect_page_no_list.append(area[1])
  159. print("pdf2text table_connect_list", table_connect_list)
  160. print("connect_text_list", connect_text_list)
  161. # pdfminer 方式
  162. try:
  163. fp = open(path, 'rb')
  164. # 用文件对象创建一个PDF文档分析器
  165. parser = PDFParser(fp)
  166. # 创建一个PDF文档
  167. doc = PDFDocument(parser)
  168. # 连接分析器,与文档对象
  169. rsrcmgr = PDFResourceManager()
  170. device = PDFPageAggregator(rsrcmgr, laparams=LAParams())
  171. interpreter = PDFPageInterpreter(rsrcmgr, device)
  172. # 判断是否能读pdf
  173. for page in PDFPage.create_pages(doc):
  174. break
  175. except pdfminer.psparser.PSEOF as e:
  176. # pdfminer 读不了空白页的对象,直接使用pymupdf转换出的图片进行ocr识别
  177. log("pdf2text " + str(e) + " use ocr read pdf!")
  178. text_list = []
  179. for page_no in page_no_list:
  180. log("pdf2text ocr page_no " + str(page_no))
  181. page_info = page_info_dict.get(page_no)
  182. # 表格
  183. if page_info[3]:
  184. # 判断表格是否跨页连接
  185. area_no = 0
  186. jump_page = 0
  187. for area in table_connect_list:
  188. if page_no in area:
  189. # 只记录一次text
  190. if page_no == area[0]:
  191. image_text = connect_text_list[area_no][0]
  192. text_list.append([image_text, page_no, 0])
  193. jump_page = 1
  194. area_no += 1
  195. # 是连接页的跳过后面步骤
  196. if jump_page:
  197. continue
  198. # 直接取text
  199. image_text = page_info_dict.get(page_no)[0]
  200. text_list.append([image_text, page_no, 0])
  201. # 非表格
  202. else:
  203. with open(output_image_dict.get(page_no), "rb") as ff:
  204. image_stream = ff.read()
  205. image_text = from_ocr_interface(image_stream)
  206. text_list.append([image_text, page_no, 0])
  207. text_list.sort(key=lambda z: z[1])
  208. text = ""
  209. for t in text_list:
  210. text += t[0]
  211. return [text]
  212. except Exception as e:
  213. log("pdf format error!")
  214. traceback.print_exc()
  215. return [-3]
  216. text_list = []
  217. page_no = 0
  218. pages = PDFPage.create_pages(doc)
  219. pages = list(pages)
  220. page_count = len(pages)
  221. for page in pages:
  222. log("pdf2text pymupdf page_no " + str(page_no))
  223. # 限制pdf页数,只取前100页
  224. # if page_no >= 70:
  225. # log("pdf2text: pdf pages only get 70 pages")
  226. # break
  227. if page_count > 20:
  228. if 10 <= page_no < page_count - 10:
  229. page_no += 1
  230. continue
  231. # 判断页码在含表格页码中,直接拿已生成的text
  232. if page_no in has_table_no_list:
  233. # 判断表格是否跨页连接
  234. area_no = 0
  235. jump_page = 0
  236. for area in table_connect_list:
  237. if page_no in area:
  238. # 只记录一次text
  239. if page_no == area[0]:
  240. image_text = connect_text_list[area_no][0]
  241. text_list.append([image_text, page_no, 0])
  242. jump_page = 1
  243. area_no += 1
  244. # 是连接页的跳过后面步骤
  245. if jump_page:
  246. page_no += 1
  247. continue
  248. # 直接取text
  249. image_text = has_table_dict.get(page_no)[0]
  250. text_list.append([image_text, page_no, 0])
  251. page_no += 1
  252. continue
  253. # 不含表格的解析pdf
  254. else:
  255. if get_platform() == "Windows":
  256. try:
  257. interpreter.process_page(page)
  258. layout = device.get_result()
  259. except Exception:
  260. log("pdf2text pdfminer read pdf page error! continue...")
  261. continue
  262. else:
  263. # 设置超时时间
  264. try:
  265. # 解析pdf中的不含表格的页
  266. if get_platform() == "Windows":
  267. origin_pdf_analyze = pdf_analyze.__wrapped__
  268. layout = origin_pdf_analyze(interpreter, page, device)
  269. else:
  270. layout = pdf_analyze(interpreter, page, device, page_no)
  271. except TimeoutError as e:
  272. log("pdf2text pdfminer read pdf page time out!")
  273. return [-4]
  274. except Exception:
  275. log("pdf2text pdfminer read pdf page error! continue...")
  276. continue
  277. # 判断该页有没有文字对象,没有则有可能是有水印
  278. only_image = 1
  279. image_count = 0
  280. for x in layout:
  281. if isinstance(x, LTTextBoxHorizontal):
  282. only_image = 0
  283. if isinstance(x, LTFigure):
  284. image_count += 1
  285. # 如果该页图片数量过多,直接ocr整页识别
  286. log("pdf2text image_count " + str(image_count))
  287. if image_count >= 3:
  288. image_text = page_info_dict.get(page_no)[0]
  289. if image_text is None:
  290. with open(output_image_dict.get(page_no), "rb") as ff:
  291. image_stream = ff.read()
  292. image_text = from_ocr_interface(image_stream)
  293. if judge_error_code(image_text):
  294. return image_text
  295. page_info_dict[page_no][0] = image_text
  296. text_list.append([image_text, page_no, 0])
  297. page_no += 1
  298. continue
  299. order_list = []
  300. for x in layout:
  301. # 该对象是否是ocr识别
  302. ocr_flag = 0
  303. if get_platform() == "Windows":
  304. # print("x", page_no, x)
  305. print()
  306. if isinstance(x, LTTextBoxHorizontal):
  307. image_text = x.get_text()
  308. # 无法识别编码,用ocr
  309. if re.search('[(]cid:[0-9]+[)]', image_text):
  310. print(re.search('[(]cid:[0-9]+[)]', image_text))
  311. image_text = page_info_dict.get(page_no)[0]
  312. if image_text is None:
  313. with open(output_image_dict.get(page_no), "rb") as ff:
  314. image_stream = ff.read()
  315. image_text = from_ocr_interface(image_stream)
  316. if judge_error_code(image_text):
  317. return image_text
  318. page_info_dict[page_no][0] = image_text
  319. image_text = add_div(image_text)
  320. # order_list.append([image_text, page_no, x.bbox[1]])
  321. order_list = [[image_text, page_no, x.bbox[1]]]
  322. break
  323. else:
  324. image_text = add_div(image_text)
  325. order_list.append([image_text, page_no, x.bbox[1]])
  326. continue
  327. if isinstance(x, LTFigure):
  328. for image in x:
  329. if isinstance(image, LTImage):
  330. try:
  331. print("pdf2text LTImage size", page_no, image.width, image.height)
  332. image_stream = image.stream.get_data()
  333. # 小的图忽略
  334. if image.width <= 300 and image.height <= 300:
  335. continue
  336. # 有些水印导致pdf分割、读取报错
  337. # if image.width <= 200 and image.height<=200:
  338. # continue
  339. # img_test = Image.open(io.BytesIO(image_stream))
  340. # img_test.save('temp/LTImage.jpg')
  341. # 查看提取的图片高宽,太大则抛错用pdf输出图进行ocr识别
  342. img_test = Image.open(io.BytesIO(image_stream))
  343. if img_test.size[1] > 2000 or img_test.size[0] > 1500:
  344. print("pdf2text LTImage stream output size", img_test.size)
  345. raise Exception
  346. # 比较小的图则直接保存用ocr识别
  347. else:
  348. img_test.save('temp/LTImage.jpg')
  349. with open('temp/LTImage.jpg', "rb") as ff:
  350. image_stream = ff.read()
  351. image_text = from_ocr_interface(image_stream)
  352. if judge_error_code(image_text):
  353. return image_text
  354. # except pdfminer.pdftypes.PDFNotImplementedError:
  355. # with open(output_image_list[page_no], "rb") as ff:
  356. # image_stream = ff.read()
  357. except Exception:
  358. log("pdf2text pdfminer read image in page " + str(page_no) +
  359. " fail! use pymupdf read image...")
  360. # print(traceback.print_exc())
  361. image_text = page_info_dict.get(page_no)[0]
  362. if image_text is None:
  363. with open(output_image_dict.get(page_no), "rb") as ff:
  364. image_stream = ff.read()
  365. image_text = from_ocr_interface(image_stream)
  366. if judge_error_code(image_text):
  367. return image_text
  368. page_info_dict[page_no][0] = image_text
  369. ocr_flag = 1
  370. # 判断只拿到了水印图: 无文字输出且只有图片对象
  371. if image_text == "" and only_image:
  372. # 拆出该页pdf
  373. try:
  374. log("pdf2text guess pdf has watermark")
  375. split_path = get_single_pdf(path, page_no)
  376. except:
  377. # 如果拆分抛异常,则大概率不是水印图,用ocr识别图片
  378. log("pdf2text guess pdf has no watermark")
  379. image_text = page_info_dict.get(page_no)[0]
  380. if image_text is None:
  381. with open(output_image_dict.get(page_no), "rb") as ff:
  382. image_stream = ff.read()
  383. image_text = from_ocr_interface(image_stream)
  384. order_list.append([image_text, page_no, -1])
  385. page_info_dict[page_no][0] = image_text
  386. ocr_flag = 1
  387. continue
  388. if judge_error_code(split_path):
  389. return split_path
  390. # 调用office格式转换
  391. file_path = from_office_interface(split_path, unique_type_dir, 'html', 3)
  392. # if file_path == [-3]:
  393. # return [-3]
  394. if judge_error_code(file_path):
  395. return file_path
  396. # 获取html文本
  397. image_text = get_html_p(file_path)
  398. if judge_error_code(image_text):
  399. return image_text
  400. if get_platform() == "Windows":
  401. print("image_text", page_no, x.bbox[1], image_text)
  402. with open("temp" + str(x.bbox[0]) + ".jpg", "wb") as ff:
  403. ff.write(image_stream)
  404. image_text = add_div(image_text)
  405. if ocr_flag:
  406. order_list.append([image_text, page_no, -1])
  407. else:
  408. order_list.append([image_text, page_no, x.bbox[1]])
  409. order_list.sort(key=lambda z: z[2], reverse=True)
  410. # 有ocr参与识别
  411. if order_list[-1][2] == -1:
  412. ocr_order_list = [order_list[-1]]
  413. not_ocr_order_list = []
  414. not_ocr_text = ""
  415. # 去重,因读取失败而重复获取
  416. for order in order_list:
  417. if order[2] != -1:
  418. not_ocr_order_list.append(order)
  419. not_ocr_text += order[0]
  420. if string_similarity(ocr_order_list[0][0], not_ocr_text) >= 0.85:
  421. order_list = not_ocr_order_list
  422. else:
  423. order_list = ocr_order_list
  424. for order in order_list:
  425. text_list.append(order)
  426. page_no += 1
  427. text = ""
  428. for t in text_list:
  429. # text += add_div(t[0])
  430. if t[0] is not None:
  431. text += t[0]
  432. return [text]
  433. except UnicodeDecodeError as e:
  434. log("pdf2text pdfminer create pages failed! " + str(e))
  435. return [-3]
  436. except Exception as e:
  437. log("pdf2text error!")
  438. traceback.print_exc()
  439. return [-1]
  440. def get_single_pdf(path, page_no):
  441. log("into get_single_pdf")
  442. try:
  443. # print("path, ", path)
  444. pdf_origin = PdfFileReader(path, strict=False)
  445. pdf_new = PdfFileWriter()
  446. pdf_new.addPage(pdf_origin.getPage(page_no))
  447. path_new = path.split(".")[0] + "_split.pdf"
  448. with open(path_new, "wb") as ff:
  449. pdf_new.write(ff)
  450. return path_new
  451. except PyPDF2.utils.PdfReadError as e:
  452. raise e
  453. except Exception as e:
  454. log("get_single_pdf error! page " + str(page_no))
  455. traceback.print_exc()
  456. raise e
  457. def page_table_connect(has_table_dict):
  458. log("into page_table_connect")
  459. if not has_table_dict:
  460. return [], []
  461. try:
  462. # 判断是否有页码的表格相连
  463. table_connect_list = []
  464. temp_list = []
  465. # 离图片顶部或底部距离,页面高度的1/7
  466. threshold = 7
  467. page_no_list = list(has_table_dict.keys())
  468. page_no_list.sort(key=lambda x: x)
  469. for i in range(1, len(page_no_list)):
  470. page_info = has_table_dict.get(page_no_list[i])
  471. last_page_info = has_table_dict.get(page_no_list[i - 1])
  472. # 页码需相连
  473. if page_no_list[i] - page_no_list[i - 1] == 1:
  474. # 上一页最后一个区域的列数和下一页第一个区域列数都为0,且相等
  475. if not last_page_info[1][-1] and not page_info[1][0] and \
  476. last_page_info[1][-1] == page_info[1][0]:
  477. # 上一页的轮廓点要离底部一定距离内,下一页的轮廓点要离顶部一定距离内
  478. if last_page_info[4][0] - last_page_info[2][-1][1][1] \
  479. <= int(last_page_info[4][0] / threshold) \
  480. and page_info[2][0][0][1] - 0 \
  481. <= int(page_info[4][0] / threshold):
  482. temp_list.append(page_no_list[i - 1])
  483. temp_list.append(page_no_list[i])
  484. continue
  485. # 条件不符合的,存储之前保存的连接页码
  486. if len(temp_list) > 1:
  487. temp_list = list(set(temp_list))
  488. temp_list.sort(key=lambda x: x)
  489. table_connect_list.append(temp_list)
  490. temp_list = []
  491. if len(temp_list) > 1:
  492. temp_list = list(set(temp_list))
  493. temp_list.sort(key=lambda x: x)
  494. table_connect_list.append(temp_list)
  495. temp_list = []
  496. # 连接两页内容
  497. connect_text_list = []
  498. for area in table_connect_list:
  499. first_page_no = area[0]
  500. area_page_text = str(has_table_dict.get(first_page_no)[0])
  501. for i in range(1, len(area)):
  502. current_page_no = area[i]
  503. current_page_text = str(has_table_dict.get(current_page_no)[0])
  504. # 连接两个table
  505. table_prefix = re.finditer('<table border="1">', current_page_text)
  506. index_list = []
  507. for t in table_prefix:
  508. index_list.append(t.span())
  509. delete_index = index_list[0]
  510. current_page_text = current_page_text[:delete_index[0]] \
  511. + current_page_text[delete_index[1]:]
  512. table_suffix = re.finditer('</table>', area_page_text)
  513. index_list = []
  514. for t in table_suffix:
  515. index_list.append(t.span())
  516. delete_index = index_list[-1]
  517. area_page_text = area_page_text[:delete_index[0]] \
  518. + area_page_text[delete_index[1]:]
  519. area_page_text = area_page_text + current_page_text
  520. connect_text_list.append([area_page_text, area])
  521. return table_connect_list, connect_text_list
  522. except Exception as e:
  523. # print("page_table_connect", e)
  524. log("page_table_connect error!")
  525. traceback.print_exc()
  526. return [-1], [-1]
  527. @timeout(30, timeout_exception=TimeoutError)
  528. def read_pdf(path, package_name, packages):
  529. log(package_name)
  530. laparams = LAParams(line_overlap=0.01,
  531. char_margin=0.3,
  532. line_margin=0.01,
  533. word_margin=0.01,
  534. boxes_flow=0.1, )
  535. if package_name == packages[0]:
  536. fp = open(path, 'rb')
  537. parser = PDFParser(fp)
  538. doc_pdfminer = PDFDocument(parser)
  539. rsrcmgr = PDFResourceManager()
  540. device = PDFPageAggregator(rsrcmgr, laparams=laparams)
  541. interpreter = PDFPageInterpreter(rsrcmgr, device)
  542. return doc_pdfminer, device, interpreter
  543. elif package_name == packages[1]:
  544. doc_pymupdf = fitz.open(path)
  545. return doc_pymupdf
  546. elif package_name == packages[2]:
  547. doc_pypdf2 = PdfFileReader(path, strict=False)
  548. doc_pypdf2_new = PdfFileWriter()
  549. return doc_pypdf2, doc_pypdf2_new
  550. elif package_name == packages[3]:
  551. fp = open(path, 'rb')
  552. lt = LineTable()
  553. doc_top = 0
  554. doc_pdfplumber = read_pdfplumber(fp, laparams)
  555. return lt, doc_top, doc_pdfplumber
  556. @timeout(25, timeout_exception=TimeoutError)
  557. def read_pdfminer(path, laparams):
  558. fp = open(path, 'rb')
  559. parser = PDFParser(fp)
  560. doc_pdfminer = PDFDocument(parser)
  561. rsrcmgr = PDFResourceManager()
  562. device = PDFPageAggregator(rsrcmgr, laparams=laparams)
  563. interpreter = PDFPageInterpreter(rsrcmgr, device)
  564. return doc_pdfminer, device, interpreter
  565. @timeout(15, timeout_exception=TimeoutError)
  566. def read_pymupdf(path):
  567. return fitz.open(path)
  568. @timeout(15, timeout_exception=TimeoutError)
  569. def read_pypdf2(path):
  570. doc_pypdf2 = PdfFileReader(path, strict=False)
  571. doc_pypdf2_new = PdfFileWriter()
  572. return doc_pypdf2, doc_pypdf2_new
  573. @timeout(25, timeout_exception=TimeoutError, use_signals=False)
  574. def read_pdfplumber(path, laparams):
  575. fp = open(path, 'rb')
  576. lt = LineTable()
  577. doc_top = 0
  578. doc_pdfplumber = PDF(fp, laparams=laparams.__dict__)
  579. return lt, doc_top, doc_pdfplumber
  580. class PDFConvert:
  581. def __init__(self, path, unique_type_dir, need_page_no):
  582. self._doc = _Document(path)
  583. self.path = path
  584. self.unique_type_dir = unique_type_dir
  585. if not os.path.exists(self.unique_type_dir):
  586. os.mkdir(self.unique_type_dir)
  587. # 指定提取的页码范围
  588. self.need_page_no = need_page_no
  589. self.start_page_no = None
  590. self.end_page_no = None
  591. # 默认使用limit_page_cnt控制,前10页后10页
  592. if self.need_page_no is None:
  593. self.limit_page_cnt = 20
  594. else:
  595. # 使用start_page_no,end_page_no范围控制,例如2,5
  596. ss = self.need_page_no.split(',')
  597. if len(ss) != 2:
  598. self._doc.error_code = [-14]
  599. else:
  600. self.start_page_no = int(ss[0])
  601. self.end_page_no = int(ss[-1])
  602. if self.end_page_no == -1:
  603. self.end_page_no = 1000000
  604. self.start_page_no -= 1
  605. self.end_page_no -= 1
  606. if self.end_page_no <= self.start_page_no or self.start_page_no < 0 or self.end_page_no < -1:
  607. self._doc.error_code = [-14]
  608. self.packages = ["pdfminer", "PyMuPDF", "PyPDF2", "pdfplumber"]
  609. self.has_init_pdf = [0] * len(self.packages)
  610. # 记录图片对象的md5,用于去除大量重复图片
  611. self.md5_image_obj_list = []
  612. @memory_decorator
  613. def init_package(self, package_name):
  614. # 各个包初始化
  615. try:
  616. laparams = LAParams(line_overlap=0.01,
  617. char_margin=0.3,
  618. line_margin=0.01,
  619. word_margin=0.01,
  620. boxes_flow=0.1, )
  621. if package_name == self.packages[0]:
  622. # fp = open(self.path, 'rb')
  623. # parser = PDFParser(fp)
  624. # self.doc_pdfminer = PDFDocument(parser)
  625. # rsrcmgr = PDFResourceManager()
  626. # self.laparams = LAParams(line_overlap=0.01,
  627. # char_margin=0.3,
  628. # line_margin=0.01,
  629. # word_margin=0.01,
  630. # boxes_flow=0.1,)
  631. # self.device = PDFPageAggregator(rsrcmgr, laparams=self.laparams)
  632. # self.interpreter = PDFPageInterpreter(rsrcmgr, self.device)
  633. self.doc_pdfminer, self.device, self.interpreter = read_pdfminer(self.path, laparams)
  634. self.has_init_pdf[0] = 1
  635. elif package_name == self.packages[1]:
  636. self.doc_pymupdf = read_pymupdf(self.path)
  637. self.has_init_pdf[1] = 1
  638. elif package_name == self.packages[2]:
  639. # self.doc_pypdf2 = PdfFileReader(self.path, strict=False)
  640. # self.doc_pypdf2_new = PdfFileWriter()
  641. self.doc_pypdf2, self.doc_pypdf2_new = read_pypdf2(self.path)
  642. self.has_init_pdf[2] = 1
  643. elif package_name == self.packages[3]:
  644. # self.fp = open(self.path, 'rb')
  645. # self.lt = LineTable()
  646. # self.doc_top = 0
  647. # self.doc_pdfplumber = PDF(self.fp, laparams=self.laparams.__dict__)
  648. self.lt, self.doc_top, self.doc_pdfplumber = read_pdfplumber(self.path, laparams)
  649. self.has_init_pdf[3] = 0
  650. else:
  651. log("Only Support Packages " + str(self.packages))
  652. raise Exception
  653. except Exception as e:
  654. log(package_name + " cannot open pdf!")
  655. traceback.print_exc()
  656. self._doc.error_code = [-3]
  657. def convert(self, limit_page_cnt=20):
  658. if self.has_init_pdf[0] == 0:
  659. self.init_package("pdfminer")
  660. if self._doc.error_code is not None:
  661. self._doc.error_code = None
  662. # pdfminer读不了直接转成图片识别
  663. self.get_all_page_image()
  664. return
  665. # 判断是否能读pdf
  666. try:
  667. pages = PDFPage.create_pages(self.doc_pdfminer)
  668. for page in pages:
  669. break
  670. pages = list(pages)
  671. # except pdfminer.psparser.PSEOF as e:
  672. except:
  673. # pdfminer 读不了空白页的对象,直接使用pymupdf转换出的图片进行ocr识别
  674. log("pdf2text pdfminer read failed! read by pymupdf!")
  675. traceback.print_exc()
  676. try:
  677. self.get_all_page_image()
  678. return
  679. except:
  680. traceback.print_exc()
  681. log("pdf2text use pymupdf read failed!")
  682. self._doc.error_code = [-3]
  683. return
  684. # 每一页进行处理
  685. pages = PDFPage.create_pages(self.doc_pdfminer)
  686. pages = list(pages)
  687. page_count = len(pages)
  688. page_no = 0
  689. for page in pages:
  690. # 指定pdf页码
  691. if self.start_page_no is not None and self.end_page_no is not None:
  692. if page_count < self.end_page_no:
  693. self.end_page_no = page_count
  694. if page_no < self.start_page_no or page_no >= self.end_page_no:
  695. page_no += 1
  696. continue
  697. # 限制pdf页数,只取前后各10页
  698. else:
  699. if page_count > limit_page_cnt and int(limit_page_cnt/2) <= page_no < page_count - int(limit_page_cnt/2):
  700. page_no += 1
  701. continue
  702. # 解析单页
  703. self._page = _Page(page, page_no)
  704. self.convert_page(page, page_no)
  705. if self._doc.error_code is None and self._page.error_code is not None:
  706. if self._page.error_code[0] in [-4, -3, 0]:
  707. page_no += 1
  708. continue
  709. else:
  710. self._doc.error_code = self._page.error_code
  711. break
  712. self._doc.add_child(self._page)
  713. page_no += 1
  714. self.delete_same_image()
  715. def delete_same_image(self, show=0):
  716. # 剔除大量重复图片
  717. md5_dict = {}
  718. for _md5, image_obj in self.md5_image_obj_list:
  719. if _md5 in md5_dict.keys():
  720. md5_dict[_md5] += [image_obj]
  721. else:
  722. md5_dict[_md5] = [image_obj]
  723. cnt_threshold = 10
  724. delete_obj_list = []
  725. for _md5 in md5_dict.keys():
  726. img_list = md5_dict.get(_md5)
  727. print('len(md5_dict.get(_md5))', _md5, len(img_list))
  728. if len(img_list) >= cnt_threshold:
  729. if show:
  730. img_np = bytes2np(img_list[0].content)
  731. cv2.namedWindow('delete same img_np', cv2.WINDOW_NORMAL)
  732. cv2.imshow('delete same img_np', img_np)
  733. cv2.waitKey(0)
  734. delete_obj_list += img_list
  735. for page in self._doc.children:
  736. for obj in delete_obj_list:
  737. if obj in page.children:
  738. page.children.remove(obj)
  739. if show:
  740. for page in self._doc.children:
  741. for obj in page.children:
  742. if isinstance(obj, _Image):
  743. img_np = bytes2np(obj.content)
  744. cv2.imshow('page img_np', img_np)
  745. cv2.waitKey(0)
  746. def clean_text(self, _text):
  747. return re.sub("\s", "", _text)
  748. def get_text_lines(self, page, page_no):
  749. lt_line_list = []
  750. page_plumber = pdfPage(self.doc_pdfplumber, page, page_number=page_no, initial_doctop=self.doc_top)
  751. self.doc_top += page_plumber.height
  752. table_finder = TableFinder(page_plumber)
  753. all_width_zero = True
  754. for _edge in table_finder.get_edges():
  755. if _edge.get('linewidth') and _edge.get('linewidth') > 0:
  756. all_width_zero = False
  757. break
  758. for _edge in table_finder.get_edges():
  759. # print(_edge)
  760. if _edge.get('linewidth', 0.1) > 0 or all_width_zero:
  761. lt_line_list.append(LTLine(1, (float(_edge["x0"]), float(_edge["y0"])),
  762. (float(_edge["x1"]), float(_edge["y1"]))))
  763. log("pdf page %s has %s lines" % (str(page_no), str(len(lt_line_list))))
  764. return lt_line_list
  765. def get_page_lines(self, layout, page_no, show=0):
  766. def _plot(_line_list, mode=1):
  767. for _line in _line_list:
  768. if mode == 1:
  769. x0, y0, x1, y1 = _line.__dict__.get("bbox")
  770. elif mode == 2:
  771. x0, y0, x1, y1 = _line
  772. plt.plot([x0, x1], [y0, y1])
  773. plt.show()
  774. return
  775. def is_cross(A, B, C, D):
  776. if A[0] == B[0] == C[0] == D[0]:
  777. if A[1] <= C[1] <= B[1] or A[1] <= D[1] <= B[1] \
  778. or C[1] <= A[1] <= D[1] or C[1] <= B[1] <= D[1]:
  779. return True
  780. if A[1] == B[1] == C[1] == D[1]:
  781. if A[0] <= C[0] <= B[0] or A[0] <= D[0] <= B[0] \
  782. or C[0] <= A[0] <= D[0] or C[0] <= B[0] <= D[0]:
  783. return True
  784. line1 = LineString([A, B])
  785. line2 = LineString([C, D])
  786. int_pt = line1.intersection(line2)
  787. try:
  788. point_of_intersection = int_pt.x, int_pt.y
  789. return True
  790. except:
  791. return False
  792. def calculate_k(bbox):
  793. x = [bbox[0], bbox[2]]
  794. y = [bbox[1], bbox[3]]
  795. slope, intercept, r_value, p_value, std_err = linregress(x, y)
  796. # print('k', slope)
  797. if math.isnan(slope):
  798. slope = 0
  799. return slope
  800. def line_iou(line1, line2, axis=0):
  801. inter = min(line1[1][axis], line2[1][axis]) - max(line1[0][axis], line2[0][axis])
  802. # union = max(line1[1][axis], line2[1][axis]) - min(line1[0][axis], line2[0][axis])
  803. union = min(abs(line1[0][axis] - line1[1][axis]), abs(line2[0][axis] - line2[1][axis]))
  804. if union in [0, 0.]:
  805. iou = 0.
  806. else:
  807. iou = inter / union
  808. return iou
  809. def get_cross_line(_line_list, threshold=1, cross_times=0):
  810. # 根据是否有交点判断表格线
  811. _cross_line_list = []
  812. for line1 in _line_list:
  813. if line1 in _cross_line_list:
  814. continue
  815. if abs(line1[2] - line1[0]) > abs(line1[3] - line1[1]):
  816. p1 = [max(0, line1[0] - threshold), line1[1]]
  817. p2 = [min(line1[2] + threshold, page_w), line1[3]]
  818. else:
  819. p1 = [line1[0], max(0, line1[1] - threshold)]
  820. p2 = [line1[2], min(line1[3] + threshold, page_h)]
  821. line1 = [p1[0], p1[1], p2[0], p2[1]]
  822. _times = 0
  823. for line2 in _line_list:
  824. if abs(line2[2] - line2[0]) > abs(line2[3] - line2[1]):
  825. p3 = [max(0, line2[0] - threshold), line2[1]]
  826. p4 = [min(line2[2] + threshold, page_w), line2[3]]
  827. else:
  828. p3 = [line2[0], max(0, line2[1] - threshold)]
  829. p4 = [line2[2], min(line2[3] + threshold, page_h)]
  830. line2 = [p3[0], p3[1], p4[0], p4[1]]
  831. if line1 == line2:
  832. continue
  833. if is_cross(p1, p2, p3, p4):
  834. _times += 1
  835. if _times >= cross_times:
  836. _cross_line_list += [line1]
  837. break
  838. return _cross_line_list
  839. def repair_bias_line(_line_list):
  840. temp_list = []
  841. for line in _line_list:
  842. x0, y0, x1, y1 = line
  843. _y = min(y0, y1)
  844. _x = min(x0, x1)
  845. if abs(x0 - x1) > abs(y0 - y1):
  846. temp_list.append([x0, _y, x1, _y])
  847. else:
  848. temp_list.append([_x, y0, _x, y1])
  849. _line_list = temp_list
  850. return _line_list
  851. def repair_col_line(_straight_list, _bias_list, threshold=2, min_width=7):
  852. if not _straight_list or not _bias_list:
  853. print('add_col_bias_line empty', len(_straight_list), len(_bias_list))
  854. return []
  855. # 分列
  856. _straight_list.sort(key=lambda x: (x[0], x[1]))
  857. cols = []
  858. col = []
  859. current_w = _straight_list[0][0]
  860. for line in _straight_list:
  861. if abs(line[0] - line[2]) > abs(line[1] - line[3]):
  862. continue
  863. if min(line[0], line[2]) - threshold <= current_w <= max(line[0], line[2]) + threshold:
  864. col.append(line)
  865. else:
  866. if col:
  867. cols.append(col)
  868. col = [line]
  869. current_w = line[0]
  870. if col:
  871. cols.append(col)
  872. # 补充col
  873. new_list = []
  874. for line in bias_line_list:
  875. if abs(line[0] - line[2]) > abs(line[1] - line[3]):
  876. continue
  877. for col in cols:
  878. w = col[0][0]
  879. if w - threshold <= line[0] <= w + threshold or w - threshold <= line[2] <= w + threshold:
  880. new_list.append([w, line[1] - 3, w, line[3] + 3])
  881. new_list += _straight_list
  882. # 去重
  883. new_list = [str(x) for x in new_list]
  884. new_list = list(set(new_list))
  885. new_list = [eval(x) for x in new_list]
  886. # 分列
  887. new_list.sort(key=lambda x: (x[0], x[1]))
  888. cols = []
  889. col = []
  890. current_w = new_list[0][0]
  891. for line in new_list:
  892. if abs(line[0] - line[2]) > abs(line[1] - line[3]):
  893. continue
  894. if min(line[0], line[2]) - threshold <= current_w <= max(line[0], line[2]) + threshold:
  895. col.append(line)
  896. else:
  897. if col:
  898. cols.append(col)
  899. col = [line]
  900. current_w = line[0]
  901. if col:
  902. cols.append(col)
  903. # 删除col
  904. for col1 in cols:
  905. for col2 in cols:
  906. if col1 == col2 or abs(col1[0][0] - col2[0][0]) > min_width:
  907. continue
  908. col1_len, col2_len = 0, 0
  909. for c in col1:
  910. col1_len += abs(c[1] - c[3])
  911. for c in col2:
  912. col2_len += abs(c[1] - c[3])
  913. if col1_len > col2_len * 3:
  914. for c in col2:
  915. if c in new_list:
  916. new_list.remove(c)
  917. if col2_len > col1_len * 3:
  918. for c in col1:
  919. if c in new_list:
  920. new_list.remove(c)
  921. return new_list
  922. def merge_line(_line_list, threshold=2):
  923. new_line_list = []
  924. # 分列
  925. _line_list.sort(key=lambda x: (x[0], x[1]))
  926. cols = []
  927. col = [_line_list[0]]
  928. current_w = _line_list[0][0]
  929. for line in _line_list:
  930. if abs(line[0] - line[2]) > abs(line[1] - line[3]):
  931. continue
  932. if min(line[0], line[2]) - threshold <= current_w <= max(line[0], line[2]) + threshold \
  933. and is_cross(line[0:2], line[2:4], col[-1][0:2], col[-1][2:4]):
  934. col.append(line)
  935. else:
  936. if col:
  937. cols.append(col)
  938. col = [line]
  939. current_w = line[0]
  940. if col:
  941. cols.append(col)
  942. for col in cols:
  943. temp_c = col[0]
  944. col_w = col[0][0]
  945. for i in range(len(col) - 1):
  946. c = col[i]
  947. next_c = col[i + 1]
  948. if is_cross(c[0:2], c[2:4], next_c[0:2], next_c[2:4]):
  949. temp_c = [col_w, min(temp_c[1], c[1], c[3], next_c[1], next_c[3]), col_w,
  950. max(temp_c[3], c[1], c[3], next_c[1], next_c[3])]
  951. else:
  952. new_line_list.append(temp_c)
  953. temp_c = next_c
  954. if not new_line_list or (new_line_list and new_line_list[-1] != temp_c):
  955. new_line_list.append(temp_c)
  956. # 分行
  957. _line_list.sort(key=lambda x: (x[1], x[0]))
  958. rows = []
  959. row = []
  960. current_h = _line_list[0][1]
  961. for line in _line_list:
  962. if abs(line[0] - line[2]) < abs(line[1] - line[3]):
  963. continue
  964. if min(line[1], line[3]) - threshold <= current_h <= max(line[1], line[3]) + threshold:
  965. row.append(line)
  966. else:
  967. if row:
  968. rows.append(row)
  969. row = [line]
  970. current_h = line[1]
  971. if row:
  972. rows.append(row)
  973. for row in rows:
  974. temp_r = row[0]
  975. row_h = row[0][1]
  976. for i in range(len(row) - 1):
  977. r = row[i]
  978. next_r = row[i + 1]
  979. # if is_cross(r[0:2], r[2:4], next_r[0:2], next_r[2:4]):
  980. if line_iou([r[0:2], r[2:4]], [next_r[0:2], next_r[2:4]], axis=0):
  981. temp_r = [min(temp_r[0], r[0], r[2], next_r[0], next_r[2]), row_h,
  982. max(temp_r[2], r[0], r[2], next_r[0], next_r[2]), row_h]
  983. else:
  984. new_line_list.append(temp_r)
  985. temp_r = next_r
  986. if not new_line_list or (new_line_list and new_line_list[-1] != temp_r):
  987. new_line_list.append(temp_r)
  988. return new_line_list
  989. def remove_outline_no_cross(_line_list):
  990. row_list = []
  991. col_list = []
  992. for line in _line_list:
  993. # 存所有行
  994. if abs(line[0] - line[2]) > abs(line[1] - line[3]):
  995. row_list.append(line)
  996. # 存所有列
  997. if abs(line[0] - line[2]) < abs(line[1] - line[3]):
  998. col_list.append(line)
  999. if not col_list:
  1000. return _line_list
  1001. # 左右两条边框
  1002. col_list.sort(key=lambda x: (x[0], x[1]))
  1003. left_col = col_list[0]
  1004. right_col = col_list[-1]
  1005. # 判断有交点但中间区域无交点
  1006. compare_list = []
  1007. for col in [left_col, right_col]:
  1008. add_h = abs(col[1]-col[3]) / 8
  1009. center_area = [col[1]+add_h, col[3]-add_h]
  1010. cross_cnt = 0
  1011. center_cross_cnt = 0
  1012. center_row_cnt = 0
  1013. for row in row_list:
  1014. if is_cross(row[0:2], row[2:4], col[0:2], col[2:4]):
  1015. if center_area[0] <= row[1] <= center_area[1]:
  1016. center_cross_cnt += 1
  1017. else:
  1018. cross_cnt += 1
  1019. else:
  1020. if center_area[0] <= row[1] <= center_area[1]:
  1021. center_row_cnt += 1
  1022. compare_list.append([cross_cnt, center_cross_cnt, center_row_cnt])
  1023. _flag = True
  1024. for c in compare_list:
  1025. if c[0] >= 2 and c[1] == 0 and c[2] >= 2:
  1026. continue
  1027. _flag = False
  1028. print('compare_list', compare_list)
  1029. if _flag and compare_list[0][1] == compare_list[1][1] \
  1030. and compare_list[0][2] == compare_list[1][2]:
  1031. for col in [left_col, right_col]:
  1032. if col in _line_list:
  1033. _line_list.remove(col)
  1034. return _line_list
  1035. def cross_line_process(_cross_line_list, _bias_line_list):
  1036. # 斜线校正
  1037. if _cross_line_list:
  1038. _cross_line_list = repair_bias_line(_cross_line_list)
  1039. # 修复竖线
  1040. if _bias_line_list:
  1041. _cross_line_list = repair_col_line(_cross_line_list, _bias_line_list)
  1042. # 根据是否有交点判断表格线
  1043. _cross_line_list = get_cross_line(_cross_line_list, threshold=1, cross_times=1)
  1044. # 合并线条
  1045. if not _cross_line_list:
  1046. return []
  1047. _cross_line_list = merge_line(_cross_line_list)
  1048. # 删除最外层嵌套边框
  1049. _cross_line_list = remove_outline_no_cross(_cross_line_list)
  1050. # 复用otr的部分后处理,补线
  1051. from otr.table_line_new import table_line_pdf
  1052. _cross_line_list = table_line_pdf(_cross_line_list, page_w, page_h)
  1053. return _cross_line_list
  1054. log('into get_page_lines')
  1055. page_h = layout.height
  1056. page_w = layout.width
  1057. element_list = []
  1058. line_list = []
  1059. bias_line_list = []
  1060. text_bbox_list = []
  1061. for element in layout:
  1062. if isinstance(element, LTTextContainer):
  1063. text_bbox_list.append(element.bbox)
  1064. # 只取这三种类型的bbox
  1065. if isinstance(element, (LTRect, LTCurve, LTLine)):
  1066. element_list.append(element)
  1067. if element.height > 0.5 and element.width > 0.5:
  1068. # print('element.height, element.width', element.height, element.width)
  1069. k = calculate_k(element.bbox)
  1070. if 1.73 / 3 < abs(k) < 1.73:
  1071. continue
  1072. else:
  1073. bias_line_list.append(element.bbox)
  1074. continue
  1075. line_list.append(element.bbox)
  1076. if show:
  1077. print('get_page_lines line_list', line_list)
  1078. print('get_page_lines bias_line_list', bias_line_list)
  1079. _plot(line_list+bias_line_list, mode=2)
  1080. if not line_list and not bias_line_list:
  1081. return []
  1082. # 是否使用斜线来生成表格
  1083. line_list_copy = copy.deepcopy(line_list)
  1084. if len(line_list) < 6 and len(bias_line_list) > len(line_list) * 2:
  1085. if show:
  1086. print('use bias line')
  1087. # bias_line_list += add_col_bias_line(line_list, bias_line_list)
  1088. line_list = bias_line_list
  1089. # 去重
  1090. line_list = [str(x) for x in line_list]
  1091. line_list = list(set(line_list))
  1092. line_list = [eval(x) for x in line_list]
  1093. if show:
  1094. _plot(line_list, mode=2)
  1095. # 根据是否有交点判断表格线
  1096. cross_line_list = get_cross_line(line_list_copy+bias_line_list, threshold=2, cross_times=1)
  1097. if show:
  1098. print('get_page_lines cross_line_list', cross_line_list)
  1099. if not cross_line_list:
  1100. # 将线全部合并再获取一次
  1101. cross_line_list = get_cross_line(line_list_copy+bias_line_list, threshold=2, cross_times=1)
  1102. if not cross_line_list:
  1103. return []
  1104. cross_line_list = cross_line_process(cross_line_list, bias_line_list)
  1105. if not cross_line_list:
  1106. cross_line_list = get_cross_line(line_list_copy+bias_line_list, threshold=2, cross_times=1)
  1107. cross_line_list = cross_line_process(cross_line_list, bias_line_list)
  1108. if show:
  1109. print('get_page_lines cross_line_list2', cross_line_list)
  1110. # show
  1111. if show:
  1112. print('len(cross_line_list)', len(cross_line_list))
  1113. # _plot(line_list, mode=2)
  1114. _plot(cross_line_list, mode=2)
  1115. lt_line_list = []
  1116. for line in cross_line_list:
  1117. lt_line_list.append(LTLine(1, (float(line[0]), float(line[1])),
  1118. (float(line[2]), float(line[3]))))
  1119. log("pdf page %s has %s lines" % (str(page_no), str(len(lt_line_list))))
  1120. return lt_line_list
  1121. def recognize_text(self, layout, page_no, lt_text_list, lt_line_list):
  1122. list_tables, filter_objs, _, connect_textbox_list = self.lt.recognize_table(lt_text_list, lt_line_list, from_pdf=True)
  1123. self._page.in_table_objs = filter_objs
  1124. # print("=======text_len:%d:filter_len:%d"%(len(lt_text_list),len(filter_objs)))
  1125. for table in list_tables:
  1126. _table = _Table(table["table"], table["bbox"])
  1127. # self._page.children.append(_table)
  1128. self._page.add_child(_table)
  1129. list_sentences = ParseUtils.recognize_sentences(lt_text_list, filter_objs,
  1130. layout.bbox, page_no)
  1131. for sentence in list_sentences:
  1132. _sen = _Sentence(sentence.text, sentence.bbox)
  1133. self._page.add_child(_sen)
  1134. # pdf对象需反向排序
  1135. self._page.is_reverse = True
  1136. return list_tables
  1137. def is_text_legal(self, lt_text_list, page_no):
  1138. # 无法识别pdf字符编码,整页用ocr
  1139. text_temp = ""
  1140. for _t in lt_text_list:
  1141. text_temp += _t.get_text()
  1142. if re.search('[(]cid:[0-9]+[)]', text_temp):
  1143. log("text has cid! try pymupdf...")
  1144. page_image = self.get_page_image(page_no)
  1145. if judge_error_code(page_image):
  1146. self._page.error_code = page_image
  1147. else:
  1148. _image = _Image(page_image[1], page_image[0])
  1149. self._page.add_child(_image)
  1150. return False
  1151. match1 = re.findall(get_garble_code(), text_temp)
  1152. # match2 = re.search('[\u4e00-\u9fa5]', text_temp)
  1153. if len(match1) > 3 and len(text_temp) > 10:
  1154. log("pdf garbled code! try pymupdf... " + text_temp[:20])
  1155. page_image = self.get_page_image(page_no)
  1156. if judge_error_code(page_image):
  1157. self._page.error_code = page_image
  1158. else:
  1159. _image = _Image(page_image[1], page_image[0])
  1160. self._page.add_child(_image)
  1161. return False
  1162. return True
  1163. def judge_b_table(self, lt_text_list, table_list):
  1164. table_h_list = []
  1165. for table in table_list:
  1166. table_h_list.append([table.get('bbox')[1], table.get('bbox')[3]])
  1167. # 先分行
  1168. lt_text_list.sort(key=lambda x: (x.bbox[1], x.bbox[0]))
  1169. lt_text_row_list = []
  1170. current_h = lt_text_list[0].bbox[1]
  1171. row = []
  1172. threshold = 2
  1173. for lt_text in lt_text_list:
  1174. bbox = lt_text.bbox
  1175. if current_h - threshold <= bbox[1] <= current_h + threshold:
  1176. row.append(lt_text)
  1177. else:
  1178. if row:
  1179. lt_text_row_list.append(row)
  1180. row = [lt_text]
  1181. current_h = lt_text.bbox[1]
  1182. if row:
  1183. lt_text_row_list.append(row)
  1184. # print('lt_text_row_list')
  1185. # for r in lt_text_row_list:
  1186. # print('r', [x.get_text() for x in r])
  1187. # 判断文本中间是否是空格,或一行文本中间有多个
  1188. is_b_table_flag = False
  1189. is_b_table_cnt = 3
  1190. tolerate_cnt = 2
  1191. t_cnt = 0
  1192. row_cnt = 0
  1193. b_table_row_list = []
  1194. for row in lt_text_row_list:
  1195. # 水印行跳过
  1196. if len(row) == 1 and len(row[0].get_text()[:-1]) == 1:
  1197. continue
  1198. # 目录行跳过
  1199. continue_flag = False
  1200. for r in row:
  1201. if re.search('[.·]{7,}', r.get_text()):
  1202. continue_flag = True
  1203. break
  1204. if continue_flag:
  1205. continue
  1206. if len(row) == 1:
  1207. text = row[0].get_text()
  1208. bbox = row[0].bbox
  1209. match = re.search('[ ]{3,}', text)
  1210. if match and re.search('[\u4e00-\u9fff]{2,}', text[:match.span()[0]]) \
  1211. and re.search('[\u4e00-\u9fff]{2,}', text[match.span()[1]:]):
  1212. row_cnt += 1
  1213. t_cnt = 0
  1214. b_table_row_list += row
  1215. else:
  1216. # 容忍
  1217. if t_cnt < tolerate_cnt:
  1218. t_cnt += 1
  1219. continue
  1220. row_cnt = 0
  1221. b_table_row_list = []
  1222. else:
  1223. row_cnt += 1
  1224. t_cnt = 0
  1225. b_table_row_list += row
  1226. if row_cnt >= is_b_table_cnt:
  1227. # 判断在不在有边框表格的范围
  1228. in_flag = False
  1229. for table_h in table_h_list:
  1230. for b in b_table_row_list:
  1231. # print('b.bbox', b.bbox)
  1232. # print(table_h)
  1233. if table_h[1] <= b.bbox[1] <= table_h[0] or table_h[1] <= b.bbox[3] <= table_h[0]:
  1234. in_flag = True
  1235. break
  1236. if in_flag:
  1237. break
  1238. if in_flag:
  1239. is_b_table_flag = False
  1240. t_cnt = 0
  1241. row_cnt = 0
  1242. else:
  1243. print('True b_table_row_list', b_table_row_list)
  1244. print('table_h_list', table_h_list)
  1245. is_b_table_flag = True
  1246. break
  1247. log('pdf is_b_table_flag ' + str(is_b_table_flag))
  1248. return is_b_table_flag
  1249. def convert_page(self, page, page_no):
  1250. layout = self.get_layout(page, page_no)
  1251. if self._doc.error_code is not None:
  1252. return
  1253. if judge_error_code(layout):
  1254. self._page.error_code = layout
  1255. return
  1256. # 判断该页的对象类型,并存储
  1257. lt_text_list = []
  1258. lt_image_list = []
  1259. for x in layout:
  1260. if isinstance(x, (LTTextBoxHorizontal, LTTextBoxVertical)):
  1261. lt_text_list.append(x)
  1262. if isinstance(x, LTFigure):
  1263. for y in x:
  1264. if isinstance(y, LTImage):
  1265. # 小的图忽略
  1266. if y.width <= 300 and y.height <= 300:
  1267. continue
  1268. # 图的width超过layout width,很大可能是水印
  1269. if y.width > layout.width + 20:
  1270. continue
  1271. lt_image_list.append(y)
  1272. lt_text_list = self.delete_water_mark(lt_text_list, layout.bbox, 15)
  1273. log("convert_pdf page " + str(page_no))
  1274. log("len(lt_image_list), len(lt_text_list) " + str(len(lt_image_list)) + " " + str(len(lt_text_list)))
  1275. log('layout.width, layout.height ' + str(layout.width) + str(layout.height))
  1276. # 若只有文本且图片数为0,直接提取文字及表格
  1277. # if only_image == 0 and image_count == 0:
  1278. # if len(lt_image_list) == 0 and len(lt_text_list) > 0:
  1279. # # PDFPlumber
  1280. # if self.has_init_pdf[3] == 0:
  1281. # self.init_package("pdfplumber")
  1282. # if self._doc.error_code is not None:
  1283. # self._doc.error_code = None
  1284. # log("init pdfplumber failed! try pymupdf...")
  1285. # # 调用pdfplumber获取pdf图片报错,则使用pypdf2将pdf转html
  1286. # page_image = self.get_page_image(page_no)
  1287. # if judge_error_code(page_image):
  1288. # self._page.error_code = page_image
  1289. # else:
  1290. # _image = _Image(page_image[1], page_image[0])
  1291. # self._page.add_child(_image)
  1292. # return
  1293. #
  1294. # if not self.is_text_legal(lt_text_list, page_no):
  1295. # return
  1296. #
  1297. # # 根据text规律,判断该页是否可能有无边框表格
  1298. # start_time = time.time()
  1299. # if self.judge_b_table(lt_text_list):
  1300. # page_image = self.get_page_image(page_no)
  1301. # if judge_error_code(page_image):
  1302. # self._page.error_code = page_image
  1303. # else:
  1304. # _image = _Image(page_image[1], page_image[0])
  1305. # _image.is_from_pdf = True
  1306. # _image.b_table_from_text = True
  1307. # _image.b_table_text_obj_list = lt_text_list
  1308. # _image.b_table_layout_size = (layout.width, layout.height)
  1309. # self._page.add_child(_image)
  1310. # log('convert_pdf judge_b_table set image cost: ' + str(time.time()-start_time))
  1311. #
  1312. # try:
  1313. # lt_line_list = self.get_page_lines(layout, page_no)
  1314. # except:
  1315. # traceback.print_exc()
  1316. # lt_line_list = []
  1317. # self._page.error_code = [-13]
  1318. # try:
  1319. # # lt_line_list = self.get_text_lines(page,page_no)
  1320. # self.recognize_text(layout, page_no, lt_text_list, lt_line_list)
  1321. # except:
  1322. # traceback.print_exc()
  1323. # self._page.error_code = [-8]
  1324. # 若该页图片数量过多,或无文本,则直接ocr整页识别
  1325. # elif image_count > 3 or only_image == 1:
  1326. if len(lt_image_list) > 4 or len(lt_text_list) == 0:
  1327. page_image = self.get_page_image(page_no)
  1328. if judge_error_code(page_image):
  1329. self._page.error_code = page_image
  1330. else:
  1331. _image = _Image(page_image[1], page_image[0])
  1332. _image.is_from_pdf = True
  1333. self._page.add_child(_image)
  1334. # 正常读取该页对象
  1335. else:
  1336. # 图表对象
  1337. for image in lt_image_list:
  1338. try:
  1339. print("pdf2text LTImage size", page_no, image.width, image.height)
  1340. image_stream = image.stream.get_data()
  1341. # 小的图忽略
  1342. if image.width <= 300 and image.height <= 300:
  1343. continue
  1344. # 查看提取的图片高宽,太大则用pdf输出图进行ocr识别
  1345. img_test = Image.open(io.BytesIO(image_stream))
  1346. if image.height >= 1000 and image.width >= 1000:
  1347. page_image = self.get_page_image(page_no)
  1348. if judge_error_code(page_image):
  1349. self._page.error_code = page_image
  1350. else:
  1351. _image = _Image(page_image[1], page_image[0])
  1352. _image.is_from_pdf = True
  1353. self._page.add_child(_image)
  1354. image_md5 = get_md5_from_bytes(page_image[1])
  1355. self.md5_image_obj_list.append([image_md5, _image])
  1356. return
  1357. # 比较小的图则直接保存用ocr识别
  1358. else:
  1359. temp_path = self.unique_type_dir + 'page' + str(page_no) \
  1360. + '_lt' + str(lt_image_list.index(image)) + '.jpg'
  1361. img_test.save(temp_path)
  1362. with open(temp_path, "rb") as ff:
  1363. image_stream = ff.read()
  1364. _image = _Image(image_stream, temp_path, image.bbox)
  1365. self._page.add_child(_image)
  1366. image_md5 = get_md5_from_bytes(image_stream)
  1367. self.md5_image_obj_list.append([image_md5, _image])
  1368. except Exception:
  1369. log("pdf2text pdfminer read image in page " + str(page_no) +
  1370. " fail! use pymupdf read image...")
  1371. traceback.print_exc()
  1372. # pdf对象需反向排序
  1373. self._page.is_reverse = True
  1374. self.init_package("pdfplumber")
  1375. if not self.is_text_legal(lt_text_list, page_no):
  1376. return
  1377. try:
  1378. lt_line_list = self.get_page_lines(layout, page_no)
  1379. except:
  1380. traceback.print_exc()
  1381. lt_line_list = []
  1382. self._page.error_code = [-13]
  1383. table_list = self.recognize_text(layout, page_no, lt_text_list, lt_line_list)
  1384. # 根据text规律,判断该页是否可能有无边框表格
  1385. if self.judge_b_table(lt_text_list, table_list):
  1386. page_image = self.get_page_image(page_no)
  1387. if judge_error_code(page_image):
  1388. self._page.error_code = page_image
  1389. else:
  1390. _image = _Image(page_image[1], page_image[0])
  1391. _image.is_from_pdf = True
  1392. _image.b_table_from_text = True
  1393. _image.b_table_text_obj_list = lt_text_list
  1394. _image.b_table_layout_size = (layout.width, layout.height)
  1395. self._page.add_child(_image)
  1396. def get_layout(self, page, page_no):
  1397. log("get_layout")
  1398. if self.has_init_pdf[0] == 0:
  1399. self.init_package("pdfminer")
  1400. if self._doc.error_code is not None:
  1401. return
  1402. # 获取该页layout
  1403. start_time = time.time()
  1404. try:
  1405. if get_platform() == "Windows":
  1406. # origin_pdf_analyze = pdf_analyze.__wrapped__
  1407. # layout = origin_pdf_analyze(self.interpreter, page, self.device)
  1408. layout = pdf_analyze(self.interpreter, page, self.device, page_no)
  1409. else:
  1410. layout = pdf_analyze(self.interpreter, page, self.device, page_no)
  1411. except TimeoutError as e:
  1412. log("pdf2text pdfminer read pdf page " + str(page_no) + " time out! " + str(time.time() - start_time))
  1413. layout = [-4]
  1414. except Exception:
  1415. traceback.print_exc()
  1416. log("pdf2text pdfminer read pdf page " + str(page_no) + " error! continue...")
  1417. layout = [-3]
  1418. return layout
  1419. def get_page_image(self, page_no):
  1420. log("")
  1421. try:
  1422. if self.has_init_pdf[1] == 0:
  1423. self.init_package("PyMuPDF")
  1424. if self._doc.error_code is not None:
  1425. return
  1426. # save_dir = self.path.split(".")[-2] + "_" + self.path.split(".")[-1]
  1427. output = self.unique_type_dir + "page" + str(page_no) + ".png"
  1428. page = self.doc_pymupdf.loadPage(page_no)
  1429. rotate = int(0)
  1430. zoom_x = 2.
  1431. zoom_y = 2.
  1432. mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
  1433. pix = page.getPixmap(matrix=mat, alpha=False)
  1434. pix.writePNG(output)
  1435. # 输出图片resize
  1436. self.resize_image(output)
  1437. with open(output, "rb") as f:
  1438. pdf_image = f.read()
  1439. return [output, pdf_image]
  1440. except ValueError as e:
  1441. traceback.print_exc()
  1442. if str(e) == "page not in document":
  1443. log("pdf2Image page not in document! continue... page " + str(page_no))
  1444. return [0]
  1445. elif "encrypted" in str(e):
  1446. log("pdf2Image document need password " + str(page_no))
  1447. return [-7]
  1448. except RuntimeError as e:
  1449. if "cannot find page" in str(e):
  1450. log("pdf2Image page {} not in document! continue... ".format(str(page_no)) + str(e))
  1451. return [0]
  1452. else:
  1453. traceback.print_exc()
  1454. return [-3]
  1455. def get_all_page_image(self):
  1456. log("")
  1457. if self.has_init_pdf[1] == 0:
  1458. self.init_package("PyMuPDF")
  1459. if self._doc.error_code is not None:
  1460. return
  1461. page_count = self.doc_pymupdf.page_count
  1462. for page_no in range(page_count):
  1463. # 限制pdf页数,只取前10页后10页
  1464. if page_count > 20:
  1465. if 10 <= page_no < page_count - 10:
  1466. continue
  1467. self._page = _Page(None, page_no)
  1468. page_image = self.get_page_image(page_no)
  1469. if judge_error_code(page_image):
  1470. self._page.error_code = page_image
  1471. else:
  1472. _image = _Image(page_image[1], page_image[0])
  1473. self._page.add_child(_image)
  1474. # 报错继续读后面页面
  1475. if self._doc.error_code is None and self._page.error_code is not None:
  1476. continue
  1477. self._doc.add_child(self._page)
  1478. def connect_table(self, html_list):
  1479. if not html_list:
  1480. return html_list
  1481. # 判断初始条件1
  1482. # 0: 前一页最后一个表格为A,后一页第一个表格为B
  1483. # 1.1: A后无文本(除了页码),且B前无文本(除了页码)
  1484. # 1.2: B前有文字(可能是页眉,小于60字),且B的第一行前几个单元格为空,且第一行不为空的单元格有文字较多的格子
  1485. # 1.3: B前有文字(可能是页眉,小于60字),且B的第一行第一个单元格为空,且有文字的格子数量占所有格子的一半
  1486. connect_flag_list = []
  1487. soup_list = []
  1488. for i, h in enumerate(html_list):
  1489. soup = BeautifulSoup(h, 'lxml')
  1490. soup_list.append(soup)
  1491. # 找最后一个表格
  1492. last_table_start, last_table_end = None, None
  1493. # print('h', h)
  1494. match = re.finditer('<table', h)
  1495. for m in match:
  1496. last_table_start = m.span()[0]
  1497. if last_table_start is not None:
  1498. match = re.finditer('</table>', h[last_table_start:])
  1499. for m in match:
  1500. last_table_end = m.span()[1] + last_table_start
  1501. # 最后一个表格后有无除了页码外的内容
  1502. connect_flag1 = False
  1503. if last_table_end is not None:
  1504. match = re.search('[^-/第页0-9,,]*', re.sub('<div>|</div>', '', h[last_table_end:]))
  1505. # print('match1', match.group())
  1506. if not match or match.group() == '':
  1507. connect_flag1 = True
  1508. # 找第一个表格
  1509. first_table_start, first_table_end = None, None
  1510. match = re.finditer('<table', h)
  1511. for m in match:
  1512. first_table_start = m.span()[0]
  1513. break
  1514. # 第一个表格后有无内容
  1515. connect_flag2 = False
  1516. if first_table_start is not None and first_table_start == 0:
  1517. connect_flag2 = True
  1518. # 有内容但是是页眉
  1519. if not connect_flag2:
  1520. tables = soup.findAll('table')
  1521. if tables:
  1522. first_table = tables[0]
  1523. rows = first_table.findAll('tr')
  1524. if rows:
  1525. first_row = rows[0]
  1526. col_text_list = [len(x.text) for x in first_row]
  1527. # 文字大于60且第一个为空
  1528. if len(h[:first_table_start]) <= 60 and col_text_list[0] == 0 and max(col_text_list) >= 30:
  1529. connect_flag2 = True
  1530. # 有文字格子数占一半一下且第一个格子为空
  1531. elif col_text_list.count(0) >= len(col_text_list) / 2 and col_text_list[0] == 0:
  1532. connect_flag2 = True
  1533. connect_flag_list.append([i, connect_flag2, connect_flag1])
  1534. print('connect_flag_list', connect_flag_list)
  1535. # 根据条件1合并需连接页码,形成组
  1536. connect_pages_list = []
  1537. temp_list = []
  1538. for i, c in enumerate(connect_flag_list):
  1539. if temp_list and c[1]:
  1540. temp_list.append(c)
  1541. elif not temp_list and c[2]:
  1542. temp_list.append(c)
  1543. else:
  1544. if temp_list:
  1545. connect_pages_list.append(temp_list)
  1546. temp_list = []
  1547. connect_pages_list.append([c])
  1548. if temp_list:
  1549. connect_pages_list.append(temp_list)
  1550. print('connect_pages_list', connect_pages_list)
  1551. # 判断后续条件:判断组内列数是否相同
  1552. connect_pages_list2 = []
  1553. for c_list in connect_pages_list:
  1554. if len(c_list) == 1:
  1555. connect_pages_list2.append(c_list)
  1556. else:
  1557. col_cnt_list = []
  1558. # 单元格可能被复制了,相同的合并当做一列
  1559. merge_col_cnt_list = []
  1560. for c in c_list:
  1561. soup = soup_list[c[0]]
  1562. table1 = soup.findAll('table')[-1]
  1563. table2 = soup.findAll('table')[0]
  1564. tr1 = table1.findAll('tr')
  1565. tr2 = table2.findAll('tr')
  1566. td1 = tr1[-1].findAll('td')
  1567. td2 = tr2[0].findAll('td')
  1568. col_cnt_list.append([len(td2), len(td1)])
  1569. # # 计算合并重复文本格子后的列数
  1570. # last_text = td1[0].text
  1571. # merge_td1 = [last_text]
  1572. # for td in td1:
  1573. # if td.text == last_text:
  1574. # continue
  1575. # else:
  1576. # merge_td1.append(td.text)
  1577. # last_text = td.text
  1578. # last_text = td2[0].text
  1579. # merge_td2 = [last_text]
  1580. # for td in td2:
  1581. # if td.text == last_text:
  1582. # continue
  1583. # else:
  1584. # merge_td2.append(td.text)
  1585. # last_text = td.text
  1586. # merge_col_cnt_list.append([len(merge_td2), len(merge_td1)])
  1587. # 判断
  1588. new_c_list = [c_list[0]]
  1589. # print('col_cnt_list', col_cnt_list)
  1590. for i in range(len(col_cnt_list) - 1):
  1591. if col_cnt_list[i][1] != col_cnt_list[i + 1][0]:
  1592. # and merge_col_cnt_list[i][1] != merge_col_cnt_list[i + 1][0]:
  1593. connect_pages_list2.append(new_c_list)
  1594. new_c_list = [c_list[i + 1]]
  1595. else:
  1596. new_c_list.append(c_list[i + 1])
  1597. if new_c_list:
  1598. connect_pages_list2.append(new_c_list)
  1599. print('connect_pages_list2', connect_pages_list2)
  1600. # 符合连接条件的拼接表格
  1601. new_html_list = []
  1602. for c_list in connect_pages_list2:
  1603. if len(c_list) == 1:
  1604. new_html_list.append(html_list[c_list[0][0]])
  1605. continue
  1606. new_html = ''
  1607. for c in c_list:
  1608. # 加#@#@#防止替换错表格
  1609. new_html += html_list[c[0]] + '#@#@#'
  1610. new_html = new_html[:-5]
  1611. # ([-/第页0-9]|<div>|</div>)*
  1612. new_html = re.sub('</table>((<div>[-/第页0-9,,]*</div>#@#@#)|(#@#@#<div>[^<]*</div>)|#@#@#)<table border="1">',
  1613. '<tr><td>#@#@#</td></tr>',
  1614. new_html)
  1615. # print('new_html', new_html)
  1616. soup = BeautifulSoup(new_html, 'lxml')
  1617. trs = soup.findAll('tr')
  1618. for i in range(len(trs)):
  1619. if trs[i].get_text() == '#@#@#':
  1620. td1 = trs[i - 1].findAll('td')
  1621. td2 = trs[i + 1].findAll('td')
  1622. if td2[0].get_text() == '':
  1623. for j in range(len(td1)):
  1624. td1[j].string = td1[j].get_text() + td2[j].get_text()
  1625. trs[i + 1].decompose()
  1626. trs[i].decompose()
  1627. new_html = str(soup)
  1628. new_html_list.append(new_html)
  1629. html_str = ''
  1630. for h in new_html_list:
  1631. html_str += h
  1632. return [html_str]
  1633. def get_html(self):
  1634. if self._doc.error_code is not None:
  1635. return self._doc.error_code
  1636. self.convert()
  1637. if self._doc.error_code is not None:
  1638. return self._doc.error_code
  1639. html = self._doc.get_html(return_list=True)
  1640. # 表格连接
  1641. try:
  1642. html = self.connect_table(html)
  1643. except:
  1644. traceback.print_exc()
  1645. return [-12]
  1646. return html
  1647. def delete_water_mark(self, lt_text_list, page_bbox, times=5):
  1648. # 删除过多重复字句,为水印
  1649. duplicate_dict = {}
  1650. for _obj in lt_text_list:
  1651. t = _obj.get_text()
  1652. if t in duplicate_dict.keys():
  1653. duplicate_dict[t][0] += 1
  1654. duplicate_dict[t][1].append(_obj)
  1655. else:
  1656. duplicate_dict[t] = [1, [_obj]]
  1657. delete_text = []
  1658. for t in duplicate_dict.keys():
  1659. if duplicate_dict[t][0] >= times:
  1660. obj_list = duplicate_dict[t][1]
  1661. obj_list.sort(key=lambda x: x.bbox[3])
  1662. obj_distance_h = abs(obj_list[-1].bbox[3] - obj_list[0].bbox[1])
  1663. obj_list.sort(key=lambda x: x.bbox[2])
  1664. obj_distance_w = abs(obj_list[-1].bbox[2] - obj_list[0].bbox[0])
  1665. if obj_distance_h >= abs(page_bbox[1] - page_bbox[3]) * 0.7 \
  1666. and obj_distance_w >= abs(page_bbox[0] - page_bbox[2]) * 0.7:
  1667. delete_text.append(t)
  1668. temp_text_list = []
  1669. for _obj in lt_text_list:
  1670. t = _obj.get_text()
  1671. if t not in delete_text:
  1672. temp_text_list.append(_obj)
  1673. return temp_text_list
  1674. def resize_image(self, img_path, max_size=2000):
  1675. _img = cv2.imread(img_path)
  1676. if _img.shape[0] <= max_size or _img.shape[1] <= max_size:
  1677. return
  1678. else:
  1679. resize_axis = 0 if _img.shape[0] >= _img.shape[1] else 1
  1680. ratio = max_size / _img.shape[resize_axis]
  1681. new_shape = [0, 0]
  1682. new_shape[resize_axis] = max_size
  1683. new_shape[1 - resize_axis] = int(_img.shape[1 - resize_axis] * ratio)
  1684. _img = cv2.resize(_img, (new_shape[1], new_shape[0]))
  1685. cv2.imwrite(img_path, _img)
  1686. def get_single_pdf(self, path, page_no):
  1687. log("into get_single_pdf")
  1688. try:
  1689. pdf_origin = copy.deepcopy(self.doc_pypdf2)
  1690. pdf_new = copy.deepcopy(self.doc_pypdf2_new)
  1691. pdf_new.addPage(pdf_origin.getPage(page_no))
  1692. path_new = path.split(".")[0] + "_split.pdf"
  1693. with open(path_new, "wb") as ff:
  1694. pdf_new.write(ff)
  1695. return path_new
  1696. except PyPDF2.utils.PdfReadError as e:
  1697. return [-3]
  1698. except Exception as e:
  1699. log("get_single_pdf error! page " + str(page_no))
  1700. return [-3]
  1701. def get_text_font():
  1702. def flags_decomposer(flags):
  1703. """Make font flags human readable."""
  1704. l = []
  1705. if flags & 2 ** 0:
  1706. l.append("superscript")
  1707. if flags & 2 ** 1:
  1708. l.append("italic")
  1709. if flags & 2 ** 2:
  1710. l.append("serifed")
  1711. else:
  1712. l.append("sans")
  1713. if flags & 2 ** 3:
  1714. l.append("monospaced")
  1715. else:
  1716. l.append("proportional")
  1717. if flags & 2 ** 4:
  1718. l.append("bold")
  1719. return ", ".join(l)
  1720. def get_underlined_textLines(page):
  1721. """
  1722. 获取某页pdf上的所有下划线文本信息
  1723. :param page: fitz中的一页
  1724. :return: list of tuples,每个tuple都是一个完整的下划线覆盖的整体:[(下划线句, 所在blk_no, 所在line_no), ...]
  1725. """
  1726. paths = page.get_drawings() # get drawings on the current page
  1727. # 获取该页内所有的height很小的bbox。因为下划线其实大多是这种矩形
  1728. # subselect things we may regard as lines
  1729. lines = []
  1730. for p in paths:
  1731. for item in p["items"]:
  1732. if item[0] == "l": # an actual line
  1733. p1, p2 = item[1:]
  1734. if p1.y == p2.y:
  1735. lines.append((p1, p2))
  1736. elif item[0] == "re": # a rectangle: check if height is small
  1737. r = item[1]
  1738. if r.width > r.height and r.height <= 2:
  1739. lines.append((r.tl, r.tr)) # take top left / right points
  1740. # 获取该页的`max_lineheight`,用于下面比较距离使用
  1741. blocks = page.get_text("dict", flags=11)["blocks"]
  1742. max_lineheight = 0
  1743. for b in blocks:
  1744. for l in b["lines"]:
  1745. bbox = fitz.Rect(l["bbox"])
  1746. if bbox.height > max_lineheight:
  1747. max_lineheight = bbox.height
  1748. underlined_res = []
  1749. # 开始对下划线内容进行查询
  1750. # make a list of words
  1751. words = page.get_text("words")
  1752. # if underlined, the bottom left / right of a word
  1753. # should not be too far away from left / right end of some line:
  1754. for wdx, w in enumerate(words): # w[4] is the actual word string
  1755. r = fitz.Rect(w[:4]) # first 4 items are the word bbox
  1756. for p1, p2 in lines: # check distances for start / end points
  1757. if abs(r.bl - p1) <= max_lineheight: # 当前word的左下满足下划线左下
  1758. if abs(r.br - p2) <= max_lineheight: # 当前word的右下满足下划线右下(单个词,无空格)
  1759. print(f"Word '{w[4]}' is underlined! Its block-line number is {w[-3], w[-2]}")
  1760. underlined_res.append((w[4], w[-3], w[-2])) # 分别是(下划线词,所在blk_no,所在line_no)
  1761. break # don't check more lines
  1762. else: # 继续寻找同line右侧的有缘人,因为有些下划线覆盖的词包含多个词,多个词之间有空格
  1763. curr_line_num = w[-2] # line nunmber
  1764. for right_wdx in range(wdx + 1, len(words), 1):
  1765. _next_w = words[right_wdx]
  1766. if _next_w[-2] != curr_line_num: # 当前遍历到的右侧word已经不是当前行的了(跨行是不行的)
  1767. break
  1768. _r_right = fitz.Rect(_next_w[:4]) # 获取当前同行右侧某word的方框4点
  1769. if abs(_r_right.br - p2) <= max_lineheight: # 用此word右下点和p2(目标下划线右上点)算距离,距离要小于max_lineheight
  1770. print(
  1771. f"Word '{' '.join([_one_word[4] for _one_word in words[wdx:right_wdx + 1]])}' is underlined! " +
  1772. f"Its block-line number is {w[-3], w[-2]}")
  1773. underlined_res.append(
  1774. (' '.join([_one_word[4] for _one_word in words[wdx:right_wdx + 1]]),
  1775. w[-3], w[-2])
  1776. ) # 分别是(下划线词,所在blk_no,所在line_no)
  1777. break # don't check more lines
  1778. return underlined_res
  1779. _p = r'C:\Users\Administrator\Desktop\test_pdf\error2-2.pdf'
  1780. doc_pymupdf = read_pymupdf(_p)
  1781. page = doc_pymupdf[0]
  1782. blocks = page.get_text("dict", flags=11)["blocks"]
  1783. for b in blocks: # iterate through the text blocks
  1784. for l in b["lines"]: # iterate through the text lines
  1785. for s in l["spans"]: # iterate through the text spans
  1786. print("")
  1787. font_properties = "Font: '%s' (%s), size %g, color #%06x" % (
  1788. s["font"], # font name
  1789. flags_decomposer(s["flags"]), # readable font flags
  1790. s["size"], # font size
  1791. s["color"], # font color
  1792. )
  1793. print(s)
  1794. print("Text: '%s'" % s["text"]) # simple print of text
  1795. print(font_properties)
  1796. get_underlined_textLines(page)
  1797. # 以下为现成pdf单页解析接口
  1798. class ParseSentence:
  1799. def __init__(self, bbox, fontname, fontsize, _text, _title, title_text, _pattern, title_degree, is_outline,
  1800. outline_location, page_no):
  1801. (x0, y0, x1, y1) = bbox
  1802. self.x0 = x0
  1803. self.y0 = y0
  1804. self.x1 = x1
  1805. self.y1 = y1
  1806. self.bbox = bbox
  1807. self.fontname = fontname
  1808. self.fontsize = fontsize
  1809. self.text = _text
  1810. self.title = _title
  1811. self.title_text = title_text
  1812. self.groups = _pattern
  1813. self.title_degree = title_degree
  1814. self.is_outline = is_outline
  1815. self.outline_location = outline_location
  1816. self.page_no = page_no
  1817. def __repr__(self):
  1818. return "%s,%s,%s,%d,%s" % (self.text, self.title, self.is_outline, self.outline_location, str(self.bbox))
  1819. class ParseUtils:
  1820. @staticmethod
  1821. def getFontinfo(_page):
  1822. for _obj in _page._objs:
  1823. if isinstance(_obj, (LTTextBoxHorizontal, LTTextBoxVertical)):
  1824. for textline in _obj._objs:
  1825. done = False
  1826. for lchar in textline._objs:
  1827. if isinstance(lchar, (LTChar)):
  1828. _obj.fontname = lchar.fontname
  1829. _obj.fontsize = lchar.size
  1830. done = True
  1831. break
  1832. if done:
  1833. break
  1834. @staticmethod
  1835. def recognize_sentences(list_textbox, filter_objs, page_bbox, page_no,
  1836. remove_space=True, sourceP_LB=True):
  1837. list_textbox.sort(key=lambda x: x.bbox[0])
  1838. list_textbox.sort(key=lambda x: x.bbox[3], reverse=sourceP_LB)
  1839. cluster_textbox = []
  1840. for _textbox in list_textbox:
  1841. if _textbox in filter_objs:
  1842. continue
  1843. _find = False
  1844. for _ct in cluster_textbox:
  1845. if abs(_ct["y"] - _textbox.bbox[1]) < 5:
  1846. _find = True
  1847. _ct["textbox"].append(_textbox)
  1848. if not _find:
  1849. cluster_textbox.append({"y": _textbox.bbox[1], "textbox": [_textbox]})
  1850. cluster_textbox.sort(key=lambda x: x["y"], reverse=sourceP_LB)
  1851. list_sentences = []
  1852. for _line in cluster_textbox:
  1853. _textboxs = _line["textbox"]
  1854. _textboxs.sort(key=lambda x: x.bbox[0])
  1855. _linetext = _textboxs[0].get_text()
  1856. for _i in range(1, len(_textboxs)):
  1857. if abs(_textboxs[_i].bbox[0] - _textboxs[_i - 1].bbox[2]) > 60:
  1858. if _linetext[-1] not in (",", ",", "。", ".", "、", ";"):
  1859. _linetext += "=,="
  1860. _linetext += _textboxs[_i].get_text()
  1861. _linetext = re.sub("[\s\r\n]", "", _linetext)
  1862. _bbox = (_textboxs[0].bbox[0], _textboxs[0].bbox[1],
  1863. _textboxs[-1].bbox[2], _textboxs[-1].bbox[3])
  1864. _title = None
  1865. _pattern_groups = None
  1866. title_text = ""
  1867. if not _title:
  1868. _groups = ParseUtils.find_title_by_pattern(_textboxs[0].get_text())
  1869. if _groups:
  1870. _title = _groups[0][0]
  1871. title_text = _groups[0][1]
  1872. _pattern_groups = _groups
  1873. if not _title:
  1874. _groups = ParseUtils.find_title_by_pattern(_linetext)
  1875. if _groups:
  1876. _title = _groups[0][0]
  1877. title_text = _groups[0][1]
  1878. _pattern_groups = _groups
  1879. if not _title:
  1880. _title = ParseUtils.rec_incenter(_bbox, page_bbox)
  1881. title_degree = 2
  1882. if not _title:
  1883. _linetext = _linetext.replace("=,=", ",")
  1884. else:
  1885. _linetext = _linetext.replace("=,=", "")
  1886. title_degree = int(_title.split("_")[1])
  1887. # 页码
  1888. if ParseUtils.rec_incenter(_bbox, page_bbox) and re.search("^\d+$", _linetext) is not None:
  1889. continue
  1890. if _linetext == "" or re.search("^,+$", _linetext) is not None:
  1891. continue
  1892. is_outline = False
  1893. outline_location = -1
  1894. _search = re.search("(?P<text>.+?)\.{5,}(?P<nums>\d+)$", _linetext)
  1895. if _search is not None:
  1896. is_outline = True
  1897. _linetext = _search.group("text")
  1898. outline_location = int(_search.group("nums"))
  1899. list_sentences.append(
  1900. ParseSentence(_bbox, _textboxs[-1].__dict__.get("fontname"), _textboxs[-1].__dict__.get("fontsize"),
  1901. _linetext, _title, title_text, _pattern_groups, title_degree, is_outline,
  1902. outline_location, page_no))
  1903. # for _sen in list_sentences:
  1904. # print(_sen.__dict__)
  1905. return list_sentences
  1906. @staticmethod
  1907. def find_title_by_pattern(_text,
  1908. _pattern="(?P<title_1>(?P<title_1_index_0_0>^第?)(?P<title_1_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_1_index_2_0>[、章]))|" \
  1909. "(?P<title_3>^(?P<title_3_index_0_1>[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+))|" \
  1910. "(?P<title_4>^(?P<title_4_index_0_0>第?)(?P<title_4_index_1_1>[一二三四五六七八九十]+)(?P<title_4_index_2_0>[节]))|" \
  1911. "(?P<title_11>^(?P<title_11_index_0_0>\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-])(?P<title_11_index_1_1>\d{1,2})(?P<title_11_index_2_0>[\..、\s\-]))|" \
  1912. "(?P<title_10>^(?P<title_10_index_0_0>\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-])(?P<title_10_index_1_1>\d{1,2})(?P<title_10_index_2_0>[\..、\s\-]))|" \
  1913. "(?P<title_7>^(?P<title_7_index_0_0>\d{1,2}[\..、\s\-])(?P<title_7_index_1_1>\d{1,2})(?P<title_7_index_2_0>[\..、\s\-]))|" \
  1914. "(?P<title_6>^(?P<title_6_index_0_1>\d{1,2})(?P<title_6_index_1_0>[\..、\s\-]))|" \
  1915. "(?P<title_15>^(?P<title_15_index_0_0>(?)(?P<title_15_index_1_1>\d{1,2})(?P<title_15_index_2_0>)))|" \
  1916. "(?P<title_17>^(?P<title_17_index_0_0>(?)(?P<title_17_index_1_1>[a-zA-Z]+)(?P<title_17_index_2_0>)))|"
  1917. "(?P<title_19>^(?P<title_19_index_0_0>(?)(?P<title_19_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_19_index_2_0>)))|" \
  1918. ):
  1919. _se = re.search(_pattern, _text)
  1920. groups = []
  1921. if _se is not None:
  1922. _gd = _se.groupdict()
  1923. for k, v in _gd.items():
  1924. if v is not None:
  1925. groups.append((k, v))
  1926. if len(groups):
  1927. groups.sort(key=lambda x: x[0])
  1928. return groups
  1929. return None
  1930. @staticmethod
  1931. def rec_incenter(o_bbox, p_bbox):
  1932. p_width = p_bbox[2] - p_bbox[0]
  1933. l_space = (o_bbox[0] - p_bbox[0]) / p_width
  1934. r_space = (p_bbox[2] - o_bbox[2]) / p_width
  1935. if abs((l_space - r_space)) < 0.1 and l_space > 0.2:
  1936. return "title_2"
  1937. @staticmethod
  1938. def is_first_title(_title):
  1939. if _title is None:
  1940. return False
  1941. if re.search("^\d+$", _title) is not None:
  1942. if int(_title) == 1:
  1943. return True
  1944. return False
  1945. if re.search("^[一二三四五六七八九十百]+$", _title) is not None:
  1946. if _title == "一":
  1947. return True
  1948. return False
  1949. if re.search("^[a-z]+$", _title) is not None:
  1950. if _title == "a":
  1951. return True
  1952. return False
  1953. if re.search("^[A-Z]+$", _title) is not None:
  1954. if _title == "A":
  1955. return True
  1956. return False
  1957. if re.search("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]$", _title) is not None:
  1958. if _title == "Ⅰ":
  1959. return True
  1960. return False
  1961. return False
  1962. @staticmethod
  1963. def get_next_title(_title):
  1964. if re.search("^\d+$", _title) is not None:
  1965. return str(int(_title) + 1)
  1966. if re.search("^[一二三四五六七八九十百]+$", _title) is not None:
  1967. _next_title = ParseUtils.make_increase(['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'],
  1968. re.sub("[十百]", '', _title))
  1969. _next_title = list(_next_title)
  1970. _next_title.reverse()
  1971. if _next_title[-1] != "十":
  1972. if len(_next_title) >= 2:
  1973. _next_title.insert(-1, '十')
  1974. if len(_next_title) >= 4:
  1975. _next_title.insert(-3, '百')
  1976. if _title[0] == "十":
  1977. if _next_title == "十":
  1978. _next_title = ["二", "十"]
  1979. _next_title.insert(0, "十")
  1980. _next_title = "".join(_next_title)
  1981. return _next_title
  1982. if re.search("^[a-z]+$", _title) is not None:
  1983. _next_title = ParseUtils.make_increase([chr(i + ord('a')) for i in range(26)], _title)
  1984. _next_title = list(_next_title)
  1985. _next_title.reverse()
  1986. return "".join(_next_title)
  1987. if re.search("^[A-Z]+$", _title) is not None:
  1988. _next_title = ParseUtils.make_increase([chr(i + ord('A')) for i in range(26)], _title)
  1989. _next_title = list(_next_title)
  1990. _next_title.reverse()
  1991. return "".join(_next_title)
  1992. if re.search("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]$", _title) is not None:
  1993. _sort = ["Ⅰ", "Ⅱ", "Ⅲ", "Ⅳ", "Ⅴ", "Ⅵ", "Ⅶ", "Ⅷ", "Ⅸ", "Ⅹ", "Ⅺ", "Ⅻ"]
  1994. _index = _sort.index(_title)
  1995. if _index < len(_sort) - 1:
  1996. return _sort[_index + 1]
  1997. return None
  1998. @staticmethod
  1999. def make_increase(_sort, _title, _add=1):
  2000. if len(_title) == 0 and _add == 0:
  2001. return ""
  2002. if len(_title) == 0 and _add == 1:
  2003. return _sort[0]
  2004. _index = _sort.index(_title[-1])
  2005. next_index = (_index + _add) % len(_sort)
  2006. next_chr = _sort[next_index]
  2007. if _index == len(_sort) - 1:
  2008. _add = 1
  2009. else:
  2010. _add = 0
  2011. return next_chr + ParseUtils.make_increase(_sort, _title[:-1], _add)
  2012. @staticmethod
  2013. def rec_serial(_text, o_bbox, p_bbox, fontname, _pattern="(?P<title_1>^[一二三四五六七八九十]+[、])|" \
  2014. "(?P<title_2>^\d+[\.、\s])|" \
  2015. "(?P<title_3>^\d+\.\d+[\.、\s])|" \
  2016. "(?P<title_4>^\d+\.\d+\.\d+[\.、\s])|" \
  2017. "(?P<title_5>^\d+\.\d+\.\d+\.\d+[\.、\s])"):
  2018. # todo :recog the serial of the sentence
  2019. _se = re.search(_pattern, _text)
  2020. if _se is not None:
  2021. _gd = _se.groupdict()
  2022. for k, v in _gd.items():
  2023. if v is not None:
  2024. return k
  2025. return None
  2026. if __name__ == '__main__':
  2027. # get_text_font()
  2028. PDFConvert(r"C:/Users/Administrator/Downloads/1651896704621.pdf", "C:/Users/Administrator/Downloads/1").get_html()
  2029. # print(b'\x10')