快要春節(jié)了,老板發(fā)話:'提前完成工作可以提前回家';于是老貓每天加班加點(diǎn)趕進(jìn)度,估計(jì)提前1周回家;正當(dāng)老貓沉浸在幸福之時(shí),老板過來關(guān)心問我:'老貓,車票買了嗎,買不到晚幾天走吧,那會(huì)好買';忽然有種被算計(jì)的感覺!!
人無遠(yuǎn)慮必有近憂,車票是個(gè)大問題,老貓要買的車票這兩天放票;于是每天發(fā)動(dòng)同事幫我搶票,可是連續(xù)兩次都沒搶到;老貓有點(diǎn)慌了,買不到車票怎么辦?
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了淮北免費(fèi)建站歡迎大家使用!
那么多車票,為什么好幾個(gè)人搶連續(xù)兩天都搶不到;
老貓分析其中可能存在原因,分析過程如下:
1>老貓買的是比較緊張的車次,搶票人數(shù)遠(yuǎn)遠(yuǎn)大于出票數(shù)量;
2>每次搶票和網(wǎng)速,手速有一定關(guān)系;
3>雖然感覺每次手動(dòng)購票速度很快,但是購票人數(shù)多,手速快的也很多,對比而言也就不快了。
老貓又仔細(xì)分析了網(wǎng)頁購票過程:
1>登錄,驗(yàn)證碼與密碼登錄;
2>刷票,選擇始發(fā)站,終點(diǎn)站,日期,車次;
3>出票后點(diǎn)擊預(yù)購;
4>等待排隊(duì),選擇乘車人;
理論上大家刷票過程都是一樣的,但是幾個(gè)因素會(huì)影響我們購票結(jié)果:
1>第一步登錄,這個(gè)沒有問題,老貓和大家都會(huì)提前登錄;
2>第二步:先看個(gè)示意圖:
老貓?zhí)崆斑x擇好車次等信息,到出票時(shí)刻點(diǎn)擊刷票;但是網(wǎng)速與瀏覽器渲染頁面速度,可能會(huì)影響下一步操作;
3>刷票之后,如果出現(xiàn)購買車次,馬上點(diǎn)擊預(yù)購,這里會(huì)和自己手速有關(guān),幾百毫秒過去了,老貓可能已經(jīng)排在幾百人之后了;
4>點(diǎn)擊預(yù)購,出現(xiàn)下面頁面:
這里考驗(yàn)手速:點(diǎn)擊購票人與訂單提交,然后憑天由命吧。
結(jié)合上面分析:搶熱點(diǎn)車次,真有點(diǎn)撞大運(yùn)的感覺。如何解決問題呢?
1>使用第三方軟件代購或者購買加油包;
2>拼車回家;
出去安全考慮,還是應(yīng)該選擇第一個(gè)方式。但是老貓有點(diǎn)好奇,為什么第三方軟件或者加油包能夠購買成功?老貓猜測可能原因:
老貓認(rèn)為每個(gè)賬號就是一個(gè)購票者,這些賬號由腳本控制,到時(shí)間點(diǎn)開始搶票,腳本速度肯定要比人的速度快,所有購買成功幾率要大于認(rèn)為操作。以上部分構(gòu)成純屬瞎猜,如有雷同,純屬巧合。
這樣來看,老貓除了和幾萬個(gè)人競爭,還要和第三方軟件競爭,所以熱點(diǎn)車次的車票更難買到了。這樣大家也就理解為什么第三方軟件購買成功幾率更大。
為了驗(yàn)證這個(gè)猜測,老貓打算使用Pyhon腳本自動(dòng)登錄12306,說干就干。
12306的驗(yàn)證碼是比較惡心的,每次弄得不清楚,還整一些不認(rèn)識的東西;老貓?jiān)?jīng)連續(xù)10次選擇錯(cuò)誤,最后系統(tǒng)警告我刷碼頻繁,最后只能讓人代買。
12306網(wǎng)頁版登錄過程分下面幾個(gè)部分:
1>輸入用戶名與密碼,如果不輸入,登錄提示錯(cuò)誤;
2>圖片驗(yàn)證;
2>賬號與密碼驗(yàn)證;
這里我們來看后兩個(gè)步驟:
我們先借助瀏覽器分析登錄行為;
a)輸入用戶名與密碼,界面如下:
b)點(diǎn)擊刷新,更新驗(yàn)證碼:
為了防止失效,看到效果,我們刷新驗(yàn)證碼;
請求地址:https://kyfw.12306.cn/passport/captcha/captcha-image64?
請求參數(shù):
{
'login_site': 'E',
'module': 'login',
'rand': 'sjrand',
'1546822674059':'',#當(dāng)前時(shí)間戳,
'callback': 'jQuery19109060005139400158_1546821765087',#使用固定值
'_': '1546821765097'#1546821765097,通過瀏覽器觀察:每次請求值加1
}
c)選擇驗(yàn)證碼并登陸(嘗試錯(cuò)誤選擇,觀察結(jié)果):
過程如下 :
查看請求信息:
驗(yàn)證失敗,應(yīng)答信息為:
jQuery19109060005139400158_1546821765087({"result_message":"驗(yàn)證碼校驗(yàn)失敗","result_code":"5"});
d)圖片驗(yàn)證分析:
圖片驗(yàn)證請求地址:https://kyfw.12306.cn/passport/captcha/captcha-check? ;
請求參數(shù):
{
'callback': 'jQuery19109060005139400158_1546821765087',#與上一步請求驗(yàn)證圖片相同
'answer': '111,52,109,96',#根據(jù)圖片選擇點(diǎn)擊位置
'rand': 'sjrand',
'login_site': 'E',
'_': '1546821765102'#每次請求值加1,
}
e)如何選擇點(diǎn)擊位置?
有下面幾種方式:
1>機(jī)器學(xué)習(xí)圖片識別(設(shè)計(jì)內(nèi)容較多,準(zhǔn)確率不敢保證);
2>云打碼(老貓自己沒有折騰);
3>將圖片下載下來,自己選擇位置,手動(dòng)填入位置;
老貓實(shí)現(xiàn)方式:
1>觀察請求信息,坐標(biāo)應(yīng)該是正確圖片大概位置,
2>圖片信息:小圖長寬為70,
第一排坐標(biāo):(35,35),(105,35),(175,35),(245,35);
第二排坐標(biāo):(35,105),(105,105),(175,105),(245,105);
3>根據(jù)圖片提示,選擇對應(yīng)位置圖片,然后返回坐標(biāo);例如選擇0,1,返回值:
35,35,105,35
如果選擇成功,應(yīng)答消息中會(huì)有"驗(yàn)證碼校驗(yàn)成功"信息;
我們根據(jù)上面分析來使用代碼完成這一過程。
老貓將其過程分析下面幾步:
1>下載圖片,然后打開圖片查看;
2>輸入正確圖片位置,獲取坐標(biāo);
3>提交驗(yàn)證;
實(shí)現(xiàn)需要知識點(diǎn):
1>requests模塊,cookie管理;
2>Python基本知識點(diǎn)與面向?qū)ο缶幊蹋?/p>
實(shí)現(xiàn)思路:
1>通過瀏覽器獲取當(dāng)前callback值;
2>請求并保存驗(yàn)證碼;
3>手動(dòng)打開圖片,輸入正確位置,獲取坐標(biāo);
4>請求驗(yàn)證,如果驗(yàn)證失敗重復(fù)2~4步驟;
驗(yàn)證碼驗(yàn)證代碼實(shí)現(xiàn):
import re
import base64
import json
import requests
import time
class login12306:
#初始化
def __init__(self, callback, nums, headers):
self.callback = callback
self.nums = nums
self.headers = headers
self.s = requests.Session()
self.fpath = './code.jpg'
#發(fā)起請求
def do_request(self, req_type='GET', url='',pdata=None, jdata=None):
if req_type == 'POST':
req = req = requests.Request(req_type, url, data=pdata, \
json=jdata,headers=self.headers)
else:
req = requests.Request(req_type, url, params=pdata, \
headers=self.headers)
pre = self.s.prepare_request(req)
try:
resp = self.s.send(pre, timeout=3)
return resp
except:
return None
#獲取驗(yàn)證碼坐標(biāo)
def gen_pointlist(self):
#提示輸入位置
indexs = input('input 1~8 : \n')
width = 35
points = []
select = []
#生成圖片對應(yīng)位置
for i in range(0, 8):
points.append([(i) % 4 * width * 2 + width, width * (i // 4 * 2) + width])
#根據(jù)位置后去坐標(biāo)點(diǎn)
[select.extend(points[int(index)-1]) for index in indexs]
tmp = [str(val) for val in select]
return ','.join(tmp)
#下載驗(yàn)證碼
def downimg(self):
url = 'https://kyfw.12306.cn/passport/captcha/captcha-image64?'
ts = str(int(time.time() * 1000))
pdata = {
'login_site': 'E',
'module': 'login',
'rand': 'sjrand',
ts: '',
'callback': self.callback,
'_': str(self.nums)
}
req = self.do_request('GET', url, pdata=pdata)
if not req:
return
m = re.search(r'\((.*)\)', req.text)
text = m.group()[1:-1]
jdata = json.loads(text)
img = base64.b64decode(jdata['image'])
with open(self.fpath, 'wb') as f:
f.write(img)
return True
#獲取驗(yàn)證碼信息
def get_qcinfo(self):
#下載驗(yàn)證碼
if self.downimg():
#下載完成之后打開圖片,并輸入位置獲取坐標(biāo)
indexs = self.gen_pointlist()
return indexs
#驗(yàn)證碼校驗(yàn)
def qc_verify(self):
indexs = self.get_qcinfo()
if not indexs:
return False
self.nums += 1
#驗(yàn)證碼輸入檢查
url = 'https://kyfw.12306.cn/passport/captcha/captcha-check?'
print(indexs)
jdata = {
'callback': self.callback,'answer': indexs,'rand': 'sjrand',
'login_site': 'E','_': self.nums
}
req = self.do_request('GET', url, pdata=jdata)
#輸入成功返回True
if ('驗(yàn)證碼校驗(yàn)成功') in req.text:
print('驗(yàn)證碼成功')
self.indexs = indexs
return True
return False
#用戶名與密碼登錄
def user_login(self):
pass
#登錄接口
def start_login(self):
while True:
if self.qc_verify():
break
time.sleep(2)
self.user_login()
if __name__ == '__main__':
callback = 'jQuery19109060005139400158_1546821765087',
nums = 1546821765097
hds = {'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Host': 'kyfw.12306.cn',
'Cache-Control': 'no-cache',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
}
obj =login12306(callback, nums, hds)
obj.start_login()
運(yùn)行輸出結(jié)果如下:
瀏覽器中,輸入正確驗(yàn)證碼,用戶名與密碼;點(diǎn)擊登錄,登錄過程如下圖:
我們完善user_login方法,代碼如下:
def user_login(self):
url = 'https://kyfw.12306.cn/passport/web/login'
jdata = {
'username': 'myusername',#更換自己用戶名
'password': 'mypwd',#更換成自己密碼
'appid': 'otn',
'answer': self.indexs,
}
req = self.do_request('POST', url, pdata=jdata)
items = [
{'url':'https://kyfw.12306.cn/otn/login/userLogin', 'f':'GET'},
{'url':'https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin','f':'GET'},
]
for item in items:
url = item.get('url')
f = item.get('f')
req = self.do_request(f, url)
url = 'https://kyfw.12306.cn/passport/web/auth/uamtk'
info = {'appid': 'otn'}
req = self.do_request('POST', url, pdata=info)
url = 'https://kyfw.12306.cn/otn/uamauthclient'
jdata = req.json()
info = {'tk': jdata['newapptk']}
req = self.do_request('POST', url, pdata=info)
req_items = [
{'url':'https://kyfw.12306.cn/otn/login/userLogin','f':'GET'},
{'url':'https://kyfw.12306.cn/otn/login/conf','f':'POST'},
{'url':'https://kyfw.12306.cn/otn/index/initMy12306Api','f':'post'},
]
for item in req_items:
url = item.get('url')
f = item.get('f')
req = self.do_request(f, url)
print(req.text)
return req
再次運(yùn)行代碼結(jié)果如下:
登錄成功了,我們可以提取應(yīng)答消息中的信息,判斷是否登錄成功。
對于用戶名密碼請求地址,我們可以不用關(guān)心具體作用,只需要記錄請求地址與參數(shù),有的數(shù)據(jù)可能需要在應(yīng)答中提取。基于這個(gè)基礎(chǔ),我們可以使用Python完成預(yù)定車次選擇,查找賬戶中聯(lián)系人,購票這一系列操作。
經(jīng)過分析,可以看到使用腳本操作,省去了頁面渲染與人為點(diǎn)擊這兩個(gè)操作,節(jié)省了時(shí)間,這樣訂單提交速度就比其他人快,購買成功幾率明顯增加。
但是老貓不打算使用,因?yàn)槭褂玫娜嗽蕉啵@種現(xiàn)象越猖獗;
但是,老貓?jiān)趺椿乩霞夷兀坷县堃惶煨牟辉谘?,沒有一點(diǎn)工作狀態(tài)。老板看在心里也十分為我著急。
大家如果想學(xué)習(xí)Python及爬蟲技術(shù),可以查看下面兩個(gè)專欄:
Python爬蟲專欄地址:https://blog.51cto.com/cloumn/detail/17 ;
Python入門專欄地址:https://blog.51cto.com/cloumn/detail/34 ;