【Python3爬虫】最新的12306爬虫

1、写在前面

  我在之前写过一次12306网站的爬虫,当时实现了模拟登陆和查询车票,可是感受还不太够,因此对以前的代码加以修改,还实现了一个订购车票的功能。git

2、主要思路

  在使用Selenium作模拟登陆12306网站的时候,须要将登陆成功后的Cookie保存下来,这个Cookie在后面是必需的。而后就是在12306网站上查票订票,同时使用Fiddler软件进行抓包,经过分析获得订票所需的十多个请求,只要依次发送这些请求,在请求成功以后就可以订到票。github

3、模拟登陆

  以前的代码已经基本实现了模拟登陆的功能,可是还无法获得想要的Cookie,因此须要对以前的代码进行改进。虽然Selenium模块提供了get_cookies()方法,可是使用这个方法获得的是当前会话的Cookie,也就是Selenium开启的浏览器中当前页面的Cookie,这个Cookie和本地浏览器中的Cookie是不一样的。以下是从本地Chrome中拷贝的Cookie,其中以_jc_save开头的字段都是以前查询车票的记录,而其他字段都是生成的:正则表达式

JSESSIONID=A318817EEE594DE954CE352761DF4CD7;json

_jc_save_fromStation=%u6B66%u6C49%2CWHN;浏览器

_jc_save_wfdc_flag=dc;cookie

_jc_save_toStation=%u4E0A%u6D77%2CAOH;dom

RAIL_EXPIRATION=1560095439082;ide

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

_jc_save_fromDate=2019-06-30;post

_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("未登陆!请先登陆。")

4、订购车票

   因为查询车票以前就已经作过了,因此这里就再也不赘述。这里就说查询车票以后的操做,首先是在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:

 

5、运行结果

  下图是在Pycharm中的运行截图,在登陆成功以后查询余票,将查询的结果显示出来:

   查询车票以后就是预订车票,须要输入车次名称、座位类别和选择乘客,而后提交订单,最终成功订到火车票。

  订票成功以后,进入12306网站进行查看,能够看到成功订到票了, 以下图所示:

 

 

完整代码已上传到GitHub

相关文章
相关标签/搜索