為什麼要文件格式轉換?
無論讀者現在是做數據挖掘、數據分析、自然語言處理、智能對話系統、商品推薦系統等等,都不可避免的涉及語料的問題即大數據。數據來源無非分為結構化數據、半結構化數據和非結構化數據。其中結構化數據以規範的文檔、資料庫文件等等為代表;半結構化數據以網頁、json文件等為代表;非結構化數據以自由文本為主,諸如隨想錄、中醫病症記錄等等。遺憾的是現實生活中半結構化和非結構化數據居多,而且往往還需要自己去收集。
讀者試想以下情況:
你的技術主管交給你一堆數據文件,讓你做數據分析工作。你打開一看文件格式繁雜,諸如pdf、doc、docx、txt、excel等。更悲催的是有些pdf文件還是加密的,或者是圖片格式的等複雜情況。此刻你採用什麼方法做數據分析與預處理工作呢?
上面情況算你幸運,隔幾天技術主管直接給你一堆網站,讓你自己去採集信息。你或許會驚喜的說的,那不簡單,使用爬蟲技術不就可以啦?恭喜你思路完全正確,可是爬取過程中遇到一些網頁是pdf格式的情況,你不能直接抓取頁面了。你此刻如何去採集信息呢?
現有工具的轉換效果如何
針對以上典型的情況,自定義插件PDFMiner、win2com等將派上用場(本文主要講述文件格式轉化,網絡爬蟲解析讀者自行研究)。首先我們看看常規方式的處理,比如我下載個格式轉化軟體或者在線格式轉化軟體,具體如下所示:
在線格式轉換工具1頁面效果如下:
圖1-1 在線格式轉換工具1
pdf格式轉化為txt後的效果如下:
圖1-2 工具1pdf格式轉化為txt結果
上面轉換效果讀者是否滿意?是否因為某一個在線轉換工具不完備,那我們再嘗試一個,在線格式轉換工具2頁面效果如下:
圖1-3 在線格式轉換工具2
pdf格式轉化為txt後的效果如下:
圖1-4 工具2pdf格式轉化為txt結果
繼續我們的格式轉換工作,我們這次採用offic軟體內帶的pdf另存為效果如下:
圖1-5 offic軟體內帶的pdf另存為效果
總結
通過上面現有常規的方法,我們總結出以下問題:
1、 格式轉換後,識別亂碼較多。2、 不支持或者限制支持批量處理。3、 格式轉換後的txt文件存在編碼問題。4、 生成目標文件的標題跟原標題不一致。5、 操作不夠靈活便捷。1.2 基於自定義格式轉換介紹預期效果
1、 將帶有嵌套的目錄放在一個根目錄文件下,只需要傳入文件名即可自動轉化。2、 自動過濾掉不符合指定格式的文件。3、 對處理的pdf文件不能識別的(加密文件等)給出日誌記錄其路徑。4、 生成目標文件的標題跟原文件目錄標題保持一致。5、 生成的文件按照統一的utf-8編碼格式保存。6、 支持默認保存路徑與自定義保存路徑。預期效果展示
待處理語料數據如下:
圖1-6 處理語料數據集
處理後默認自動保存的結果(支持自定義指定保存目錄):
圖1-7 默認自動保存轉化後的數據集
基於自定義插件的文本轉化效果:
圖1-8 自定義插件的文本轉化效果
基於pdfminer插件的運行效果
圖1-9 整體運行效果圖
2.1 基礎準備工作運行環境
1、windows7以上64bit作業系統2、sublime運行環境3、python3.0+
需要插件
1、 pdfminer插件: 連結: https://pan.baidu.com/s/1p7X430bvBpjJ-qGNO-Fmcg 密碼: v5th 或者:pip install pdfminer3k
圖2-1 pip方式安裝pdfminer插件
2、 win2com 插件:連結: https://pan.baidu.com/s/1-2BsiTs8XjMIe5Gnh_GFjw 密碼: 7j3t pip install pypiwin32
圖2-2 pip方式安裝win2com插件
2.2 類庫重構算法基礎類庫重構
重構又稱高度代碼封裝,旨在代碼重用和面向對象編程。本文將相關基本方法封裝在一個類庫中供外部類調用,提高代碼復用性和可讀性。具體重構文件結構如下:
重構文件名:BaseClass.py'''功能描述:遍歷目錄,對子文件單獨處理參數描述: 1 rootdir:待處理的目錄路徑 2 deffun: 方法參數,默認為空 3 savepath: 保存路徑'''class TraversalFun(): TraversalDir:遍歷目錄文件方法 creat_savepath:支持默認和自定義保存目錄方法 AllFiles:遞歸遍歷所有文件,並提供具體文件操作功能 TranType:通過指定關鍵字操作,檢查文件類型並轉化目標類型 filelogs:記錄文件處理日誌方法 cleardir:清空目錄文件方法 writeFile:文件的寫操作方法 readFile:文件的讀操作方法 mkdir:創建目錄方法 ''' 功能描述:提供全局變量類 作 者:白寧超 時 間:2017年10月24日15:07:38 ''' class Global(object):提高各個公共全局變量 ''' 功能描述:測試類 作 者:白寧超 時 間:2017年10月24日15:07:38 ''' def TestMethod(filepath,newpath):方法測試類
核心方法詳解
1 TraversalFun類方法:
def __init__(self,rootdir,deffun=None,savedir=""): self.rootdir = rootdir # 目錄路徑 self.deffun = deffun # 參數方法 self.savedir = savedir # 保存路徑''' 遍歷目錄文件'''def TraversalDir(self,defpar='newpath'): try: # 支持默認和自定義保存目錄 newdir = TraversalFun.creat_savepath(self,defpar) # 遞歸遍歷word文件並將其轉化txt文件 TraversalFun.AllFiles(self,self.rootdir,newdir) except Exception as e: raise e'''支持默認和自定義保存目錄'''# @staticmethoddef creat_savepath(self,defpar): # 文件路徑切分為上級路徑和文件名('F:\\kjxm\\kjt', '1.txt') prapath,filename = os.path.split(self.rootdir) newdir = "" if self.savedir=="": newdir = os.path.abspath(os.path.join(prapath,filename+"_"+defpar)) else: newdir = self.savedir print("保存目錄路徑:\n"+newdir) if not os.path.exists(newdir): os.mkdir(newdir) return newdir'''遞歸遍歷所有文件,並提供具體文件操作功能。'''def AllFiles(self,rootdir,newdir=''): # 返回指定目錄包含的文件或文件夾的名字的列表 for lists in os.listdir(rootdir): # 待處理文件夾名字集合 path = os.path.join(rootdir, lists) # 核心算法,對文件具體操作 if os.path.isfile(path): self.deffun(path,newdir) # 具體方法實現功能 # TraversalFun.filelogs(rootdir) # 日誌文件 # 遞歸遍歷文件目錄 if os.path.isdir(path): newpath = os.path.join(newdir, lists) if not os.path.exists(newpath): os.mkdir(newpath) TraversalFun.AllFiles(self,path,newpath)''' 通過指定關鍵字操作,檢查文件類型並轉化目標類型'''def TranType(filename,typename): # print("本方法支持文件類型處理格式:pdf2txt,代表pdf轉化為txt;word2txt,代表word轉化txt;word2pdf,代表word轉化pdf。") # 新的文件名稱 new_name = "" if typename == "pdf2txt" : #如果不是pdf文件,或者是pdf臨時文件退出 if not fnmatch.fnmatch(filename, '*.pdf') or not fnmatch.fnmatch(filename, '*.PDF') or fnmatch.fnmatch(filename, '~$*'): return # 如果是pdf文件,修改文件名 if fnmatch.fnmatch(filename, '*.pdf') or fnmatch.fnmatch(filename, '*.PDF'): new_name = filename[:-4]+'.txt' # 截取".pdf"之前的文件名 if typename == "word2txt" : #如果是word文件: if fnmatch.fnmatch(filename, '*.doc') : new_name = filename[:-4]+'.txt' print(new_name) if fnmatch.fnmatch(filename, '*.docx'): new_name = filename[:-5]+'.txt' # 如果不是word文件,或者是word臨時文件退出 else: return if typename == "word2pdf" : #如果是word文件: if fnmatch.fnmatch(filename, '*.doc'): new_name = filename[:-4]+'.pdf' if fnmatch.fnmatch(filename, '*.docx'): new_name = filename[:-5]+'.pdf' #如果不是word文件:繼續 else: return return new_name'''記錄文件處理日誌'''def filelogs(rootdir): prapath,filename = os.path.split(rootdir) # 創建日誌目錄 dirpath = prapath+r"/"+filename+"_logs" TraversalFun.mkdir(dirpath) # 錯誤文件路徑 errorpath = dirpath+r"/errorlogs.txt" # 限制文件路徑 limitpath = dirpath+r"/limitlogs.txt" # 錯誤文件日誌寫入 TraversalFun.writeFile(errorpath,'\n'.join(Global.error_file_list)) # # 限制文件日誌寫入 TraversalFun.writeFile(limitpath,'\n'.join(Global.limit_file_list))'''清空目錄文件'''def cleardir(dirpath): if not os.path.exists(dirpath): TraversalFun.mkdir(dirpath) else: shutil.rmtree(dirpath) TraversalFun.mkdir(dirpath)''' 文件的寫操作'''def writeFile(filepath,strs): #encoding="utf-8" with open(filepath,'wb') as f: f.write(strs.encode())''' 文件的讀操作'''def readFile(filepath): isfile = os.path.exists(filepath) readstr = "" if isfile: with open(filepath,"r",encoding="utf-8") as f: readstr = f.read() else: return return readstr''' 創建目錄 '''def mkdir(dirpath): # 判斷路徑是否存在 isExists=os.path.exists(dirpath) # 判斷結果 if not isExists: os.makedirs(dirpath) print(dirpath+' 創建成功') else: pass
2 TestMethod測試類
'''功能描述:測試類作 者:白寧超時 間:2017年10月24日15:07:38'''def TestMethod(filepath,newpath): if os.path.isfile(filepath) : print("this is file name:"+filepath) else: pass
3 利用測試類方法運行方法參數效果圖
方法的調用:傳達參數分別是跟目錄和測試類中的方法參數
t1=time.time()# 根目錄文件路徑rootDir = r"../../Corpus/DataSet"tra=TraversalFun(rootDir,TestMethod) # 默認方法參數列印所有文件路徑tra.TraversalDir() # 遍歷文件並進行相關操作t2=time.time()totalTime=Decimal(str(t2-t1)).quantize(Decimal('0.0000'))print("耗時:"+str(totalTime)+" s"+"\n")input()
運行結果如圖所示:
圖2-3 測試方法參數判斷是文件的列印文件路徑
pdfminer原理介紹
圖3-1 pdfminer解析原理
由於解析PDF是一件非常耗時和內存的工作,因此PDFMiner使用了一種稱作lazy parsing的策略,只在需要的時候才去解析,以減少時間和內存的使用。要解析PDF至少需要兩個類:PDFParser 和 PDFDocument,PDFParser 從文件中提取數據,PDFDocument保存數據。另外還需要PDFPageInterpreter去處理頁面內容,PDFDevice將其轉換為我們所需要的。PDFResourceManager用於保存共享內容例如字體或圖片。
圖3-2 LTpage解析原理
LTPage :表示整個頁。可能會含有LTTextBox,LTFigure,LTImage,LTRect,LTCurve和LTLine子對象。
LTTextBox:表示一組文本塊可能包含在一個矩形區域。注意此box是由幾何分析中創建,並且不一定表示該文本的一個邏輯邊界。它包含TTextLine對象的列表。使用 get_text()方法返回的文本內容。
LTTextLine :包含表示單個文本行LTChar對象的列表。字符對齊要麼 水平或垂直,取決於文本的寫入模式。
get_text()方法返回的文本內容。
LTAnno:在文本中實際的字母表示為Unicode字符串(?)。需要注意的是,雖然一個LTChar對象具有實際邊界,LTAnno對象沒有,因為這些是「虛擬」的字符,根據兩個字符間的關係(例如,一個空格)由布局分析後插入。
LTImage:表示一個圖像對象。嵌入式圖像可以是JPEG或其它格式,但是目前PDFMiner沒有放置太多精力在圖形對象。
LTLine:代表一條直線。可用於分離文本或附圖。
LTRect:表示矩形。可用於框架的另一圖片或數字。
LTCurve:表示一個通用的 Bezier曲線
pdfminer學習文獻
英文官方:https://euske.github.io/pdfminer/index.html 中文:https://blog.csdn.net/robolinux/article/details/43318229
pdfminer代碼實現
# pdfminer庫的地址 https://pypi.python.org/pypi/pdfminer3k# 下載後,用cmd執行命令 setup.py installfrom pdfminer.pdfparser import PDFParser,PDFDocumentfrom pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreterfrom pdfminer.converter import PDFPageAggregatorfrom pdfminer.layout import LTTextBoxHorizontal,LAParamsfrom pdfminer.pdfinterp import PDFTextExtractionNotAllowedfrom decimal import Decimalimport time,fnmatch,os,re,sysfrom BaseClass import * #全局變量# from BaseClass import TraversalFun # 文件遍歷處理基類函數# 清除警告import logginglogging.Logger.propagate = Falselogging.getLogger().setLevel(logging.ERROR)'''pdf文件格式轉換為txt'''def PdfToText(filepath,newdir=''): # 文件路徑切分為上級路徑和文件名 prapath,filename = os.path.split(filepath) new_txt_name=TraversalFun.TranType(filename,"pdf2txt") # 更改文件名 if new_txt_name ==None: return newpath = os.path.join(newdir,new_txt_name) # 文件保存路徑 print ("->格式轉換後保留路徑:\n"+newpath) try: praser = PDFParser(open(filepath, 'rb')) # 創建一個pdf文檔分析器 doc = PDFDocument() # 創建一個PDF文檔 praser.set_document(doc) # 連接分析器 與文檔對象 doc.set_parser(praser) doc.initialize() # 提供初始化密碼,如果沒有密碼 就創建一個空的字符串 # 檢測文檔是否提供txt轉換,不提供就忽略 if not doc.is_extractable: Global.error_file_list.append(filepath) return rsrcmgr = PDFResourceManager() # 創建PDf 資源管理器管理共享資源 laparams = LAParams() # 創建一個PDF設備對象 device = PDFPageAggregator(rsrcmgr, laparams=laparams) interpreter = PDFPageInterpreter(rsrcmgr, device) # 創建一個PDF解釋器對象 pdfStr = "" # 存儲解析後的提取內容 # 循環遍歷列表,每次處理一個page的內容 for page in doc.get_pages(): # doc.get_pages()獲取page列表 interpreter.process_page(page) layout = device.get_result() # 接受該頁面的LTPage對象 # 這裡layout是一個LTPage對象 裡面存放著 這個page解析出的各種對象 一般包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 想要獲取文本就獲得對象的text屬性, for x in layout: if (isinstance(x, LTTextBoxHorizontal)): pdfStr = pdfStr + x.get_text() TraversalFun.writeFile(newpath,pdfStr) # 寫文件 # 限制文件列表 filesize = os.path.getsize(newpath) if filesize < Global.limit_file_size : Global.limit_file_list.append(newpath+"\t"+ str(Decimal(filesize/1024).quantize(Decimal('0.00'))) +"KB") os.remove(newpath) else : Global.all_FileNum+=1 except Exception as e: Global.error_file_list.append(filepath) returnif __name__ == '__main__': t1=time.time() rootDir = r"../../Corpus/DataSet" # 默認處理路徑 TraversalFun.cleardir(r'../../Corpus/DataSet_newpath') # 每次加載清空目錄 print ('【批量生成的文件:】') tra=TraversalFun(rootDir,PdfToText) # 默認方法參數列印所有文件路徑 tra.TraversalDir() # 寫入日誌文件 TraversalFun.filelogs(rootDir) print ('共處理文檔數目:'+str(Global.all_FileNum+len(Global.error_file_list)+len(Global.limit_file_list))+' 個,其中:\n \ 1) 篩選文件(可用)'+str(Global.all_FileNum)+'個.\n \ 2) 錯誤文件(不能識別)'+ str(len(Global.error_file_list)) +'個.\n \ 3) 限制文件(<5K)'+ str(len(Global.limit_file_list))+'個.' ) t2=time.time() totalTime=Decimal(str(t2-t1)).quantize(Decimal('0.0000')) print("耗時:"+str(totalTime)+" s"+"\n") input()
解析pdf文件用到的類:
PDFParser:從一個文件中獲取數據
PDFDocument:保存獲取的數據,和PDFParser是相互關聯的
PDFPageInterpreter處理頁面內容
PDFDevice將其翻譯成你需要的格式
PDFResourceManager用於存儲共享資源,如字體或圖像。
pdfminer頁面結果:
圖3-3 pdfminer運行效果
pdfminer轉化結果
圖3-4 pdfminer轉化結果
實驗結論
錯誤分析,打開日誌文件查看
圖3-5 pdfminer轉化限制文件
錯誤原因分析:因為我們在全局變量中限制了最小文件讀取1KB,該文件0KB不符合要求故而過濾出來。打開查看發現該pdf是一張圖片轉換出來的,沒有成功識別。但是,通過技術研究是可以實現的,本文沒有深入進行。還有以下結論:
1 可以支持批量文本和單文本轉化。2 編碼格式一致,默認utf-8。3 生成文件名譽原始處理文件名保存一致。4 生成的文本信息相對比較規範。
支持多方式轉化,其他案例讀者自行研究。
擴展學習
在解析有些PDF的時候會報這樣的異常:pdfminer.pdfdocument.PDFEncryptionError: Unknown algorithm: param={'CF': {'StdCF': {'Length': 16, 'CFM': /AESV2, 'AuthEvent': /DocOpen}}, 'O': '\xe4\xe74\xb86/\xa8)\xa6x\xe6\xa3/U\xdf\x0fWR\x9cPh\xac\xae\x88B\x06_\xb0\x93@\x9f\x8d', 'Filter': /Standard, 'P': -1340, 'Length': 128, 'R': 4, 'U': '|UTX#f\xc9V\x18\x87z\x10\xcb\xf5{\xa7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'V': 4, 'StmF': /StdCF, 'StrF': /StdCF}
如上是加密的PDF,所以無法解析 ,但是如果直接打開PDF卻是可以的並沒有要求輸密碼什麼的,原因是這個PDF雖然是加過密的,但密碼是空,所以就出現了這樣的問題。
解決這個的問題的辦法是通過qpdf命令來解密文件(要確保已經安裝了qpdf),要想在python中調用該命令只需使用call即可:
1 from subprocess import call2 call('qpdf --password=%s --decrypt %s %s' %('', file_path, new_file_path), shell=True)
其中參數filepath是要解密的PDF的路徑,newfile_path是解密後的PDF文件路徑,然後使用解密後的文件去做解析就OK了
導入相關包
from win32com import client as wcfrom win32com.client import Dispatch, constants, gencacheimport os,fnmatch,time,sysfrom decimal import Decimalfrom BaseClass import * # 自定義類庫
word的doc或docx文件轉化pdf文本
1 代碼實現
'''功能名稱:word的doc或docx文件轉化pdf文本功能描述:輸入一個doc或docx文件路徑,自動轉化為pdf文件,並存儲在當前路徑下。 用戶可以指定存儲文件路徑。參數描述: 1 filepath:單個文件路徑 2 newdir: 指定保存路徑測試路徑: F:\corper\kjt\1.docx'''def doc2pdf(filepath,newDir=''): # 文件路徑切分為上級路徑和文件名 prapath,filename = os.path.split(filepath) # 單文件處理使用 if newDir=='': newDir = prapath else: newDir =newDir new_txt_name=TraversalFun.TranType(filename,"word2pdf") if new_txt_name ==None: return else: print(new_txt_name) newpath = os.path.join(newDir,new_txt_name) word = wc.DispatchEx("Word.Application") worddoc = word.Documents.Open(filepath,ReadOnly = 1) worddoc.SaveAs(newpath, FileFormat = 17) worddoc.Close() Global.all_FileNum+=1
2 單個word轉換pdf
主程序運行代碼:
# 單個word轉換pdffilepath=os.path.abspath(r"../../Corpus/DataSet/2012/科技項目數據挖掘決策架構.docx")doc2pdf(filepath)
控制臺列印效果:
科技項目數據挖掘決策架構.pdf共處理文檔數目:1 個耗時:3.6121 s
結果:
圖4-1 word文檔單文件轉化結果
打開顯示:
圖4-2 轉化後規範的pdf文件
3 批量word轉換pdf
主程序運行代碼:
rootDir =os.path.abspath(r"../../Corpus/DataSet")# 1 批量的word轉換pdftra=TraversalFun(rootDir,doc2pdf) # 默認方法參數列印所有文件路徑tra.TraversalDir('word2pdf')
控制臺列印效果:
保存目錄路徑:F:\AllNote\AllProject\TechDataMining\Corpus\DataSet_word2pdf科技項目數據挖掘決策架構.pdf科技項目數據挖掘決策架構.pdf共處理文檔數目:2 個耗時:7.1494 s
結果:
圖4-3 word文檔批量轉化結果
word的doc或docx文件轉化txt文本
1 代碼實現
'''功能名稱:單個word的doc或docx文件轉化txt文本'''def WordTranslate(filepath,newDir=''): # 文件路徑切分為上級路徑和文件名 prapath,filename = os.path.split(os.path.abspath(filepath)) if newDir=='': newDir = prapath else: newDir =newDir new_txt_name=TraversalFun.TranType(filename,'word2txt') if new_txt_name == None: return else: word_to_txt = os.path.join(newDir,new_txt_name) print ("格式轉換後保留路徑:\n"+word_to_txt) #加載處理應用 wordapp = wc.Dispatch('Word.Application') doc = wordapp.Documents.Open(filepath) #為了讓python可以在後續操作中r方式讀取txt和不產生亂碼,參數為4 doc.SaveAs(word_to_txt,4) doc.Close() # print(word_to_txt) Global.all_FileNum += 1
2 單個word轉換txt
# 單個word轉換txt filepath=os.path.abspath(r"../../Corpus/DataSet/2012/科技項目數據挖掘決策架構.docx")WordTranslate(filepath)
3 批量的word轉換txt
# 批量的word轉換txttra=TraversalFun(rootDir,WordTranslate) # 默認方法參數列印所有文件路徑tra.TraversalDir('word2txt')
4 批量的word轉換txt結果
圖4-4 批量的word轉換txt結果
pdf文件轉化txt文本
1 代碼實現
'''功能名稱:pdf文件轉化txt文本功能描述:輸入一個pdf文件路徑,自動轉化為txt文件,並存儲在當前路徑下。 用戶可以指定存儲文件路徑。參數描述: 1 filepath:單個文件路徑 2 newdir: 指定保存路徑測試路徑: F:\corper\kjt\申報書.pdf'''def PdfTranslate(filepath,newDir=''): # 文件路徑切分為上級路徑和文件名 prapath,filename = os.path.split(filepath) if newDir=="": newDir = prapath else: newDir = newDir new_txt_name=TraversalFun.TranType(filename,"pdf2txt") if new_txt_name ==None: return else: word_to_txt = os.path.join(newDir,new_txt_name) # print(word_to_txt) #加載處理應用 wordapp = wc.Dispatch('Word.Application') doc = wordapp.Documents.Open(filepath) #為了讓python可以在後續操作中r方式讀取txt和不產生亂碼,參數為4 doc.SaveAs(word_to_txt,4) doc.Close() Global.all_FileNum += 1
2 單個pdf文件轉化txt文本
# 單個pdf轉換txtfilepath=os.path.abspath(r"../../Corpus/DataSet/2012/改進樸素貝葉斯文本分類方法研究.pdf")PdfTranslate(filepath)
3 批量pdf文件轉化txt文本
# 3 批量的pdf轉換txttra=TraversalFun(rootDir,PdfTranslate) # 默認方法參數列印所有文件路徑tra.TraversalDir("pdf2txt")
4 批量pdf文件轉化txt文本結果
圖4-5 批量pdf文件轉化txt文本結果
機器學習和自然語言QQ群:436303759
【微信公眾號:datathinks】
源碼請進QQ群文件下載:
圖5-1 完整項目文件
推薦書籍
【自然語言處理理論與實戰 】由清華大學教授、博士生導師王道順;電子科技大學教授、博士生導師周世傑;中國科學院研究員、博士生導師崔喆;百度企業智能平臺,大數據高級工程師 潘耀峰;美團點評,大數據研發工程師陸志君;英國哈德斯菲爾德大學,人工智慧博士張朝龍聯合推薦。已經預售,可在京東、 淘寶 、亞馬遜 、 噹噹 等網站購買....
參考文獻http://www.unixuser.org/~euske/python/pdfminer/programming.html
https://www.cnblogs.com/jamespei/p/5339769.html
https://blog.csdn.net/u011389474/article/details/60139786
https://blog.csdn.net/u010983763/article/details/78654651
https://blog.csdn.net/zyc121561/article/details/77879831
https://blog.csdn.net/zyc121561/article/details/77877912?locationNum=7&fps=1
聲明個人博客同步更新,PC閱讀請訪問(https://www.cnblogs.com/baiboy/p/pybnc1.html)。本文原創,轉載必須註明出處: 數據分析:基於Python的自定義文件格式轉換系統.