【Python3爬蟲】最新的12306爬蟲


一、寫在前面

  我在以前寫過一次12306網站的爬蟲,當時實現了模擬登錄和查詢車票,但是感覺還不太夠,所以對之前的代碼加以修改,還實現了一個訂購車票的功能。

二、主要思路

  在使用Selenium做模擬登錄12306網站的時候,需要將登錄成功后的Cookie保存下來,這個Cookie在后面是必需的。然后就是在12306網站上查票訂票,同時使用Fiddler軟件進行抓包,通過分析得到訂票所需的十多個請求,只要依次發送這些請求,在請求成功之后就能夠訂到票。

三、模擬登錄

  之前的代碼已經基本實現了模擬登錄的功能,但是還沒法得到想要的Cookie,所以需要對之前的代碼進行改進。雖然Selenium模塊提供了get_cookies()方法,但是使用這個方法得到的是當前會話的Cookie,也就是Selenium開啟的瀏覽器中當前頁面的Cookie,這個Cookie和本地瀏覽器中的Cookie是不同的。如下是從本地Chrome中拷貝的Cookie,其中以_jc_save開頭的字段都是之前查詢車票的記錄,而其余字段都是生成的:

JSESSIONID=A318817EEE594DE954CE352761DF4CD7;

_jc_save_fromStation=%u6B66%u6C49%2CWHN;

_jc_save_wfdc_flag=dc;

_jc_save_toStation=%u4E0A%u6D77%2CAOH;

RAIL_EXPIRATION=1560095439082;

RAIL_DEVICEID=P2wunHEkKFe9MgTM56h-NxsWiIGNkK6JLCOVaG0DHzRm-RxYa7YnDwftPoumiZ0wL7GPsQ93YBHRHgMgB_GLWwZ9Vb65tNiVuwaIOytW8lVG7B1KopI4pSyUr1u06RWpKPhvExBg3FA7ed87WxO3E-68Wg-hXZLl;

_jc_save_fromDate=2019-06-30;

_jc_save_toDate=2019-06-06;

_jc_save_showIns=true;

route=495c805987d0f5c8c84b14f60212447d;

BIGipServerotn=300941834.24610.0000;

BIGipServerpool_passport=250413578.50215.0000

   下面是使用Selenium模塊的get_cookies()方法得到的Cookie,可以看到和瀏覽器中的Cookie有很大不同,缺少了很多字段:

[{'domain': 'kyfw.12306.cn', 'httpOnly': False, 'name': 'JSESSIONID', 'path': '/otn', 'secure': False, 'value': '672BAF8C694C50C49D3EFFCF9913A745'},

{'domain': 'kyfw.12306.cn', 'httpOnly': False, 'name': 'route', 'path': '/', 'secure': False, 'value': 'c5c62a339e7744272a54643b3be5bf64'},

{'domain': 'kyfw.12306.cn', 'httpOnly': False, 'name': 'BIGipServerotn', 'path': '/', 'secure': False, 'value': '1139802634.24610.0000'}]

  解決辦法是使用add_cookie()方法向Selenium開啟的Chrome中添加Cookie,達到模擬本地瀏覽器的效果,最終就能登錄成功。在登錄成功之后,要獲取此時的Cookie,除了使用get_cookies()方法或者get_cookie()方法,還可以使用如下語句:

cookie = browser.execute_script("return document.cookie;")

  不過為了驗證是否真的登錄成功了,還需要進行一下測試,驗證是否登錄成功的方法如下代碼,這段代碼會發送一個請求,請求的結果中包含了是否登錄信息(即is_login)和用戶名等信息:

 1 def get_name(self):  2     """
 3  獲取用戶姓名  4  :return:  5     """
 6     url = "https://kyfw.12306.cn/otn/login/conf"
 7     res = requests.post(url, headers=self.headers)  8     is_login = res.json()['data']['is_login']  9     if is_login == 'Y': 10         self.name = res.json()['data']['name'] 11         print("歡迎用戶:{}".format(self.name)) 12     else: 13         print("未登錄!請先登錄。")

四、訂購車票

   由於查詢車票之前就已經做過了,所以這里就不再贅述。這里就說查詢車票之后的操作,首先是在12306網站上查余票,然后選擇一個車次點擊預訂,就會跳轉到如下頁面:

  在這個頁面上可以選擇乘客、選擇座位類型,然后再提交訂單。這里雖然我們可以使用開發者工具然后刷新頁面來抓包,但是為了避免遺漏掉某些請求,所以我選擇使用Fiddler軟件抓包,最終經過分析實踐得到12個請求,其url和對應的含義如下圖所示:

  這里我並不打算把所有的請求都說一遍,我會將幾個重要的請求拿出來描述,這些請求所使用的headers都是一樣的,其中包含了登錄后的Cookie,如果Cookie失效就會導致訂票失敗。

1.初始化頁面

首先是initDc這個請求,在這個請求的結果中包含了后面請求所必需的一個參數--token(如下圖),獲取的方法也比較簡單,可以直接使用正則表達式進行匹配:

  初始化頁面獲取token的代碼如下:

 1 # 初始化,獲取token值
 2 def init_dc():
 3     global token
 4     url = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"
 5     data = {
 6         "_json_att": ""
 7     }
 8     res = requests.post(url, headers=headers, data=data)
 9     result = re.findall(" var globalRepeatSubmitToken = '(.*?)';", res.text)
10     # print(result)
11     if len(result):
12         token = result[0]
13     else:
14         raise Exception("Error init")

2.提交車票預訂信息

  其次是提交車票預訂信息,在Fiddler中點擊Inspectors,然后選擇WebForms,可以看到如下圖所示信息,其中包含了出發城市、目的城市、出發日期等:

 

   需要注意的是secretStr這個加密字符串,其來源於查詢車票時的結果,在結果中每一條車次信息中都包含了一個字符串,不過這兩個字符串並不完全一樣。如下圖所示就是兩個字符串的對比,要得到加密字符串只需要使用unquote()方法:

 

3.確認訂單信息

   在選擇完車次、座位類型、乘客之后會生成一個訂單,然后就會發送一個確認訂單信息的請求,其中包含了很多重要的信息。這里我放上該部分的代碼:

 1 # 確認訂單信息
 2 def check_order_info(name, uid, mobile, type_id):  3     # 商務座,一等座,二等座,軟卧,硬卧,硬座
 4     type_str = ["9,0,1,", "M,0,1,", "O,0,1,", "4,0,1,", "3,0,1,", "1,0,1,"][type_id]  5     url = "https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo"
 6     data = {  7         "_json_att": "",  8         "bed_level_order_num": "000000000000000000000000000000",  9         "cancel_flag": "2", 10         "oldPassengerStr": name + ",1," + uid + ",1_", 11         "passengerTicketStr": type_str + name + ",1," + uid + "," + mobile + ",N", 12         "REPEAT_SUBMIT_TOKEN": token, 13         "randCode": "", 14         "tour_flag": "dc", 15         "whatsSelect": "1"
16  } 17     res = requests.post(url, headers=headers, data=data) 18     # print(res.text)

  這個方法包含了四個參數,name、uid和mobile分別表示乘客的姓名、身份證號和電話號碼,這三個值都是在獲取乘客信息時得到的,第四個參數是座位類別id。在這個請求攜帶的參數中有一個REPEAT_SUBMIT_TOKEN,這就是前面說過的token,由於我已經將token設置為了全局變量,所以這里就不用作為參數傳到方法里了。要注意的是每個座位類別對應的字符是不同的,我通過在頁面上選擇元素得到了每個座位類型對應的字符,最后生成一個列表,然后通過改變座位類別id就能完成選擇座位類別的功能。

4.提交預訂請求

   在確認訂單之后就是提交預訂請求,還是在Fiddler軟件中找到這個請求,然后查看其攜帶的參數,如下圖所示:

 

   其中包含了車次編碼、出發站編碼、目的站編碼、token等信息,這些編碼信息都可以在查詢車票的結果中得到,需要注意的是train_date,可以看到這是一個日期信息,而且是一個格林威治標准時間,要得到這個時間可以使用如下方法,這就能將日期轉變成格林威治標准時間:

train_date = datetime.datetime.strptime(train_date, "%Y-%m-%d").date()
this_date = train_date.strftime("%a+%b+%d+%Y")

5.檢查提交狀態

   在提交預訂請求之后,需要檢查提交狀態,這個請求包含了很多參數,其中一些參數的值都包含在提交預訂請求的結果中,除此之外這些參數還有乘客姓名、身份證號、乘客電話、token等。這個請求返回的結果中有一個submitStatus,需要提取出來,該值表明了提交是否成功。該部分的代碼如下所示:

 1 # 檢查提交狀態
 2 def confirm(key_check, left_ticket, passenger_name, passenger_id, passenger_mobile, location, type_id):  3     # 商務座,一等座,二等座,軟卧,硬卧,硬座
 4     type_str = ["9,0,1,", "M,0,1,", "O,0,1,", "4,0,1,", "3,0,1,", "1,0,1,"][type_id]  5     url = "https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue"
 6     data = {  7         "choose_seats": "",  8         "dwAll": "N",  9         "key_check_isChange": key_check, 10         "leftTicketStr": left_ticket, 11         "oldPassengerStr": passenger_name + ",1," + passenger_id + ",1_", 12         "passengerTicketStr": type_str + passenger_name + ",1," + passenger_id + "," + passenger_mobile + ",N", 13         "purpose_codes": "00", 14         "randCode": "", 15         "REPEAT_SUBMIT_TOKEN": token, 16         "roomType": "00", 17         "seatDetailType": "000", 18         "train_location": location, 19         "whatsSelect": "1", 20         "_json_att": "", } 21     res = requests.post(url, headers=headers, data=data) 22     try: 23         js = json.loads(res.text) 24         status = js["data"]["submitStatus"] 25         # print(status)
26         return status 27     except Exception as e: 28         print(e) 29         raise Exception("Confirm Error!")

6.排隊等待

   當我們的訂單提交成功之后,就需要排隊等待了,此時會發送一個請求,該請求中攜帶了一個時間戳參數(random),如下圖所示:

 

   這是一個十三位的時間戳,在Python中可以使用 int(time() * 1000) 得到十三位時間戳。需要注意的是排隊等待的結果是不確定的,正確的結果如下圖所示:

 

   其中有一個orderId,這個值是我們需要的。如果返回的結果中不包含orderId,就需要重新發送請求。

7.請求預訂結果

  在得到orderId之后,就可以請求預訂結果了,請求無誤的話就能夠成功訂到票了。下圖是在Fiddler軟件中截到的圖,其中EF73361481就是前面得到的orderId:

 

五、運行結果

  下圖是在Pycharm中的運行截圖,在登錄成功之后查詢余票,將查詢的結果顯示出來:

   查詢車票之后就是預訂車票,需要輸入車次名稱、座位類別和選擇乘客,然后提交訂單,最終成功訂到火車票。

  訂票成功之后,進入12306網站進行查看,可以看到成功訂到票了, 如下圖所示:

 

 

完整代碼已上傳到GitHub


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM