公眾號:軟測小生捷沸, 獲取更多精彩內(nèi)容偿乖。
前言:在最近的測試中遇到一個與PDF相關(guān)的測試需求缨硝,其中有一個過程是將PDF轉(zhuǎn)換成圖片腺律,然后對圖片進(jìn)行測試宜肉。
粗略的試了好幾種方式,其中語言嘗試了Python和Java翎碑,總體而言所找到的Python方式相對比Java更快一些谬返,更簡單一些。
下面首先分享一下Python將PDF轉(zhuǎn)換成圖片日杈,Java后續(xù)有時間在進(jìn)行分享遣铝。
需求:我需要先將PDF轉(zhuǎn)換成為PNG圖片,并截取圖片的一部分存儲,然后作為測試目標(biāo)進(jìn)行測試涨冀。
操作:
1填硕、PDF轉(zhuǎn)PNG圖片
2、對PNG圖片進(jìn)行指定區(qū)域截圖鹿鳖,在另存到指定文件夾下
針對截圖此處所找到的方法如上一篇文章:Python圖片裁剪的兩種方式——Pillow和OpenCV
1姻檀、PyMuPDF將PDF轉(zhuǎn)換成圖片
pip install PyMuPDF
import sys, fitz, os, datetime
def pyMuPDF_fitz(pdfPath, imagePath):
startTime_pdf2img = datetime.datetime.now()#開始時間
print("imagePath="+imagePath)
pdfDoc = fitz.open(pdfPath)
for pg in range(pdfDoc.pageCount):
page = pdfDoc[pg]
rotate = int(0)
# 每個尺寸的縮放系數(shù)為1.3,這將為我們生成分辨率提高2.6的圖像涝滴。
# 此處若是不做設(shè)置绣版,默認(rèn)圖片大小為:792X612, dpi=96
zoom_x = 1.33333333 #(1.33333333-->1056x816) (2-->1584x1224)
zoom_y = 1.33333333
mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
pix = page.getPixmap(matrix=mat, alpha=False)
if not os.path.exists(imagePath):#判斷存放圖片的文件夾是否存在
os.makedirs(imagePath) # 若圖片文件夾不存在就創(chuàng)建
pix.writePNG(imagePath+'/'+'images_%s.png' % pg)#將圖片寫入指定的文件夾內(nèi)
endTime_pdf2img = datetime.datetime.now()#結(jié)束時間
print('pdf2img時間=',(endTime_pdf2img - startTime_pdf2img).seconds)
if __name__ == "__main__":
pdfPath = '../path/demo.pdf'
imagePath = '../path/image'
pyMuPDF_fitz(pdfPath, imagePath)
PDF文檔頁數(shù)超過100頁的話需要十幾秒,因為先轉(zhuǎn)換成一整張1056X816的圖片歼疮,再對本地文件中的所有圖片進(jìn)行遍歷截圖杂抽,時間上比較慢,通過查看文檔發(fā)現(xiàn):
還可以在轉(zhuǎn)換的同時指定圖片的大小,對圖片指定區(qū)域進(jìn)行截取腋妙,這樣快很多默怨,一步到位,省去了二次截圖的過程骤素,前提使我們必須要知道想要截取哪一塊區(qū)域并保存
官方示例代碼如下:
#下面的這段代碼就是想要從一頁PDF的中心點為起點截取到右下角的區(qū)域匙睹,截取整張圖的1/4.
>>> mat = fitz.Matrix(2, 2) # 在每個方向縮放因子2
>>> rect = page.rect # 頁面的矩形
>>> mp = rect.tl + (rect.br - rect.tl) * 0.5 # 矩形的中心
>>> clip = fitz.Rect(mp, rect.br) # 我們想要的剪切區(qū)域
>>> pix = page.getPixmap(matrix = mat, clip = clip)
實際用到的例子是:
整張圖片導(dǎo)出之后是1056x816愚屁,但是我想要的是這張圖片最底部的部分1056x75,相當(dāng)于PDF文檔的頁腳部分痕檬。
import sys, fitz, os, datetime
def pyMuPDF_fitz(pdfPath, imagePath):
startTime_pdf2img = datetime.datetime.now()#開始時間
pdfDoc = fitz.open(pdfPath)
for pg in range(pdfDoc.pageCount):
page = pdfDoc[pg]
rotate = int(0)
# 每個尺寸的縮放系數(shù)為1.3霎槐,這將為我們生成分辨率提高2.6的圖像。
# 此處若是不做設(shè)置梦谜,默認(rèn)圖片大小為:792X612, dpi=96
zoom_x = 1.33333333 #(1.33333333-->1056x816) (2-->1584x1224)
zoom_y = 1.33333333
mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
pix = page.getPixmap(matrix=mat, alpha=False)
if not os.path.exists(imagePath):#判斷存放圖片的文件夾是否存在
os.makedirs(imagePath) # 若圖片文件夾不存在就創(chuàng)建
pix.writePNG(imagePath+'/'+'images_%s.png' % pg)#將圖片寫入指定的文件夾內(nèi)
endTime_pdf2img = datetime.datetime.now()#結(jié)束時間
print('pdf2img時間=',(endTime_pdf2img - startTime_pdf2img).seconds)
def pyMuPDF2_fitz(pdfPath, imagePath):
pdfDoc = fitz.open(pdfPath) # open document
for pg in range(pdfDoc.pageCount): # iterate through the pages
page = pdfDoc[pg]
rotate = int(0)
# 每個尺寸的縮放系數(shù)為1.3丘跌,這將為我們生成分辨率提高2.6的圖像
# 此處若是不做設(shè)置,默認(rèn)圖片大小為:792X612, dpi=96
zoom_x = 1.33333333 #(1.33333333-->1056x816) (2-->1584x1224)
zoom_y = 1.33333333
# 縮放系數(shù)1.3在每個維度 .preRotate(rotate)是執(zhí)行一個旋轉(zhuǎn)
mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
rect = page.rect # 頁面大小
mp = rect.tl + (rect.bl - (0,75/zoom_x)) # 矩形區(qū)域 56=75/1.3333
clip = fitz.Rect(mp, rect.br) # 想要截取的區(qū)域
pix = page.getPixmap(matrix=mat, alpha=False, clip=clip) # 將頁面轉(zhuǎn)換為圖像
if not os.path.exists(imagePath):
os.makedirs(imagePath)
pix.writePNG(imagePath+'/'+'psReport_%s.png' % pg)# store image as a PNG
if __name__ == "__main__":
pdfPath = '../path/demo.pdf'
imagePath = '../path/image'
#pyMuPDF_fitz(pdfPath, imagePath)#只是轉(zhuǎn)換圖片
pyMuPDF2_fitz(pdfPath, imagePath)#指定想要的區(qū)域轉(zhuǎn)換成圖片
當(dāng)然上面這種是綜合下來最快的唁桩,另外再介紹一種方法pdf2image
2闭树、pdf2image 將PDF轉(zhuǎn)換成圖片
pdf2image也是個包裝器,真正的轉(zhuǎn)換工具是poppler
GitHub地址:https://github.com/Belval/pdf2image 荒澡,上面也有相關(guān)的配置說明报辱。
1、安裝pdf2image: pip install pdf2image
2单山、Windows安裝配置poppler(這里只介紹Windows碍现,Mac和Linux去上面Github地址里面參考官網(wǎng))
Windows用戶必須為Windows安裝poppler (http://blog.alivate.com.au/poppler-windows/),然后將bin/文件夾添加到PATH(開始>輸入env>編輯系統(tǒng)環(huán)境變量>環(huán)境變量...>系統(tǒng)變量>Path)
注意這里配置之后需要重啟一下電腦才會生效米奸,不然會報如下錯誤:
ERROE:FileNotFoundError: [WinError 2] The system cannot find the file specified
During handling of the above exception, another exception occurred:
3昼接、pip install pillow (如果你還沒有安裝過的話)
from pdf2image import convert_from_path,convert_from_bytes
import tempfile
from pdf2image.exceptions import (
PDFInfoNotInstalledError,
PDFPageCountError,
PDFSyntaxError
)
def pdf2image2(pdfPath, imagePath, pageNum):
#方法一:
#convert_from_path('a.pdf', dpi=500, "output",fmt="JPEG",output_file="ok"
#,thread_count=4)
#這會將a.pdf轉(zhuǎn)換成在output文件夾下形如ok_線程id-頁碼.jpg的一些文件。
#若不指定thread_count則默認(rèn)為1悴晰,并且在文件名中顯示id. 這種轉(zhuǎn)換是直接寫入到磁盤上的慢睡,
#因此不會占用太多內(nèi)存。
#下面的寫法直接寫入到內(nèi)存,默認(rèn)是C:\Users\pppp\AppData\Local\Temp\生成的uuid4名字
images = convert_from_path(pdfPath, dpi=96)
for image in images:
if not os.path.exists(imagePath):
os.makedirs(imagePath)
image.save(imagePath+'/'+'psReport_%s.png' % images.index(image), 'PNG')
#方法二:
images = convert_from_bytes(open('/home/belval/example.pdf', 'rb').read())
for image in images:
if not os.path.exists(imagePath):
os.makedirs(imagePath)
image.save(imagePath+'/'+'psReport_%s.png' % images.index(image), 'PNG')
#方法三膨疏,也是最推薦的方法
with tempfile.TemporaryDirectory() as path:
images_from_path = convert_from_path(pdfPath, output_folder=path,
dpi=96)
for image in images_from_path:
if not os.path.exists(imagePath):
os.makedirs(imagePath)
image.save(imagePath+'/'
+'psReport_%s.png' % images_from_path.index(image), 'PNG')
print(images_from_path)
以下是參數(shù)定義:
convert_from_path(pdf_path, dpi=200, output_folder=None, first_page=None, last_page=None, fmt='ppm', thread_count=1, userpw=None, use_cropbox=False, strict=False, transparent=False, single_file=False, output_file=str(uuid.uuid4()), poppler_path=None)
convert_from_bytes(pdf_file, dpi=200, output_folder=None, first_page=None, last_page=None, fmt='ppm', thread_count=1, userpw=None, use_cropbox=False, strict=False, transparent=False, single_file=False, output_file=str(uuid.uuid4()), poppler_path=None)
pdf_path --> 要轉(zhuǎn)換的PDF文檔路徑
dpi -->DPI中的圖像質(zhì)量(默認(rèn)為200)一睁,Windows默認(rèn)為96dpi
output_folder --> 將生成的圖像寫入文件夾(而不是直接寫入內(nèi)存)若是path不做指定的話,path的默認(rèn)地址是:C:\Users\pppp\AppData\Local\Temp\生成的uuid4佃却。
first_page --> 從哪一頁開始轉(zhuǎn)換者吁,默認(rèn)是PDF的第一頁
last_page -->轉(zhuǎn)換到哪一頁,默認(rèn)是PDF的最后一頁
fmt --> 輸出圖像格式默認(rèn)格式是ppm饲帅,還可以設(shè)置為png和jpeg等
thread_count --> 允許生成多少個線程進(jìn)行處理复凳,一般不超過4個線程;
userpw --> PDF的密碼(若有密碼的話需要添加)
use_cropbox -->使用cropbox而不是mediabox
strict --> 參數(shù)允許您使用自定義類型PDFSyntaxError捕獲pdftoppm語法錯誤
transparent --> 參數(shù)允許生成沒有背景的圖像灶泵,而不是通常的白色圖像(為此需要pdftocairo)
single_file --> 使用pdftoppm / pdftocairo中的-singlefile選項
output_file --> 輸出文件名是什么
poppler_path --> 查找poppler二進(jìn)制文件的路徑育八,允許用戶使用poppler_path指定poppler的安裝路徑;默認(rèn)不指定的話需要將bin添加到系統(tǒng)PATH
pdf2image應(yīng)該也可以對指定區(qū)域進(jìn)行截取赦邻,暫時還沒詳細(xì)研究其方法髓棋,因為已經(jīng)找到更快的方法解決問題了,對比如下所示:
3、 比較PyMuPDF和pdf2image
以下是對一份75頁的PDF按声,輸出DPI=96的時間性能對比膳犹,pdf2image使用的是默認(rèn)線程數(shù),下面的對比并沒有設(shè)置多線程签则,使用多線程會快一點须床,當(dāng)線程數(shù)設(shè)為5的時候,速度是9秒渐裂。
可以看出使用pyMuPDF_Fitz明顯快一倍多豺旬,最終選取了這種方式。
4柒凉、Wand將PDF轉(zhuǎn)換成圖片
和pdf2image一樣族阅,wand都是包裝接口(bindings),而實際進(jìn)行轉(zhuǎn)換的工具是ImageMagick.
Wind官網(wǎng):http://docs.wand-py.org/en/0.5.6/
ImageMagick: https://imagemagick.org/script/download.php#windows
from wand.image import Image
filename="somefile.pdf"
with(Image(filename=filename, resolution=120)) as source:
images = source.sequence
pages = len(images)
for i in range(pages):
n = i + 1
newfilename = filename[:-4] + str(n) + '.jpeg'
Image(images[i]).save(filename=newfilename)
由于問題已經(jīng)解決膝捞,而且性能也還不錯,就沒有具體去研究Wind這種方式了绑警,感興趣的可以去看看。