自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm=1001.2014.3001.5501
1 发送与分析车票信息的查询请求
得到了获取车票信息的网络请求地址,然后又分析出请求地址的必要参数以及车站名称转换的文件,接下来就需要将主窗体中输入的出发地、目的地以及出发日期三个重要的参数配置到查票的请求地址中,然后分析并接收所查询车票的对应信息。具体步骤如下:
1)在浏览器中打开如下图所示的查询请求地址。
然后在浏览器中将以json的方式返回车票的查询信息,如图33所示。
图33 返回加密的车票信息
说明:在看到的加密信息后先分析数据中是否含有可用的信息,例如,网页中的预订、时间、车次,在图33中的加密信息中含有G13的字样和时间信息。然后对照浏览器中余票查询的页面,查找对应车次信息如图34所示,此时可以判断返回的json信息确实含有可用数据。
图34 对照可用数据
2)发现可用数据后,在项目中创建query_request.py文件,在该文件中首先导入get_stations文件下的所有方法,然后分别创建名称为data与type_data的列表(list)分别用于保存整理好的车次信息与分类后的车次信息。代码如下:
from get_stations import *
from fake_useragent import UserAgent # 导入伪造头部信息的模块
'''5-7 目的地 3 车次 6 出发地 8 出发时间 9 到达时间 10 历时 26 无坐 29 硬座
24 软座 28 硬卧 33 动卧 23 软卧 21 高级软卧 30 二等座 31 一等座 32 商务座特等座
'''
data = [] # 用于保存整理好的车次信息
type_data = [] # 保存车次分类后最后的数据
headers = {"User-Agent": UserAgent().random,
'Cookie':'JSESSIONID=752D6B778A872D3284EE86393C07462E; BIGipServerpassport=937951498.50215.0000; route=6f50b51faa11b987e576cdb301e545c4; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u4E0A%u6D77%2CSHH; _jc_save_wfdc_flag=dc; BIGipServerotn=854589962.50210.0000; RAIL_EXPIRATION=1572905432654; RAIL_DEVICEID=o_JxNm2gLYQelxuRwCwhWaK2j5NgJ7lQs9oqcDri2nnS7XMbHptIMfZYPJ8mfxhFfdwJYpYKU9Khja1JBqarjGT4ayRk6mzywW7NdCi3A5bLnwjUal9MUzNW-WRPO8qfAOEM4cKZDqSzdgJQ7hRhT6FWkjU4T8P-; BIGipServerpool_passport=367854090.50215.0000; _jc_save_fromDate=2019-11-01; _jc_save_toDate=2019-11-01; BIGipServerportal=3151233290.16671.0000'} # 随机生成浏览器头部信息
说明:由于返回的加密信息很杂乱,所以需要创建“data = []”列表(list)来保存后期整理好的车次信息,然后需要将车次分类(例如,高铁、动车等),最后创建“type_data = []”列表(list)来保存分类后的车次信息。
3)创建query()方法,在调用该方法时需要三个参数,分别为出发日期、出发地以及目的地;然后创建查询请求的完整地址,并通过format()方法格式化地址;再将返回的json数据转换为字典类型;最后通过字典类型键值的方法取出对应的数据并进行整理与分类。代码如下:
def query(date, from_station, to_station):
data.clear() # 清空数据
type_data.clear() # 清空车次分类保存的数据
# 查询请求地址
url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(
date, from_station, to_station)
# 发送查询请求
response = requests.get(url,headers=headers)
# # 将json数据转换为字典类型,通过键值对取数据
result = response.json()
result = result['data']['result']
# 判断车站文件是否存在
if isStations() == True:
stations = eval(read()) # 读取所有车站并转换为dic类型
if len(result) != 0: # 判断返回数据是否为空
for i in result:
# # 分割数据并添加到列表中
tmp_list = i.split('|')
# 因为查询结果中出发站和到达站为站名的缩写字母,所以需要在车站库中找到对应的车站名称
from_station = list(stations.keys())[list(stations.values()).index(tmp_list[6])]
to_station = list(stations.keys())[list(stations.values()).index(tmp_list[7])]
# 创建座位数组,由于返回的座位数据中含有空既“”,所以将空改成--这样好识别
seat = [tmp_list[3], from_station, to_station, tmp_list[8], tmp_list[9], tmp_list[10]
, tmp_list[32], tmp_list[31], tmp_list[30], tmp_list[21]
, tmp_list[23], tmp_list[33], tmp_list[28], tmp_list[24], tmp_list[29], tmp_list[26]]
newSeat = []
# 循环将座位信息中的空既“”,改成--这样好识别
for s in seat:
if s == "":
s = "--"
else:
s = s
newSeat.append(s) # 保存新的座位信息
data.append(newSeat)
return data # 返回整理好的车次信息
说明:因为返回的Json信息顺序比较零乱,所以在获取指定的数据时通过tmp_list分割后的列表将数据与浏览器余票查询页面中的数据逐个对比后,才能找出数据所对应的位置。通过对比后找到的数据位置如下:
'''5-7 目的地 3 车次 6 出发地 8 出发时间 9 到达时间 10 历时 26 无坐 29 硬座
24 软座 28 硬卧 33 动卧 23 软卧 21 高级软卧 30 二等座 31 一等座 32 商务座特等座
'''
数字为数据分割后tmp_list的索引值。
4)依次创建获取高铁信息、移除高铁信息、获取动车信息、移除动车信息、获取直达信息、移除直达信息、获取特快信息、移除特快信息、获取快速信息及移除快速信息的方法。这些方法用于车次分类数据的处理,代码如下:
# 获取高铁信息的方法
def g_vehicle():
if len(data) != 0:
for g in data: # 循环所有火车数据
i = g[0].startswith('G') # 判断车次首字母是不是高铁
ifi: # 如果是将该条信息添加到高铁数据中
type_data.append(g)
# 移除高铁信息的方法
def r_g_vehicle():
if len(data) != 0 and len(type_data) != 0:
for g in data:
i = g[0].startswith('G')
ifi: # 移除高铁信息
type_data.remove(g)
# 获取动车信息的方法
def d_vehicle():
if len(data) != 0:
for d in data: # 循环所有火车数据
i = d[0].startswith('D') # 判断车次首字母是不是动车
ifi == True: # 如果是将该条信息添加到动车数据中
type_data.append(d)
# 移除动车信息的方法
def r_d_vehicle():
if len(data) != 0 and len(type_data) != 0:
for d in data:
i = d[0].startswith('D')
ifi == True: # 移除动车信息
type_data.remove(d)
'''由于代码几乎相同,此处省略部分代码可在源码中进行查询
省略.........
'''
# 获取快速车数据的方法
def k_vehicle():
if len(data) != 0:
for k in data: # 循环所有火车数据
i = k[0].startswith('K') # 判断车次首字母是不是快车
ifi == True: # 如果是将该条信息添加到快车数据中
type_data.append(k)
# 移除快速车数据的方法
def r_k_vehicle():
if len(data) != 0 and len(type_data) != 0:
for k in data:
i = k[0].startswith('K')
ifi == True: # 移除快车信息
type_data.remove(k)
2 在主窗体中显示查票信息
完成了车票信息查询请求的文件后,接下来需要将获取的车票信息显示在快手爬票的主窗体当中。具体实现步骤如下:
(1)打开window.py文件,导入PyQt5.QtCore模块中的Qt类,然后导入PyQt5.QtWidgets模块与PyQt5.QtGui模块下的所有方法,再导入query_request文件中的所有方法。代码如下:
from PyQt5.QtCore import Qt # 导入Qt类
from PyQt5.QtWidgets import * # 导入对应模块的所有方法
from query_request import *
from PyQt5.QtGui import *
(2)在setupUi()方法中找到用于显示车票信息的tableView表格控件。然后为该控件设置相关属性,关键代码如下:
# 显示车次信息的列表
self.tableView = QtWidgets.QTableView(self.centralwidget)
self.tableView.setGeometry(QtCore.QRect(0, 320, 960, 440))
self.tableView.setObjectName("tableView")
self.model = QStandardItemModel(); # 创建存储数据的模式
# 根据空间自动改变列宽度并且不可修改列宽度
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# 设置表头不可见
self.tableView.horizontalHeader().setVisible(False)
# 纵向表头不可见
self.tableView.verticalHeader().setVisible(False)
# 设置表格内容文字大小
font = QtGui.QFont()
font.setPointSize(10)
self.tableView.setFont(font)
# 设置表格内容不可编辑
self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 垂直滚动条始终开启
self.tableView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
(3)导入time模块,该模块提供了用于处理时间的各种方法。然后在代码块的最外层创建get_time()方法用于获取系统的当前日期,再创建is_valid_date()方法用于判断输入的日期是否是一个有效的日期字符串,代码如下:
import time
# 获取系统当前时间并转换请求数据所需要的格式
def get_time():
# 获取当前时间的时间戳
now = int(time.time())
# 转换为其他日期格式,如:"%Y-%m-%d %H:%M:%S"
timeStruct = time.localtime(now)
strTime = time.strftime("%Y-%m-%d", timeStruct)
return strTime
defis_valid_date(str):
'''判断是否是一个有效的日期字符串'''
try:
time.strptime(str, "%Y-%m-%d")
return True
except:
return False
(4)依次创建change_G、change_D、change_Z、change_T、change_K()方法,以上方法均为车次分类复选框的事件处理,由于代码几乎相同,此处提供关键代码如下:
# 高铁复选框事件处理
def change_G(self, state):
# 选中将高铁信息添加到最后要显示的数据当中
if state == QtCore.Qt.Checked:
# 获取高铁信息
g_vehicle()
# 通过表格显示该车型数据
self.displayTable(len(type_data), 16, type_data)
else:
# 取消选中状态将移除该数据
r_g_vehicle()
self.displayTable(len(type_data), 16, type_data)
(5)创建checkBox_default()方法,该方法用于将所有车次分类复选框取消勾选,代码如下:
def checkBox_default(self):
self.checkBox_G.setChecked(False)
self.checkBox_D.setChecked(False)
self.checkBox_Z.setChecked(False)
self.checkBox_T.setChecked(False)
self.checkBox_K.setChecked(False)
(6)创建messageDialog()方法,用于显示主窗体非法操作的消息提示框;创建displayTable()方法,用于显示车次信息的表格与内容。代码如下:
# 显示消息提示框,参数title为提示框标题文字,message为提示信息
def messageDialog(self, title, message):
msg_box = QMessageBox(QMessageBox.Warning, title, message)
msg_box.exec_()
# 显示车次信息的表格
# train参数为共有多少列车,该参数作为表格的行。
# info参数为该次列车的具体信息,例如有座、无座、卧铺等。该参数作为表格的列
def displayTable(self, train, info, data):
self.model.clear()
for row in range(train):
for column in range(info):
# 添加表格内容
item = QStandardItem(data[row][column])
# 向表格存储模式中添加表格具体信息
self.model.setItem(row, column, item)
# 设置表格存储数据的模式
self.tableView.setModel(self.model)
(7)创建on_click()方法,该方法是查询按钮的单击事件。在该方法中首先获取出发地、目的地与出发日期三个编辑框的输入内容,然后对三个编辑框中输入的内容进行合法检测,符合规范后调用query()方法提交车票查询的请求并且将返回的数据赋值给data,最后通过调用displayTable()方法实现在表格中显示车票查询的全部信息。代码如下:
# 查询按钮的单击事件
def on_click(self):
get_from = self.textEdit.toPlainText() # 获取出发地
get_to = self.textEdit_2.toPlainText() # 获取到达地
get_date = self.textEdit_3.toPlainText() # 获取出发时间
# 判断车站文件是否存在
ifisStations() == True:
stations = eval(read()) # 读取所有车站并转换为dic类型
# 判断所有参数是否为空,以及出发地、目的地、出发日期
if get_from != "" and get_to != "" and get_date != "":
# 判断输入的车站名称是否存在,以及时间格式是否正确
if get_from in stations and get_to in stations and is_valid_date(get_date):
# 获取输入的日期是当前年初到现在一共过了多少天
inputYearDay = time.strptime(get_date, "%Y-%m-%d").tm_yday
# 获取系统当前日期是当前年初到现在一共过了多少天
yearToday = time.localtime(time.time()).tm_yday
# 计算时间差,也就是输入的日期减掉系统当前的日期
timeDifference = inputYearDay - yearToday
# 判断时间差为0时证明是查询当前的查票
# 以及29天以后的车票,12306官方要求只能查询30天以内的车票
if timeDifference >= 0 and timeDifference <= 28:
# 在所有车站文件中找到对应的参数
from_station = stations[get_from] # 出发地
to_station = stations[get_to] # 目的地
# 发送查询请求,并获取返回的信息
data = query(get_date, from_station, to_station)
self.checkBox_default() # 调用取消勾选所有车次分类复选框
if len(data) != 0: # 判断返回的数据是否为空
# 如果不是空的数据就将车票信息显示在表格中
self.displayTable(len(data), 16, data)
else:
self.messageDialog('警告', '没有返回的网络数据!')
else:
self.messageDialog('警告', '超出查询日期的范围内,'
'不可查询昨天的车票信息,以及29天以后的车票信息!')
else:
self.messageDialog('警告', '输入的站名不存在,或日期格式不正确!')
else:
self.messageDialog('警告', '请填写车站名称!')
else:
self.messageDialog('警告', '未下载车站查询文件!')
(8)在retranslateUi()方法中,首先设置出发日期的编辑框中显示系统的当前日期,然后设置查询按钮的单击事件,最后分别设置高铁、动车、直达、特快以及快车复选框选中与取消事件。关键代码如下:
self.textEdit_3.setText(get_time()) # 出发日期显示当天日期
self.pushButton.clicked.connect(self.on_click) # 查询按钮指定单击事件的方法
self.checkBox_G.stateChanged.connect(self.change_G) # 高铁选中与取消事件
self.checkBox_D.stateChanged.connect(self.change_D) # 动车选中与取消事件
self.checkBox_Z.stateChanged.connect(self.change_Z) # 直达车选中与取消事件
self.checkBox_T.stateChanged.connect(self.change_T) # 特快车选中与取消事件
self.checkBox_K.stateChanged.connect(self.change_K) # 快车选中与取消事件
(9)在window.py文件下,单击右键,选择“Run 'window'”菜单运行主窗体,然后输入符合规范的出发地、目的地与出发日期,单击查询按钮将显示如图35所示。
图35 显示查询的查票信息