巧用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 name/value

Description

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 win32print
import 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).handle
tasks=win32print.EnumJobs(handle,0, -1, 1 )
for task in tasks:
    taskName=task["pDocument"]

由於打印任務是動態增減的,每次得到的tasks可能都不同,且由於打印機可能有很多人共同使用,不能保證某個用戶的某次打印任務一定會出現在打印隊列的最上方。所以要盡可能拿到所有的任務;

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

至此,這個項目中的難點都逐一有了解決方案,希望小爬以上的思路,對喜歡自動化的你,能有所借鑒~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM