1. 程式人生 > 實用技巧 >Python自動化指令碼——涉及彈窗,自動登入,截圖,獲取使用者許可權,打包程式等

Python自動化指令碼——涉及彈窗,自動登入,截圖,獲取使用者許可權,打包程式等

2020暑期XX銀行實習-一個自動化指令碼

很多人學習python,不知道從何學起。
很多人學習python,掌握了基本語法過後,不知道在哪裡尋找案例上手。
很多已經做案例的人,卻不知道如何去學習更加高深的知識。
那麼針對這三類人,我給大家提供一個好的學習平臺,免費領取視訊教程,電子書籍,以及課程的原始碼!
QQ群:101677771

實習背景

有幸在2020年進行中國某銀行金融科技中心進行時長一個月的實習,在金科部的開發工作主要是幫助開發了一款自動化的程式,可以實現自動登入網頁,然後進行截圖,在此工程中涉及到不少功能和技巧,特此寫下記錄。可能敘述和程式碼有錯,歡迎大家一起交流。

主功能介紹

整個程式的流程,執行後彈出對話方塊填寫查詢的關鍵字,後續用於網頁自動搜尋到達指定網頁,隨後登入網頁並自動執行至最後的指定網頁,截圖指定內容部分儲存至資料夾後關閉。

獲取使用者許可權和禁用滑鼠鍵盤

在指令碼自動執行的時候,防止使用者誤輸入和誤操作,需要禁止使用滑鼠和鍵盤。利用一個函式ShellExecute,具體細節可看ShellExecuteA function。這裡貼出一段程式碼,直接使用即可。

from __future__ import print_function
import ctypes, sys

if windll.shell32.IsUserAnAdmin():
    //執行的程式碼
else:
    if sys.version_info[0] == 3:
    	ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
    	exit()//因為即使沒有許可權也會執行,所以新增exit,使得沒有許可權時會退出程式。
    else:
    	//如果是python 2.x
        ctypes.windll.shell32.ShellExecuteW(None, u"runas", unicode(sys.executable), unicode(__file__), None, 1)
        exit()

值得注意的是,如果沒有管理員執行,這裡其實運行了兩次,第一次沒有許可權,第二次獲得許可權後執行程式碼,所以我添加了一個退出的函式exit()。這是一個很有必要改進的點,日後有機會看看能否完善。

參考:https://blog.csdn.net/qq_17550379/article/details/79006655

對話方塊

通過使用pyautogui庫來實現從對話方塊獲取引數和警告對話方塊功能。

import pyautogui as pag

# 顯示一個簡單的帶文字和OK按鈕的訊息彈窗。使用者點選後返回button的文字。
pag.alert(text='對話方塊內部內容', title='對話方塊標題', button='按鈕')
//例如:
a = pag.alert(text='要開始程式麼?', title='請求框', button='OK')
print(a) # 輸出結果為返回的OK

# 顯示一個簡單的帶文字、OK和Cancel按鈕的訊息彈窗,使用者點選後返回被點選button的文字,支援自定義數字、文字的列表。
pag.confirm(text='對話方塊內部內容', title='對話方塊標題', buttons=['OK', 'Cancel']) # OK和Cancel按鈕的訊息彈窗,其中按鈕可以使用數字來設定個數
pag.confirm(text='', title='', buttons=range(10)) # 10個按鍵0-9的訊息彈窗
//例如:
b = pag.confirm(text='', title='', buttons=range(10))
print(b) # 輸出結果為點選的數字

# 可以輸入的訊息彈窗,帶OK和Cancel按鈕。使用者點選OK按鈕返回輸入的文字,點選Cancel按鈕返回None。可以設定一個預設輸入。
pag.prompt(text='對話方塊內部內容', title='對話方塊標題', default='預設輸入')

# 樣式同prompt(),用於輸入密碼,訊息用*表示。帶OK和Cancel按鈕。使用者點選OK按鈕返回輸入的文字,點選Cancel按鈕返回None。
pag.password(text='對話方塊內部內容', title='對話方塊標題', default='', mask='*')

主要使用了返回輸入的對話方塊和警告框,從返回框獲得ID後,用於後續的搜尋,當自動執行過程中出錯時,警告框可以提醒使用者。

參考:https://www.jb51.net/article/183926.htm

使用谷歌瀏覽器chrome自動登入網頁

使用谷歌瀏覽器的驅動進行自動化登入網頁是一個常用的方法,這也是我自己以前使用的方法,但是這次登入的網頁屬於銀行內部的內網,無法使用webdriver定位網頁元素,無論是使用哪種方式都不行,所以只是使用驅動啟到一個自動開啟網頁的功能,後續的使用者登入,自動搜尋等都是利用pyautogui的模擬滑鼠鍵盤輸入。

chromedriver

通常的網頁自動登入使用chromedriver即可,可以參考Python實現網站自動登入—傻瓜教程。也非常推薦這種方法,因為模擬滑鼠點選實在有些笨,只適合一些簡單的自動指令碼,但是適用面到可以說是很廣了。

模擬滑鼠點選以及鍵盤輸入

鍵盤輸入這個功能其實很多模組都有,這是比較需要的是滑鼠的點選,這是一個比較愚蠢的方法,但卻簡單粗暴加實用,當然,很不靈活。主要使用庫pyautogui

首先可以使用QQ或者微信的截圖功能,獲取螢幕上指定點的畫素點座標,記作cur_x,cur_y

import pyautogui
pyautogui.click(x=cur_x, y=cur_y, button='left')
//x,y是要點選的位置,預設是滑鼠當前位置
//button是要點選的按鍵,有三個可選值:‘left’, ‘middle’, ‘right’

輸入使用者名稱和密碼使用模擬鍵盤輸入即可,值得注意的是有幾個注意點:

  • 輸入前全選加刪除,也就是ctrl+a再del
  • 不要模擬一個一個字母輸入,很有可能受限於輸入法的問題,使用字串複製再貼上
import pyautogui as pag
   """全選內容並刪除"""
   pag.hotkey('ctrl','a')
   pag.press('delete')
  • 1
  • 2
  • 3
  • 4
import pyautogui as pag
import pyperclip
   """輸入內容。使用了剪下板,可以忽略輸入法問題!"""
   char = '使用者名稱或密碼'
   pyperclip.copy(char)
   pag.hotkey('ctrl','v')

其中我還使用該庫的locateOnScreen函式進行畫素匹配,判斷截圖是否已經達到了目標區域,和結束位置。有興趣可以點選連結python 捕捉和模擬滑鼠鍵盤操作,參考仍和第一部分一樣。

參考:https://www.jb51.net/article/183926.htm

截圖

截圖這裡有著太多方法,許多庫都提供了方法,其實如果是通常可正常爬取的網頁,推薦使用匯出PDF,而不是截圖,這樣可以得到整個網頁。截圖正常情況下只能得到當前螢幕的內容。但由於銀行內網的緣故,這裡採用了webdriver的截圖函式。

driver.save_screenshot("儲存的地址")
  • 1

但是上面的函式只能夠擷取當前頁面的圖片,我們需要擷取更多的圖片,長圖,網路上有現成的程式碼,可以實現分開擷取圖片最後拼接成長圖。因為時間有些久遠,忘記參考的連結,這裡貼出程式碼:

"""擷取長圖"""
   window_height = driver.get_window_size()['height']  # 視窗高度
   page_height = driver.execute_script('return document.documentElement.scrollHeight')  # 頁面高度
   driver.save_screenshot('分圖.png')
   if page_height > window_height:
       n = page_height // window_height  # 需要滾動的次數
       base_mat = np.atleast_2d(Image.open('1.png'))  # 開啟截圖並轉為二維矩陣

       for i in range(n):
           driver.execute_script(f'document.documentElement.scrollTop={window_height * (i + 1)};')
           time.sleep(.5)
           driver.save_screenshot(f'分圖_{i}.png')  # 儲存截圖
           mat = np.atleast_2d(Image.open(f'分圖_{i}.png'))  # 開啟截圖並轉為二維矩陣
           base_mat = np.append(base_mat, mat, axis=0)  # 拼接圖片的二維矩陣
       Image.fromarray(base_mat).save(char)

現在所使用的是利用的是,利用標誌點是否出現在螢幕裡,來擷取指定區域的所有內容,具體情況看程式碼。

所有程式碼

"""自動指令碼"""
"""登入網站並對流程截圖"""
"""基本實現功能版"""
""""為解決有些電腦執行過慢,新增一個讀取文字檔案,加入等待時間的係數time_k"""
import time
import os
from selenium import webdriver
import sys
from ctypes import *
from PIL import Image
import numpy as np
from win32 import win32api, win32gui, win32print
from win32.lib import win32con
from win32.win32api import GetSystemMetrics
import pyperclip

screen_w = GetSystemMetrics (0)
screen_h = GetSystemMetrics (1)
import pyautogui as pag
def main(screen_w , screen_h):
   #讀取時間引數
   try:
       time_k = Read_txt()
   except:
       time_k = 1

   # 判斷時間引數
   if time_k>5 or time_k<0.2:
       pag.alert(text='時間引數設定不正常,請設定在0.2~5之間', title='警告', button='OK')
       exit()

   #許可權獲取
   Inner()

   #使用者可以使用框輸入需求ID,例如:GZ20073002
   demand_id = pag.prompt(text='請輸入流程編號(請保證使用谷歌瀏覽器,且版本為83.0.4103相近)', title='查詢業務流程', default='')
   if demand_id == None:
       exit()
   #解析度判斷

   #提示框,用於截圖命名
   title = pag.prompt(text='請輸入標題,用於結果命名(可不輸)', title='標題名稱輸入', default='')
   if title == None:
       title = " "

   resolution_judgment(screen_w,screen_h)

   # 環境配置
   chromedriver = "C:\Program Files (x86)\Google\Chrome\Application"
   os.environ["webdriver.ie.driver"] = chromedriver

   driver = webdriver.Chrome()  # 選擇Chrome瀏覽器
   driver.get('網址')  # 開啟網站
   driver.maximize_window()  # 最大化谷歌瀏覽器


   time.sleep(2)
   try:
       #禁用滑鼠鍵盤
       windll.user32.BlockInput(True)

       time.sleep(1*time_k)

       #輸入使用者名稱和密碼
       username = ""  # 使用者名稱
       #password = ""  # 密碼
       password=Password()
       #滑鼠移動到使用者名稱
       Mouse_Click(1330,290)
       All_Del_Pag()
       Input_THING(username)


       #滑鼠移動到密碼
       Mouse_Click(1340,325)
       All_Del_Pag()
       Input_THING(password)

       Mouse_Click(1305,400)# 點選登入

       time.sleep(5*time_k)

       Mouse_Click(375,145)  # 點選個人主頁
       time.sleep(0.5*time_k)
       Mouse_Click(110,269)  # 點選我的任務
       time.sleep(0.5*time_k)
       Mouse_Click(90,335)  # 點選已辦任務
       time.sleep(4.5*time_k)

       #輸入編碼
       Mouse_Click(600,351)
       All_Del_Pag()
       Input_THING(demand_id)
       Mouse_Click(918,353)  # 點選確定
       time.sleep(2.5*time_k)

       Mouse_Click(1840,453)  # 點選檢視
       time.sleep(5*time_k)

       try:
           driver.switch_to.window(driver.window_handles[1])
       except:
           windll.user32.BlockInput(False)
           pag.alert(text='頁面不對!可能是網路原因\流程ID不存在\輸入法問題,請檢查後再嘗試', title='警告', button='OK')
           driver.close()
           exit()

       #抓取一個特徵,用於最後截圖定位已經到底部
       try:
           pag.scroll(-90000)
           time.sleep(.5*time_k)
           flag_img = pag.screenshot(region=(206,817,25,25))
           time.sleep(.5*time_k)
           pag.scroll(90000)
       except:
           windll.user32.BlockInput(False)
           pag.alert(text='未知錯誤,請聯絡開發者', title='警告', button='OK')
           driver.close()

       time.sleep(.5*time_k)
       Mouse_Click(231,299)  # 流程檢視
       time.sleep(.5*time_k)

       """開始截圖,不斷往下翻頁截圖,直到識別標誌特徵"""
       try:
           t = True
           i = 1
           while t:
               Html_Png(driver,'\\%s%d.png'%(title,i))
               if pag.locateOnScreen(flag_img):
                   t = False
               pag.scroll(-1000)
               i=i+1
       except:
           windll.user32.BlockInput(False)
           pag.alert(text='未知錯誤', title='警告', button='OK')
           driver.close()

       time.sleep(.5)
       windll.user32.BlockInput(False)
       #關閉驅動
       driver.close()

   except:
       windll.user32.BlockInput(False)
       driver.close()

def All_Del_Pag():
   #全選內容並刪除
   pag.hotkey('ctrl','a')
   pag.press('delete')

def Input_THING(char):
   """輸入內容。使用了剪下板,可以忽略輸入法問題!"""
   pyperclip.copy(char)
   pag.hotkey('ctrl','v')

def Mouse_Click(x,y):
   #移動並點選
   pag.moveTo(x,y)
   pag.click()

def Html_Png(driver,char):
   """截圖"""
   Png_root = Local_Adr_G(char)
   try:
       driver.save_screenshot(Png_root)
   except:
       print('截圖出錯')

def Html_LongPng(driver,char):
   #長截圖功能,建行網站無法使用,通常網站可以
   window_height = driver.get_window_size()['height']  # 視窗高度
   page_height = driver.execute_script('return document.documentElement.scrollHeight')  # 頁面高度
   driver.save_screenshot('分圖.png')
   if page_height > window_height:
       n = page_height // window_height  # 需要滾動的次數
       base_mat = np.atleast_2d(Image.open('1.png'))  # 開啟截圖並轉為二維矩陣

       for i in range(n):
           driver.execute_script(f'document.documentElement.scrollTop={window_height * (i + 1)};')
           time.sleep(.5)
           driver.save_screenshot(f'分圖_{i}.png')  # 儲存截圖
           mat = np.atleast_2d(Image.open(f'分圖_{i}.png'))  # 開啟截圖並轉為二維矩陣
           base_mat = np.append(base_mat, mat, axis=0)  # 拼接圖片的二維矩陣
       Image.fromarray(base_mat).save(char)

def Local_Adr(char):
   """獲取當前檔案路徑"""
   root = os.path.dirname(sys.argv[0])
   root = root + char
   return root

def Local_Adr_G(char):
   """獲取當前路徑下的的流程截圖檔案路徑"""
   root = os.path.dirname(sys.argv[0])+'\\流程截圖'
   root = root + char
   return root
   
def Inner():
   #獲取管理員許可權
   if windll.shell32.IsUserAnAdmin():
       return
   else:
       windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 0)
       sys.exit()

def get_real_resolution():
   """獲取真實的解析度"""
   hDC = win32gui.GetDC(0)
   # 橫向解析度
   w = win32print.GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
   # 縱向解析度
   h = win32print.GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)
   return w, h

def resolution_change(x,y):
   """修改解析度"""
   dm = win32api.EnumDisplaySettings(None, 0)
   dm.PelsWidth = x
   dm.PelsHeight = y
   dm.BitsPerPel = 32
   dm.DisplayFixedOutput = 0
   win32api.ChangeDisplaySettings(dm, 0)

def resolution_judgment(w,h):
   """解析度判斷"""
   real_resolution = get_real_resolution()
   screen_size = w,h
   screen_scale_rate = round(real_resolution[0] / screen_size[0], 2)
   if screen_size[0] == 1920:
       return
   else:
       if screen_scale_rate == 1:
           pag.alert(text='縮放比例已為100%,現在程式嘗試自動設定解析度,\n若沒有成功請手動嘗試設定960X540,並設定縮放比例為50%', title='說明', button='OK')
           resolution_change(1920,1080)
           exit()
       else:
           pag.alert(text='解析度有誤!!\n請點選左下角圖示-設定-顯示,設定解析度和縮放比例\n(1920X1080,100% 或 960X540,50%)', title='警告', button='OK')
           exit()

def Read_txt():
   """讀取檔案獲得時間引數"""
   path = os.path.dirname(sys.argv[0])+'\\time.txt'
   with open(path,"r") as f:
       data = f.readline()
       return float(data)

def Read_Password():
   path = os.path.dirname(sys.argv[0])+'\\reg.txt'
   with open(path,"r") as f:
       data = f.readline()
       return data    

def Password():
   pa=Read_Password()
   ch=""
   for i in range(0,len(pa),5):
       asc=int(str(int(pa[i:i+5])*11)[-3:])
       ch=ch+chr(asc)
   return ch


if __name__ == '__main__':
   main(screen_w , screen_h)