12306搶票系統無介面版本——(1)登入(12306驗證碼問題破解)

胡桃夾子cc發表於2018-07-26

引言

隨著高速鐵路時代的來臨,世界發達地區高速鐵路網路的不斷完善與發展,高速鐵路運輸成為各國重要的交通方式之一。同時鐵路的高速發展正在影響著人們的出行方式。
“鐵路12306”是中國鐵路客戶服務中心推出的官方購票應用軟體,與火車票務官方網站共享使用者、訂單和票額等資訊,並使用統一的購票業務規則,軟體具有車票預訂、線上支付、改簽、退票、訂單查詢、常用聯絡人管理、個人資料修改、密碼修改等功能。於2013年12月8日正式上線試執行。而“鐵路12306”雖然滿足了我們基本的購票需要,但是對一些特殊群體並沒有照顧到。為了改變這種情況,為此嘗試使用python程式碼來實現搶票系統。

使用工具和庫

開發環境是python3.6.5
開發工具是sublime Text

使用到的重要庫:
http請求(requests庫)

模擬登入流程

1.進入12306登入網站

首先匯入requests包,利用requests中的Session()保持cookie。

import requests
session = requests.Session()
url = "https://kyfw.12306.cn/otn/login/init"
response = session.get(url)

2.獲取驗證碼
這裡寫圖片描述
在開發者工具中的Network可以很容易找到其中的驗證碼。從而我們可以得到驗證碼的 Request URL: https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.5516997730666673
(這個驗證碼是實時重新整理的)
接著上面的的程式碼,我們通過傳送get請求得到驗證碼圖片並儲存。

#下載驗證碼
captcha_url = "https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.3874691076542045"

cap_response = session.get(captcha_url)
#儲存驗證碼 寫檔案
f = open('captcha.jpg','wb')
f.write(cap_response.content)  #二進位制資料
f.close()

3.校驗驗證碼
首先嚐試選擇驗證碼並點選。我們可以在Netwrok中看到一個新的響應。這裡寫圖片描述
從名字也可以很容易理解這是一個校驗驗證碼的響應。我們把Headers拉到最下面可以看到通過傳送POST請求來校驗驗證碼是否正確。
這裡寫圖片描述
這其中的answer就是我們選擇驗證碼之後的位置。
下面是程式碼實現。

#校驗驗證碼
check_url = "https://kyfw.12306.cn/passport/captcha/captcha-check"

'''
Form Data
answer: 168,45
login_site: E
rand: sjrand
'''
point = {
    '1': '35,43',
    '2': '108,43',
    '3': '185,43',
    '4': '254,43',
    '5': '34,117',
    '6': '108,117',
    '7': '180,117',
    '8': '258,117',
}
#可以確定八個圖片的位置

def get_point(nums):
    nums = nums.split(',')
    # print(nums)
    temp = []
    for item in nums:  # ['3', '6']
        temp.append(point[item])
    return ','.join(temp)


data = {
    'answer': get_point(input('請輸入驗證碼座標:')),   #'254,106'
    'login_site': 'E',
    'rand': 'sjrand'
}

check_response = session.post(check_url,data=data)
# print(check_response.text)
check_res = check_response.json()
# 判斷校驗結果
if not check_res['result_code'] == '4':
    exit('驗證碼校驗失敗,請重新登入')

最後我們通過驗證”result_code”是否等於4來判斷驗證碼是否選擇正確。
4.登入,校驗使用者名稱和密碼
假如我們嘗試選擇正確的驗證碼登入,會發現12306是先驗證驗證碼是否正確再去校驗使用者名稱或者密碼。
通過跟驗證碼同樣的方式,我們首先選擇正確的驗證碼,輸入使用者名稱和密碼後點選登入會發現得到了新的響應。
這裡寫圖片描述
這裡寫圖片描述
這裡也很容易理解,瀏覽器得到賬號和密碼之後傳送了一個表單給Request URL: https://kyfw.12306.cn/passport/web/login
用與驗證碼類似的方法來寫程式碼。

# 登入 ,校驗使用者名稱和密碼
import config
login_url = 'https://kyfw.12306.cn/passport/web/login'

login_data = {
    "username": config.username,   
    "password": config.password,
    "appid": "otn"
}
login_response = session.post(login_url,data=login_data)

這裡我新寫了一個程式碼來呼叫使用者名稱和密碼。

到這裡我們差不多完成了驗證碼問題和登入問題。

當時我也簡單的認為這裡已經登入全部完成。但是之後測試時發現得到的網頁中沒有顯示個人的名字。通過再次檢查發現登入完成後還傳送了兩個請求得到許可權來完整的完成登入。
4.獲取許可權
經過反覆測試之後發現需要兩個請求來獲取許可權。
這裡寫圖片描述
首先我們傳送請求到uamk中,FortData為:
這裡寫圖片描述

#獲取許可權
uamtk_url = 'https://kyfw.12306.cn/passport/web/auth/uamtk'

uamtk_data = {
    'appid': 'otn'
}
response = session.post(uamtk_url, data=uamtk_data)
print(response.text)

uantk_response = response.json()['newapptk']

我們嘗試輸出得到的返回值,發現其中的”newapptk”就是第二個請求傳送的表單內容
這裡寫圖片描述
所以我們定義一個變數來儲存這個值。再將它傳送給”uamauthclient”中。

# 獲取許可權
auth_url = 'https://kyfw.12306.cn/otn/uamauthclient'
auth_data = {
    'tk': uantk_response
}
response = session.post(auth_url, data=auth_data)
if response.json()['result_code'] == 0:
    print(response.text)

完成這兩步之後我們輸出返回值。可以看到這裡輸出了我們使用者的姓名。
名字已打碼
5.跳轉頁面

# 跳轉登陸頁面

login_redirect = 'https://kyfw.12306.cn/otn/login/userLogin'
response = session.get(login_redirect)

check_url = 'https://kyfw.12306.cn/otn/login/checkUser'
data = {
    '_json_att': ''
}
response = session.post(check_url,data=data)
# print(response.text)

假如我們嘗試輸出可以得到使用者的姓名來驗證是否已經獲取許可權並登入成功。

到此為止我們已經完整的完成了登入過程。之後便是購票等步驟。

下面附上程式碼執行的結果。

這裡寫圖片描述

測試完成後我們將整個程式碼進行封裝。

import requests
import random
import config


class TicketRob:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        })
        self.index_url = 'https://kyfw.12306.cn/otn/login/init'
        self.captcha_url = 'https://kyfw.12306.cn/passport/captcha/captcha-image'
        self.login_url = 'https://kyfw.12306.cn/passport/web/login'
        self.check_captcha_url = 'https://kyfw.12306.cn/passport/captcha/captcha-check'
        self.uamtk_url = 'https://kyfw.12306.cn/passport/web/auth/uamtk'
        self.auth_url = 'https://kyfw.12306.cn/otn/uamauthclient'


        self.point = {
            '1': '35,43',
            '2': '108,43',
            '3': '185,43',
            '4': '254,43',
            '5': '34,117',
            '6': '108,117',
            '7': '180,117',
            '8': '258,117',
        }
        self.dict = {}

    def get_point(self, nums):
        nums = nums.split(',')
        # print(nums)
        temp = []
        for item in nums:  # ['3', '6']
            temp.append(self.point[item])
        return ','.join(temp)

    # 檢查使用者名稱密碼
    def main(self, username, password):
        data = {
            'username': username,
            'password': password,
            'appid': 'otn'
        }
        self.session.get(self.index_url)  # 1
        self.get_captcha()  # 2
        check_res = self.check_captcha()  # 3
        if check_res:
            response = self.session.post(self.login_url, data=data)  # 4
            if response.json()['result_code'] == 0:
                tk = self.get_tk()  # 5
                auth_res = self.get_auth(tk)  # 6

        # 獲取驗證碼
    def get_captcha(self):
        data = {
            'login_site': 'E',
            'module': 'login',
            'rand': 'sjrand',
            str(random.random()): ''
        }
        response = self.session.get(self.captcha_url, params=data)
        with open('captcha.jpg', 'wb') as f:
            f.write(response.content)

    # 校驗驗證碼
    def check_captcha(self):
        data = {
            'answer': self.get_point(input('請輸入正確的圖片序號>>>:')),
            'login_site': 'E',
            'rand': 'sjrand'
        }
        response = self.session.post(self.check_captcha_url, data=data)
        if response.json()['result_code'] == '4':
            return True
        else:
            print('驗證碼選擇錯誤,請重新選擇')
        return False

    # 獲取許可權token
    def get_tk(self):
        uamtk_data = {
            'appid': 'otn'
        }
        response = self.session.post(self.uamtk_url, data=uamtk_data)
        return response.json()['newapptk']

    # 獲取許可權
    def get_auth(self, tk):
        auth_data = {
            'tk': tk
        }
        response = self.session.post(self.auth_url, data=auth_data)
        if response.json()['result_code'] == 0:
            print(response.text)
            return True
        return False

if __name__ == '__main__':
    ticket = TicketRob()
    ticket.main(config.username, config.password)

相關文章