前段時(shí)間給同事做Python爬蟲(chóng)技術(shù)分享羊壹,為了分享的效果以及聽(tīng)眾的興趣,寫(xiě)了一個(gè)爬取考勤數(shù)據(jù)的小爬蟲(chóng)齐婴。內(nèi)容比較簡(jiǎn)單油猫,以下做一個(gè)梳理:
一、問(wèn)題分析
先梳理下查詢考勤的流程:
- 登錄公司內(nèi)網(wǎng)的系統(tǒng)
- 切換到考勤查詢頁(yè)面
- 根據(jù)年份尔店、月份查詢對(duì)于的考勤數(shù)據(jù)即可
分析以上流程眨攘,主要解決兩個(gè)核心問(wèn)題即可:a. 賬號(hào)登錄 b. 找到考勤數(shù)據(jù)查詢的接口
二、解決登錄問(wèn)題
公司的考勤數(shù)據(jù)嚣州、人力系統(tǒng)鲫售、會(huì)議室等都在同一個(gè)系統(tǒng)里面,由于之前已經(jīng)用Selenium寫(xiě)過(guò)搶會(huì)議室的腳本该肴,所以登錄情竹、驗(yàn)證碼等問(wèn)題算是已經(jīng)解決了。
但是多想一步匀哄,要是每次爬個(gè)考勤秦效、調(diào)試都要祭出Selenium也有點(diǎn)麻煩的,所以...能不能再優(yōu)化下呢涎嚼?
...
當(dāng)然是有的阱州!
既然可以用Selenium登錄,就能得到登錄后的cookie法梯。理論上說(shuō)苔货,只要在cookie的過(guò)期時(shí)間內(nèi)都是可以實(shí)現(xiàn)免登錄的犀概。??
所以需要實(shí)現(xiàn)的工作是:
1、用Selenium模擬登錄夜惭,登錄成功后將cookie保存到本地姻灶;
2、拋棄Selenium诈茧,用Requests庫(kù)产喉,帶上前面的cookie訪問(wèn)網(wǎng)站即可。然后就能隨心所欲爬取數(shù)據(jù)了敢会。
說(shuō)干就干曾沈,以下是獲取登錄狀態(tài)cookie的實(shí)現(xiàn)代碼:
import time, os, json, requests,sqlite3
from selenium import webdriver
from time import sleep
from lib.mtHelper import MtHelper #自己封裝的關(guān)于驗(yàn)證碼識(shí)別、登錄等函數(shù)
from requests.cookies import RequestsCookieJar
#用selenium登錄系統(tǒng)走触,保存cookie到本地json文件晦譬。
def login_savecookie(user, password):
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(5)
mtHelper = MtHelper(driver)
driver.get('http://****.com')
mtHelper.login_auoto(user, password, 15)#之前封裝過(guò)的登錄函數(shù),最多嘗試15次驗(yàn)證碼識(shí)別
sleep(1)
dict_cookie = driver.get_cookies()#登錄成功后獲取當(dāng)前的cookie
json_cookie = json.dumps(dict_cookie)
with open(path + "\cookies.json", "w+") as f:
f.write(json_cookie)
driver.quit()
#讀取本地json文件獲取cookie互广,用于requests
def parse_cookie():
jar = RequestsCookieJar()
with open(path + "\cookies.json", "r") as f:
json_cookies = json.load(f)
for cookie in json_cookies:
jar.set(cookie['name'], cookie['value'])
return jar
所以從理論上說(shuō)敛腌,在cookie過(guò)期時(shí)間以內(nèi),只用執(zhí)行一次Selenium登錄操作即可惫皱。之后的操作直接讀取本地的cookie數(shù)據(jù)像樊,攜帶它進(jìn)行request請(qǐng)求即可。這樣能大大減少調(diào)試旅敷、運(yùn)行的成本生棍。
三、考勤數(shù)據(jù)查詢接口
按照以往的經(jīng)驗(yàn)媳谁,凡是先按F12涂滴,觀察一下請(qǐng)求的內(nèi)容再說(shuō)。
先進(jìn)入考勤查詢系統(tǒng)晴音,調(diào)出控制臺(tái)后柔纵,用鼠標(biāo)點(diǎn)擊了一下2018年3月,立馬能看到以下請(qǐng)求(沒(méi)有截圖锤躁,就手敲了一遍搁料,實(shí)際數(shù)據(jù)做過(guò)隱私處理):
#post請(qǐng)求地址:
http://****/EMPL/s/WEBLIB_GP_PAY.ISCRIPT2.FieldFormula.IScript_getPayAbsenceList?MONTHCD=2018-3&EMPLID=111111
#參數(shù):
MONTHCD:2018-3
EMPLID:111111
#返回值(截取部分),Json格式:
"{"code":"success","list_data":
[{"ATT_RESULT_COMMENT":"無(wú)請(qǐng)休假","ATT_END_TIME":"19:29:31","ATT_DATE":"2018-03-01","ATT_BGN_TIME":"08:58:40","EMPLID":"111111","DAY_OFWEEK":"星期四","ATT_RESULT":"無(wú)異常"},
{"ATT_RESULT_COMMENT":"無(wú)請(qǐng)休假","ATT_END_TIME":"18:57:48","ATT_DATE":"2018-03-02","ATT_BGN_TIME":"09:07:43","EMPLID":"111111","DAY_OFWEEK":"星期五","ATT_RESULT":"無(wú)異常"}]}"
根據(jù)以上內(nèi)容不難推測(cè)系羞,要想獲取考勤數(shù)據(jù)郭计,只需找到該post請(qǐng)求的參數(shù)規(guī)律即可。至于返回值椒振,明顯是按照工作日排序的昭伸,只需解析該Json即可得到想要的數(shù)據(jù)。
請(qǐng)求參數(shù)分析:
MONTHCD不用說(shuō)澎迎,肯定是指查詢考勤的年月份勋乾;至于EMPLID宋下,雖然不知道具體是什么嗡善,但該變量名明顯是類似員工工號(hào)的東西辑莫,應(yīng)該是唯一的。再多嘗試了幾次罩引,果不其然各吨,它是不變的。
搞定T怼=已选!
動(dòng)手來(lái)實(shí)現(xiàn)它吧:
#獲取某月的考勤數(shù)據(jù)
#month為需要獲取的月份剔桨,employeeID為員工工號(hào)屉更,jar為cookie數(shù)據(jù)。也就是上例中從json文件中獲取的cookie信息
def request_and_get_json(month = "2018-3", employeeID = "111111", jar = None):
url = "http://***/EMPLOYEE/EMPL/s/WEBLIB_GP_PAY.ISCRIPT2.FieldFormula.IScript_getPayAbsenceList?MONTHCD=%s&EMPLID=%s" % (month, employeeID)
print u"正在獲取%s的數(shù)據(jù)!"%month
response = requests.get(url, cookies = jar)
text = response.text
js = json.dumps(text,encoding='utf-8',ensure_ascii=False)
js = json.loads(js)
return js
三洒缀、數(shù)據(jù)存儲(chǔ)
前面已經(jīng)找到查詢接口的規(guī)律了瑰谜,所以用個(gè)循環(huán)遍歷就能獲取任意時(shí)間段的考勤數(shù)據(jù)了。但是僅停留于此是不夠的树绩,接下來(lái)要考慮下數(shù)據(jù)存儲(chǔ)萨脑,只有存儲(chǔ)為合理的數(shù)據(jù)結(jié)構(gòu)后才方便后面的數(shù)據(jù)分析。
數(shù)據(jù)存儲(chǔ)的方式很多饺饭,比如針對(duì)少量數(shù)據(jù)可以存為本地Json渤早、txt、Excel等文件瘫俊,數(shù)據(jù)量大也可以考慮數(shù)據(jù)庫(kù)鹊杖。
此處的考勤數(shù)據(jù)不多,不到1000條扛芽。為了便于數(shù)據(jù)的篩選骂蓖,以及減少環(huán)境搭建成本,采用sqlite3存儲(chǔ)胸哥。
先使用pip install sqlite3
命令安裝sqlite3庫(kù)涯竟,接下來(lái)就是實(shí)現(xiàn):
#保存數(shù)據(jù)到數(shù)據(jù)庫(kù)
#sql格式:"insert into info(date, week, emplid, beginTime, endTime, comment, result) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s')" % (date, week, emplid, beginTime, endTime, comment, result)
def save_to_db(sql):
conn = sqlite3.connect(path + '\db\clockData.db')
cursor = conn.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS info (date text(20) primary key, week text(20), emplid text(20), beginTime text(20), endTime text(20), comment text(20), result text(20))')
cursor.execute(sql)
cursor.close()
conn.commit()
conn.close()
至此,將以上部分串聯(lián)起來(lái)空厌,就能爬取考勤數(shù)據(jù)了庐船!
下面就看看爬取的成果:
四、數(shù)據(jù)分析
當(dāng)然嘲更,數(shù)據(jù)分析這個(gè)范疇實(shí)在是太大了筐钟,在此只做個(gè)簡(jiǎn)單的數(shù)據(jù)統(tǒng)計(jì)(常用的sql查詢命令),用于娛樂(lè)??赋朦,順便模仿一波網(wǎng)易云的情懷文案:
不說(shuō)了篓冲,我要找老板加薪去李破!??????