1. 程式人生 > >終於搞定了回家車票

終於搞定了回家車票

port win64 false 示意圖 pro connect -c pen ext

快要春節了,老板發話:‘提前完成工作可以提前回家‘;於是老貓每天加班加點趕進度,估計提前1周回家;正當老貓沈浸在幸福之時,老板過來關心問我:‘老貓,車票買了嗎,買不到晚幾天走吧,那會好買‘;忽然有種被算計的感覺!!
技術分享圖片
人無遠慮必有近憂,車票是個大問題,老貓要買的車票這兩天放票;於是每天發動同事幫我搶票,可是連續兩次都沒搶到;老貓有點慌了,買不到車票怎麽辦?
技術分享圖片

那麽多車票,為什麽好幾個人搶連續兩天都搶不到;
技術分享圖片
老貓分析其中可能存在原因,分析過程如下:

購票流程分析:

技術分享圖片

1>老貓買的是比較緊張的車次,搶票人數遠遠大於出票數量;
2>每次搶票和網速,手速有一定關系;
3>雖然感覺每次手動購票速度很快,但是購票人數多,手速快的也很多,對比而言也就不快了。

購票中存在影響因素:

老貓又仔細分析了網頁購票過程:

1>登錄,驗證碼與密碼登錄;
2>刷票,選擇始發站,終點站,日期,車次;
3>出票後點擊預購;
4>等待排隊,選擇乘車人;

理論上大家刷票過程都是一樣的,但是幾個因素會影響我們購票結果:

1>第一步登錄,這個沒有問題,老貓和大家都會提前登錄;
2>第二步:先看個示意圖:
技術分享圖片
老貓提前選擇好車次等信息,到出票時刻點擊刷票;但是網速與瀏覽器渲染頁面速度,可能會影響下一步操作;
3>刷票之後,如果出現購買車次,馬上點擊預購,這裏會和自己手速有關,幾百毫秒過去了,老貓可能已經排在幾百人之後了;
4>點擊預購,出現下面頁面:

技術分享圖片
這裏考驗手速:點擊購票人與訂單提交,然後憑天由命吧。

結合上面分析:搶熱點車次,真有點撞大運的感覺。如何解決問題呢?

1>使用第三方軟件代購或者購買加油包;
2>拼車回家;

出去安全考慮,還是應該選擇第一個方式。但是老貓有點好奇,為什麽第三方軟件或者加油包能夠購買成功?老貓猜測可能原因:
技術分享圖片

老貓認為每個賬號就是一個購票者,這些賬號由腳本控制,到時間點開始搶票,腳本速度肯定要比人的速度快,所有購買成功幾率要大於認為操作。以上部分構成純屬瞎猜,如有雷同,純屬巧合。
這樣來看,老貓除了和幾萬個人競爭,還要和第三方軟件競爭,所以熱點車次的車票更難買到了。這樣大家也就理解為什麽第三方軟件購買成功幾率更大。

為了驗證這個猜測,老貓打算使用Pyhon腳本自動登錄12306,說幹就幹。

1:登錄過程分析:

12306的驗證碼是比較惡心的,每次弄得不清楚,還整一些不認識的東西;老貓曾經連續10次選擇錯誤,最後系統警告我刷碼頻繁,最後只能讓人代買。
12306網頁版登錄過程分下面幾個部分:

1>輸入用戶名與密碼,如果不輸入,登錄提示錯誤;
2>圖片驗證;
2>賬號與密碼驗證;

這裏我們來看後兩個步驟:

1.1 驗證碼分析

我們先借助瀏覽器分析登錄行為;
a)輸入用戶名與密碼,界面如下:
技術分享圖片
b)點擊刷新,更新驗證碼:
為了防止失效,看到效果,我們刷新驗證碼;
請求地址:https://kyfw.12306.cn/passport/captcha/captcha-image64?
請求參數:

{
    ‘login_site‘: ‘E‘,
    ‘module‘: ‘login‘,
    ‘rand‘: ‘sjrand‘,
    ‘1546822674059‘:‘‘,#當前時間戳,
    ‘callback‘: ‘jQuery19109060005139400158_1546821765087‘,#使用固定值
    ‘_‘: ‘1546821765097‘#1546821765097,通過瀏覽器觀察:每次請求值加1
}

c)選擇驗證碼並登陸(嘗試錯誤選擇,觀察結果):
過程如下 :
技術分享圖片
查看請求信息:
技術分享圖片

驗證失敗,應答信息為:

jQuery19109060005139400158_1546821765087({"result_message":"驗證碼校驗失敗","result_code":"5"});

d)圖片驗證分析:
圖片驗證請求地址:https://kyfw.12306.cn/passport/captcha/captcha-check? ;
請求參數:

{
    ‘callback‘: ‘jQuery19109060005139400158_1546821765087‘,#與上一步請求驗證圖片相同
    ‘answer‘: ‘111,52,109,96‘,#根據圖片選擇點擊位置
    ‘rand‘: ‘sjrand‘,
    ‘login_site‘: ‘E‘,
    ‘_‘: ‘1546821765102‘#每次請求值加1,
}

e)如何選擇點擊位置?
有下面幾種方式:

1>機器學習圖片識別(設計內容較多,準確率不敢保證);
2>雲打碼(老貓自己沒有折騰);
3>將圖片下載下來,自己選擇位置,手動填入位置;

老貓實現方式:

1>觀察請求信息,坐標應該是正確圖片大概位置,
2>圖片信息:小圖長寬為70,
第一排坐標:(35,35),(105,35),(175,35),(245,35);
第二排坐標:(35,105),(105,105),(175,105),(245,105);
3>根據圖片提示,選擇對應位置圖片,然後返回坐標;例如選擇0,1,返回值:
35,35,105,35

如果選擇成功,應答消息中會有"驗證碼校驗成功"信息;
我們根據上面分析來使用代碼完成這一過程。

2:驗證碼處理:

老貓將其過程分析下面幾步:

1>下載圖片,然後打開圖片查看;
2>輸入正確圖片位置,獲取坐標;
3>提交驗證;

實現需要知識點:

1>requests模塊,cookie管理;
2>Python基本知識點與面向對象編程;

實現思路:

1>通過瀏覽器獲取當前callback值;
2>請求並保存驗證碼;
3>手動打開圖片,輸入正確位置,獲取坐標;
4>請求驗證,如果驗證失敗重復2~4步驟;

驗證碼驗證代碼實現:

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‘
    #發起請求
    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
    #獲取驗證碼坐標
    def gen_pointlist(self):
        #提示輸入位置
        indexs = input(‘input 1~8 : \n‘)
        width = 35
        points = []
        select = []
        #生成圖片對應位置
        for i in range(0, 8):
            points.append([(i) % 4 * width * 2 + width, width * (i // 4 * 2) + width])
        #根據位置後去坐標點
        [select.extend(points[int(index)-1]) for index in indexs]
        tmp = [str(val) for val in select]
        return ‘,‘.join(tmp)
    #下載驗證碼
    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
    #獲取驗證碼信息
    def get_qcinfo(self):
        #下載驗證碼
        if self.downimg():
            #下載完成之後打開圖片,並輸入位置獲取坐標
            indexs = self.gen_pointlist()
            return indexs
    #驗證碼校驗
    def qc_verify(self):
        indexs = self.get_qcinfo()
        if not indexs:
            return False
        self.nums += 1

        #驗證碼輸入檢查
        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 (‘驗證碼校驗成功‘) in req.text:
            print(‘驗證碼成功‘)
            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()

運行輸出結果如下:
技術分享圖片

3:用戶登錄:

瀏覽器中,輸入正確驗證碼,用戶名與密碼;點擊登錄,登錄過程如下圖:
技術分享圖片

我們完善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

再次運行代碼結果如下:
技術分享圖片
登錄成功了,我們可以提取應答消息中的信息,判斷是否登錄成功。

對於用戶名密碼請求地址,我們可以不用關心具體作用,只需要記錄請求地址與參數,有的數據可能需要在應答中提取。基於這個基礎,我們可以使用Python完成預定車次選擇,查找賬戶中聯系人,購票這一系列操作。
經過分析,可以看到使用腳本操作,省去了頁面渲染與人為點擊這兩個操作,節省了時間,這樣訂單提交速度就比其他人快,購買成功幾率明顯增加。

但是老貓不打算使用,因為使用的人越多,這種現象越猖獗;
技術分享圖片
但是,老貓怎麽回老家呢?老貓一天心不在焉,沒有一點工作狀態。老板看在心裏也十分為我著急。

最終結果:

技術分享圖片

終於搞定了回家車票