郑重申明:该文章介绍的技术仅供用于学习,不可恶意攻击12306网站。对12306服务器造成的任何损失,后果自负。
导语:由于12306服务器访问量巨大,并且官方为防止黄牛恶意刷票、以及一些非法攻击。12306各模块的 Url地址可能随时会改变。我在看各位前辈代码的时候很多代码已经不合适在现在的时间。所以,重在原理的学习,掌握了原理,不管12306的相关url变成什么样,都可以以不变应万变。
本文将使用以下工具来分析12306购票的过程,然后使用python语言,使用PyQt5实现购票界面的搭建,最终购票。
软件购票视频:
12306.mp4
1、Chrome浏览器(其他的浏览器也可以,都有类似的界面,如Chrome,装了httpwatch的IE浏览器等)+ charles(个人很喜欢)
2、一个可以登录12306网址并且可以购票的12306账号
3. Pycharm
其实在12306软件的实现过程中,我个人认为一般是分为以下三个步骤的:
1。个人账户的登录及授权
2。火车票的余票查询
3。火车票购买
一、个人账户的登录及授权
1.我们首先打开如下地址,进入个人账户的登录界面。网址:https://kyfw.12306.cn/otn/login/init。界面如下图所示
2.我们需要打开谷歌浏览器自带的抓包来对我们请求的各种数据进行获取并分析。(做法:在页面中右键菜单中选择【检查】菜单,打开后,选择【网络】选项卡。如下图所示:)
打开后页面变成二分窗口了,左侧是正常的网页页面,右侧是浏览器自带的控制台,当我们在左侧页面中进行操作后,右侧会显示我们浏览器发送的各种http请求和应答。我们在登录界面输入我们需要登录的个人账号(请注意:我们在点击登录以前要先勾选掉Preserve log,便于我们进行URL的分析)
在我们点击登录后我们回看到出现很多小条目,其实这就是我们在这一过程中发送的各个网络请求。当我们能够拿到我们的个人信息的时候,就表明登录这一块我们已经完全实现了,但是我们使用爬虫的时候需要经历哪些步骤呢?这里就需要引出我用到的charles抓包软件,个人觉得它能够对以上步骤进行层级划分。
1.1 获取验证码并进行验证
我们怎么获取验证码呢? 其实我们在进入网站的时候,验证码已经自动进行加载了,你想知道获取验证码的地址只需要进行刷新一下登录界面即可。通过Charles抓包我们能够获得以下界面。
得到验证码以后,我们就需要将验证码的答案传递给12306的服务器进行检测。(12306怎么知道我们传递的图片是不是正确的呢?–这里就是cookie sessionde原理了,后面讲)检查的地址:https://kyfw.12306.cn/passport/captcha/captcha-check
上图的四个数值是什么意思呢? 其实这里是你所选择的答案在验证码图片上的坐标位置(x,y)
我在UI上的设计思路:创建一个标签用于显示验证码,当我鼠标在我的答案上点击时,产生一个原点,并获取原点的坐标。
```python
from PyQt5.Qt import *
class HfLabel(QLabel):
def clear_points(self):
'''
作用:清除验证码标签上的点
:return:
'''
[child.deleteLater() for child in self.children() if child.inherits("QPushButton")]
def get_result(self):
'''
获取按钮的坐标点
:return:
'''
result = ",".join(["{},{}".format(child.x()+10,child.y()-20) for child in self.children() if child.inherits("QPushButton")]) # 搞清楚为什么这里要分别减10 和20 是由于控件的位置坐标来决定的
# result =",".join(["{},{}".format(btn.x() + 15, btn.y() - 15) for btn in self.children() if btn.inherits("QPushButton")]) # 搞清楚为什么这里要分别减10 和20 是由于控件的位置坐标来决定的
return result
def mousePressEvent(self,evt):
super(HfLabel, self).mousePressEvent(evt)
if evt.x()<0 and evt.y()<=30:
return None
point_btn = QPushButton(self)
point_btn.setStyleSheet("background-image:../Images/yzm_label.png")
point_btn.resize(20,20)
point_btn.move(evt.pos()-QPoint(10,10))
point_btn.show()
point_btn.clicked.connect(lambda x,Btn=point_btn:Btn.deleteLater()) # 双击取消
1.2 登录账号
当我们解决验证码的获取以及验证以后,我们应该获取输入的账号和密码,并发送至12306服务器进行验证。
验证地址:https://kyfw.12306.cn/passport/web/login
有朋友看到 登录成功以为就结束,其实到这一步还是不能哪去到自己的用户信息,我们还需要进行授权有效。
授权网址:https://kyfw.12306.cn/passport/web/auth/uamtk
传递的参数:
上面提到的utmark在我们授权这里会用到,我们请求头的cookies中除开有 utmark以外还有上面验证码的cookies信息(这是12306向比以前改进的地方) 如果只是单纯的使用request中的session对象是不能够完成的。所以在这里我取出其中需要的信息,自己传递cookies.
def Authorclient(self):
# 先向 Uamtk 请求最新的数据
headers = {
"User-Agent": "Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko) Chrome/80.0.3987.122Safari/537.36",
"Connection": "keep-alive",
"Content-Length": "60",
"Accept": "application/json,text/javascript,*/*;q=0.01",
"Sec-Fetch-Dest": "empty",
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
"Origin": "https://kyfw.12306.cn",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Referer": "https://kyfw.12306.cn/otn/login/init",
"Accept-Encoding": "gzip,deflate,br",
"Accept-Language": "zh-CN,zh;q=0.9",
}
cookies = {
"Cookie": "{};{};jc_save_wfdc_flag=dc;"
"RAIL_DEVICEID=FYQF27FM-hDGTl8bZAdL0k1DC6z4E7IgqIB6drUkWbGpOzzR3ROgaoV2_QutZUcPDywhQJE7klUQNBMZjX_EjszINQsMa0ftgfA1O2jxWVEjRjZDm4lovBeZj4A-7b2R5jy3DOtkN5uC5s_SnOcK4GG7FVWgYfAa;"
"RAIL_EXPIRATION=1585143263381;_jc_save_fromStation=%u5317%u4EAC%2CBJP;_jc_save_showIns=true;"
"route=495c805987d0f5c8c84b14f60212447d;BIGipServerotn=535298314.24610.0000;"
"BIGipServerpassport=971505930.50215.0000".format(self.Author_list[0], self.Author_list[1])
}
data = {
"appid": "otn"
}
new_tk_resp = self.session.post(API_URL.Uamtk_URL, data=data,headers = headers,cookies = cookies,verify = False)
tk = new_tk_resp.json()["newapptk"]
# 拿到 tk 以后再向另一个网站请求数据
# res2 = requests.request(method="POST",url=API_URL.Uamauthliect_URL, data={"tk":tk},headers = headers,verify = False)
res2 = self.session.post(url=API_URL.Uamauthliect_URL