利用Python實現某OA系統的自動定位功能
本文介紹了筆者通過python程式實現某OA系統自動考勤打卡功能及相關邏輯原理的解析。
Github: https://github.com/cahi1l1yn/eChecker
需求分析
疫情期間,筆者所在公司使用某OA系統的考勤功能代替原來的刷臉考勤,結果導致很多人經常忘記打卡,於是筆者尋思著能不能寫個程式實現自動考勤,希望實現的主要功能是:指定使用者名稱密碼登入和指定時間簽到簽退,擴充套件功能是:自定義簽到和簽退的IP或定位地址。
系統邏輯分析
為了通過python實現上述功能,首先需要人工訪問系統進行相關的操作,並抓包分析請求和返回資料,弄清邏輯原理,下面介紹分析過程:
登入
訪問OA系統登入頁面,點選輸入登入資訊後擷取登入資料包,分析發現登入介面除了驗證使用者名稱和密碼外,還會驗證下圖紅框所示的cookie和token引數。因此我們需要找到這兩個引數值從哪裡獲取。
重新訪問登入頁面並抓取返回包,首先從返回包頭部看到了JSESSIONID引數,而另一個lt引數則在返回頁面的原始碼中。
弄清楚這兩個引數的來源後,我們重新回到登入頁面提交登入請求,獲取並記錄下會話cookie。
考勤
登入賬號後,進入考勤模組進行打工並擷取資料包,可以看出程式是通過向考勤介面提交引數值為CHECKIN和CHECKOUT的json字串以實現簽到和簽退。
另外可以看到請求包中攜帶了好幾個cookie引數,經過不斷的測試排除後,最終確定WEBID、JSESSIONID和ETEAMSID這三個為關鍵cookie,其餘幾個都可以忽略。
自定義考勤地址
上述測試過程是PC端的,由於其中並沒有涉及到地址的引數,因此轉到APP端進行測試。擷取APP端的考勤請求包,可以看到checkaddress引數就是考勤定位地址。
筆者嘗試在PC端的考勤請求引數中插入checkaddress,從響應包中可以看出已經成功使用該引數自定義考勤地址進行考勤,同時這裡如果再加入經緯度引數的話,即可高度模擬定位考勤。
值得關注的是,筆者分析發現當考勤請求攜帶了PC端UA時,服務端會將客戶端識別為PC端,此時不會處理checkaddress引數,簽到地址就是客戶端的真實IP地址。當考勤請求攜帶移動端UA或者pythonUA時,服務端會將客戶端識別為移動端且處理checkaddress引數,此時就可以實現自定義考勤地址,包括IP地址和地理位置。
邏輯梳理
通過上述操作後,筆者已經瞭解到登入介面和考勤介面的邏輯和請求形式,下面簡單梳理相關流程,這個流程也就是後續編寫程式主要的邏輯依據:
1.【使用者訪問登入頁面】
||
\/
2.【登入頁面返回一個cookie(JSESSIONID)和token(lt)】
||
\/
3.【使用者攜帶cookie像登入介面提交token、使用者名稱和密碼】
||
\/
4.【登入介面驗證成功後返回會話cookie(ETEAMSID\JSESSIONID\)】
||
\/
5.【使用者攜帶會話cookie向考勤介面提交簽到/簽退請求】
功能實現
這裡先回顧一下本程式實現需求是:指定使用者名稱密碼登入和指定時間簽到簽退。通過上述邏輯梳理,已經可以實現指定使用者和密碼登入已經簽到簽退,另外還需要實現的就是指定時間,下面我們加入指定時間相關的功能再次梳理python程式的主要功能邏輯:
1.【輸入使用者名稱、密碼、簽到簽退時間執行程式】
||
\/
2.【登入系統獲取會話cookie】
||
\/
3.【程式獲取本地時間】
||
\/
4.【程式比對本地時間和使用者設定時間】
||
\/
5.【在指定時間攜帶會話cookie進行考勤】
程式結構
梳理出程式主要功能邏輯後,開始定義函式分別實現上述主要功能,下面列出程式的主要函式結構:
def get_cookie(user,passwd):登入系統,獲取會話cookie,該函式實現了[邏輯梳理]中的第2-4步 def keep_session():維持會話cookie有效性,因cookie長期不活躍會失效,因此通過此函式訪問系統以維持cookie,如果cookie已經失效,則會呼叫get_cookie函式重新登入獲取cookie def check_in():簽到模組,攜帶cookie向考勤介面提交CHECKIN def check_out():簽退模組,攜帶cookie向考勤介面提交CHECKOUT def get_position():定位模組,根據使用者輸入的地理位置獲取經緯度 def check_time():獲取本地時間並於使用者設定時間作比對,觸發考勤模組和會話維持模組 def main():程式入口函式,獲取使用者輸入
程式碼解析
通過上面的介紹,我們已經大概瞭解整個程式的執行邏輯,下面對部分關鍵程式碼進行解析(部分常規程式碼有省略):
def get_cookie(user,passwd): ........... token = re.search(r'LT\S+cn',html).group() #urllib訪問登入頁面後,從頁面中獲取lt引數值,即token pcookie = re.search(r'JSESSIONID=\S+',str(pres.info().headers)).group() #訪問登入頁面後,從返回包頭部中獲取cookie,後續提交登入請求時需要攜帶該cookie data ='lt='+token+'&execution=e1.2&j_pcClient=&_eventId=submit&isApplyed=false®isterSourceUrl=®isterSource=®isterDataSource=&username='+user+'&password='+passwd #組合token和使用者輸入的登入資訊,用於組成登入請求 req = urllib2.Request(lurl) cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) #通過cookiejar記錄登入成功後頁面返回的會話cookie opener.addheaders = [('Cookie',pcookie)] #在登入請求包頭中加入一開始獲取到的cookie try: res = opener.open(lurl,data=data,timeout=10) except urllib2.URLError: print '[ERROR]Urlllib error,retry later' try: cookie = re.search(r'ETEAMSID=\w+',str(cj)).group()+';'+re.search(r'JSESSIONID=\w+',str(cj)).group()+';'+re.search(r'WEBID=\w+',str(cj)).group() #到這一步已經登入成功,cookiejar已經記錄了會話cookie,記錄形式是這樣的: <CookieJar[<Cookie BIDUPSID=B681378758CB3586029EBFFFF16FBDE2 for .baidu.com/>,<Cookie PSTM=1532404690 for .baidu.com/>,<Cookie BD_NOT_HTTPS=1 for www.baidu.com/>]> 因此這裡利用正則匹配出我們所需要的3個cookie值 print '[INFO]Login succeed,your cookie is:'+cookie ...........
def check_in(): ............ req = urllib2.Request(curl) req.add_header("Cookie",cookie) req.add_header("Content-Type","application/json") if stat == '0': data = json.dumps({"type":"CHECKOUT","checkAddress":addr,"longitude":longi,'latitude':lati}) #當用戶自定義了考勤地址時,且成功獲取到經緯度資訊時,提交的請求中加入了地理位置和經緯度引數,服務端預設將urllib的UA識別為移動端,故會記錄使用者提交的地理資訊,完美模擬定位考勤效果 elif stat == '1': data = json.dumps({"type":"CHECKOUT","checkAddress":addr}) #當用戶自定義了考勤地址時,但未成功獲取到經緯度資訊時,提交的請求中只加入地理位 elif stat =='2': req.add_header('User-Agent',ua) data = json.dumps({"type":"CHECKOUT"}) #當用戶未自定義考勤地址時,提交的請求按PC端原始格式,且此處需要加入自定義的PC端UA,否則服務端會將簽到地址記錄為空值 try: res = urllib2.urlopen(req,timeout=5).read() smsg = res.find('簽到成功') fmsg = res.find('簽到失敗') if smsg > -1: print '[INFO]'+time.strftime('%Y-%m-%d_%H:%M',time.localtime())+' Checkin succeed' elif fmsg > -1: print '[WARNING]'+time.strftime('%Y-%m-%d_%H:%M',time.localtime())+' Checkin fail:'+res #以上程式碼通過在返回報文中查詢成功和失敗的字元,作為考勤是否成功的判斷依據,並輸出到終端提示使用者 ..........
def check_time(): while True: ltime = time.strftime('%H:%M',time.localtime()).lstrip('0') day = time.strftime('%a',time.localtime()) #獲取當前的時間,lstrip去0是為了時針為0-9的個位數時進行格式統一 .......... if ltime == '4:30': keep_session() time.sleep(60) #由於會話cookie在一定時間後(貌似是十幾個小時)會失效,因此設定在凌晨呼叫keepsession()維持cookie elif ltime == intime.lstrip('0') and day not in ('Sat','Sun'): #比對本地時間與使用者輸入時間,且判斷是否週末 keep_session() #進行考勤前,再次檢驗cookie是否有效 rnd = random.randint(0,600) print '[INFO]Checkin after ' + str(int(rnd)/60) + ' Min ' + str(int(rnd)%60) + ' Sec' time.sleep(int(rnd)) check_in() #為了避免使用者設定一個時間後,程式每天都在同一時間點考勤,這裡結合sleep和random實現在使用者設定時間上正向浮動隨機時間進行考勤 time.sleep(60) ........ ........ check_time()
def get_position(addr): global longi global lati url = 'http://api.map.baidu.com/geocoding/v3/?address='+addr+'&output=json&ak='+api_key+'&callback=showLocation' html = urllib2.urlopen(url.encode('utf-8')).read() longi = re.search(r'lng":\d+.\d+',html).group().lstrip('lng":') lati = re.search(r'lat":\d+.\d+',html).group().lstrip('lat":') #呼叫百度地圖API獲取經緯度資訊,使用encode('utf-8')處理url可以避免中文亂碼問題(需要註冊APIKEY)
執行效果
總結
本文分享了筆者利用python編寫某OA系統自動考勤程式的過程,包括對系統邏輯的分析、程式結構的介紹和關鍵程式碼的解析等內容。
程式最終實現了使用者自定義考勤時間、地址,並自動根據地址獲取經緯度(如地址為IP地址則不獲取),每天在指定時間以上述自定義資訊進行考勤。
注:考勤地址可自定義的漏洞已經上報。
到此這篇關於利用Python實現某OA系統的自動定位功能的文章就介紹到這了,更多相關python實現OA系統自動定位內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!