codesoft指定打印机打印_巧用win32print来控制windows系统打印机并推送打印任务
小爬最近接到的一個需求是:將windows系統下的打印任務批量有序傳輸給網絡打印機,實現批量有序打印。
????用戶先從公司的OA(B/S模式)系統下 打印指定內容的表單以及表單中的附件內容。這個問題可以這樣分解:
1、抓包,得到OA對應的任務接口,然后利用python requests模擬post請求,獲取所有的表單的URL并進行必要的去重處理;
2、打印OA表單的過程,需要瀏覽器在前臺,這個時候可以結合selenium的driver.get(url)方法,打開每一個表單,同時解析網頁內容,拿到所有附件的相關信息(名稱、后綴、下載地址),利用requests再度保存這些附件至本地;
3、打開表單后,利用 win32api.keybd_event,模擬鍵盤快捷鍵“Ctrl + Shift + P”調出系統的打印窗口;
4、選中“PDF打印機”,需要電腦中有“Microsoft Print to Pdf”或者“Foxit Reader PDF Printer”等;
5、利用pywin32中的相關方法,驅動打印過程,將每個OA表單(網頁)打印成PDF文件并格式化命名&存儲,與前面的附件內容存儲到同一個文件夾;
6、附件文件和OA生成的PDF文件均格式化存儲,用OA單號作為文件名的一部分,將兩者關聯起來;
7、將本地對應文件夾的所有內容有序推送給打印機,指定打印機為某一臺網絡打印機。同時要確保打印過程中,不亂序;
針對步驟3,可以自定義函數來實現:
#鍵盤按下def?key_down(keyname):
????win32api.keybd_event(vk_code[keyname],0,0,0)
#鍵盤抬起
def?key_up(key_name):
????win32api.keybd_event(vk_code[key_name],0,win32con.KEYEVENTF_KEYUP,0)
#按鍵組合操作
def?simulate_three_key(firstkey,sencondkey,lastkey):
????key_down(firstkey)
????key_down(sencondkey)
????key_down(lastkey)
????key_up(lastkey)
????key_up(sencondkey)
????key_up(firstkey)
#按鍵組合操作
def?simulate_two_key(firstkey,sencondkey):
????key_down(firstkey)
????key_down(sencondkey)
????key_up(sencondkey)
????key_up(firstkey)
????然后利用 simulate_three_key('ctrl',"shift",'p') 即可呼出系統的默認打印窗口:
那么步驟4,也就是上圖的打印窗口,如何選中某一個打印機呢?直接利用win32gui.SendMessage
????來選中某個打印機是非常困難的。一種可行的方法是,利用pywin32下的win32print模塊,也就是本文的重點。
比如,用下面的代碼可以遍歷并獲取到當前計算機的所有打印機信息:
for?it?in?win32print.EnumPrinters(6):????print(it[1])
????我們甚至可以知道某臺打印機的當前狀態,假定某臺打印機名為printerName,則可以這樣獲取打印機狀態:
hPrinter?=?win32print.OpenPrinter?(printerName)dic?=?hex(win32print.GetPrinter(hPrinter,2)['Status'])
if?dic[-2]=="8":
????print("The?printer?is?offline.")
if?dic[-5]=="4":
???print("The?printer?is?out?of?toner.")
elif?dic[-5]=="2":
???print("The?printer?is?low?on?toner.")
PRINTER_STATUS_BUSY 0x00000200 | The printer is busy. |
PRINTER_STATUS_DOOR_OPEN 0x00400000 | The printer door is open. |
PRINTER_STATUS_ERROR 0x00000002 | The printer is in an error state. |
PRINTER_STATUS_INITIALIZING 0x00008000 | The printer is initializing. |
PRINTER_STATUS_IO_ACTIVE 0x00000100 | The printer is in an active input or output state. |
PRINTER_STATUS_MANUAL_FEED 0x00000020 | The printer is in a manual feed state. |
PRINTER_STATUS_NOT_AVAILABLE 0x00001000 | The printer is not available for printing. |
PRINTER_STATUS_NO_TONER 0x00040000 | The printer is out of toner. |
PRINTER_STATUS_OFFLINE 0x00000080 | The printer is offline. |
PRINTER_STATUS_OUTPUT_BIN_FULL 0x00000800 | The printer's output bin is full. |
PRINTER_STATUS_OUT_OF_MEMORY 0x00200000 | The printer has run out of memory. |
PRINTER_STATUS_PAGE_PUNT 0x00080000 | The printer cannot print the current page. |
PRINTER_STATUS_PAPER_JAM 0x00000008 | Paper is stuck in the printer. |
PRINTER_STATUS_PAPER_OUT 0x00000010 | The printer is out of paper. |
PRINTER_STATUS_PAPER_PROBLEM 0x00000040 | The printer has an unspecified paper problem. |
PRINTER_STATUS_PAUSED 0x00000001 | The printer is paused. |
PRINTER_STATUS_PENDING_DELETION 0x00000004 | The printer is being deleted as a result of a client's call to?RpcDeletePrinter. No new jobs can be submitted on existing printer objects for that printer. |
PRINTER_STATUS_POWER_SAVE 0x01000000 | The printer is in power-save mode.<182> |
PRINTER_STATUS_PRINTING 0x00000400 | The printer is printing. |
PRINTER_STATUS_PROCESSING 0x00004000 | The printer is processing a?print job. |
PRINTER_STATUS_SERVER_OFFLINE 0x02000000 | The printer is offline.<183> |
PRINTER_STATUS_SERVER_UNKNOWN 0x00800000 | The printer status is unknown.<184> |
PRINTER_STATUS_TONER_LOW 0x00020000 | The printer is low on toner. |
PRINTER_STATUS_USER_INTERVENTION 0x00100000 | The printer has an error that requires the user to do something. |
PRINTER_STATUS_WAITING 0x00002000 | The printer is waiting. |
PRINTER_STATUS_WARMING_UP 0x00010000 | The printer is warming up. |
????????更多的打印機接口信息,可查詢微軟的開發文檔:https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/1625e9d9-29e4-48f4-b83d-3bd0fdaea787?redirectedfrom=MSDN
我們也可以得到當前默認的打印機,設置默認打印機:
currentPrinter=win32print.GetDefaultPrinterW()win32print.SetDefaultPrinterW(printer)
????????我們利用上面兩個函數,可以先得到系統當前的打印機,用變量存儲后,再設置默認打印機至 PDF打印機,待執行完所有任務后,再設置默認打印機為用戶一開始的默認打印機,整個過程用戶不需要更多的干預;
重點說步驟7:我們需要以OA表單+附件的形式,逐一給打印機分配任務,且不能亂序:
如果附件是圖片性質,我們可以結合Pillow庫來處理,示例代碼如下:
import?win32printimport?win32ui
from?PIL?import?Image,?ImageWin
#?Constants?for?GetDeviceCaps
#
#
#?HORZRES?/?VERTRES?=?printable?area
#
HORZRES?=?8
VERTRES?=?10
#
#?LOGPIXELS?=?dots?per?inch
#
LOGPIXELSX?=?88
LOGPIXELSY?=?90
#
#?PHYSICALWIDTH/HEIGHT?=?total?area
#
PHYSICALWIDTH?=?110
PHYSICALHEIGHT?=?111
#
#?PHYSICALOFFSETX/Y?=?left?/?top?margin
#
PHYSICALOFFSETX?=?112
PHYSICALOFFSETY?=?113
def?print_image(file_name):
?
????printer_name?=?win32print.GetDefaultPrinterW()?#?獲得默認打印機
????
????#
????#?You?can?only?write?a?Device-independent?bitmap
????#?directly?to?a?Windows?device?context;?therefore
????#?we?need?(for?ease)?to?use?the?Python?Imaging
????#?Library?to?manipulate?the?image.
????#
????#?Create?a?device?context?from?a?named?printer
????#?and?assess?the?printable?size?of?the?paper.
????#
????hDC?=?win32ui.CreateDC?()
????hDC.CreatePrinterDC?(printer_name)
????printable_area?=?hDC.GetDeviceCaps?(HORZRES),?hDC.GetDeviceCaps?(VERTRES)
????printer_size?=?hDC.GetDeviceCaps?(PHYSICALWIDTH),?hDC.GetDeviceCaps?(PHYSICALHEIGHT)
????printer_margins?=?hDC.GetDeviceCaps?(PHYSICALOFFSETX),?hDC.GetDeviceCaps?(PHYSICALOFFSETY)
????
????#
????#?Open?the?image,?rotate?it?if?it's?wider?than
????#?it?is?high,?and?work?out?how?much?to?multiply
????#?each?pixel?by?to?get?it?as?big?as?possible?on
????#?the?page?without?distorting.
????#
????bmp?=?Image.open?(file_name)
????#?bmp?=?bmp.rotate?(90)
????#?bmp.save("test1.png")
????if?bmp.size[0]?>?bmp.size[1]:
????????#?bmp?=?bmp.rotate?(90)
????????bmp=bmp.transpose(Image.ROTATE_90)
????
????ratios?=?[1.0?*?printable_area[0]?/?bmp.size[0],?1.0?*?printable_area[1]?/?bmp.size[1]]
????scale?=?min?(ratios)*0.85?#這個0.85的系數是不希望圖片被打印太大,缺少margin,不方便文檔的裝訂
????file_name=file_name.split("\\")[-1]?#這一步是為了提取fullpath中的filename部分
????
????#
????#?Start?the?print?job,?and?draw?the?bitmap?to
????#?the?printer?device?at?the?scaled?size.
????#
????hDC.StartDoc?(file_name)
????hDC.StartPage?()
????
????dib?=?ImageWin.Dib?(bmp)
????scaled_width,?scaled_height?=?[int?(scale?*?i)?for?i?in?bmp.size]
????x1?=?int?((printer_size[0]?-?scaled_width)?/?2)
????y1?=?int?((printer_size[1]?-?scaled_height)?/?2)
????x2?=?x1?+?scaled_width
????y2?=?y1?+?scaled_height
????dib.draw?(hDC.GetHandleOutput?(),?(x1,?y1,?x2,?y2))
????
????hDC.EndPage?()
????hDC.EndDoc?()
????hDC.DeleteDC?()
????????需要強調的是,如果我們對圖片進行后臺旋轉90度時,一定要用transpose(Image.ROTATE_90),不要使用 rotate (90),否則打印的圖片很有可能顯示不完整,且有黑邊;
具體的transpose用法見Pillow官網文檔:
????如果我們要打印的任務是PDF或者其他office類型的文檔,可以利用win32api.ShellExecute方法,示例如下:
def?printer_loading(filename):????#?open?(filename,?"r")
????currentPrinter=win32print.GetDefaultPrinterW()
????win32api.ShellExecute?(0,"print",filename,'/d:"%s"'?%?currentPrinter,".",0)
該方法有一個缺陷,win32api.ShellExecute 會在指令發出后,立即返回值,而不是等打印任務真正傳輸到打印機后再返回。這就意味著,附件中的圖片用win32ui的方法走后臺已經傳輸給打印機,而PDF等其他文件可能還沒及時發送給打印機,造成打印任務亂序。
????????可行的解決方法是,利用win32print.EnumJobs,定時獲取打印機當前的任務隊列,確保隊列中出現剛推送的任務后,再來推送下一個打印任務。示例如下:
handle?=?win32print.OpenPrinter(printer_name).handletasks=win32print.EnumJobs(handle,0,?-1,?1)
for?task?in?tasks:
????taskName=task["pDocument"]
????????由于打印任務是動態增減的,每次得到的tasks可能都不同,且由于打印機可能有很多人共同使用,不能保證某個用戶的某次打印任務一定會出現在打印隊列的最上方。所以要盡可能拿到所有的任務;
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
????????至此,這個項目中的難點都逐一有了解決方案,希望小爬以上的思路,對喜歡自動化的你,能有所借鑒~~
總結
以上是生活随笔為你收集整理的codesoft指定打印机打印_巧用win32print来控制windows系统打印机并推送打印任务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php树形数据结构是什么,数据结构 之
- 下一篇: java信息管理系统总结_java实现科