上一篇文章已经生成了界面设计的py文件,下面通过代码来调试主窗体中各控件的细节处理,以及对应的属性。
(1)打开window.py文件,在右侧代码区域的setupUi()方法中自改主窗体的最大值与最小值,用于保持主窗体大小不变,无法扩大或缩小。代码如下:
(2)将图片资源img文件夹复制到该项目中,然后导入PyQt5.QtGui模块中的QPalette、QPixmap、QColor用于对控件设置背景图片,为对象名label_title_img的Label控件设置背景图片,该控件用于显示顶部图片。代码如下:
(3)设置查询部分widget控件的背景图片,该控件起到容器的作用,在设置背景图片时并没有Label控件那么简单。首先需要为该控件开启自动填充背景功能,然后创建调色板对象,指定调色板背景图片,最后为控件设置对应的调色板即可。代码如下:
(4)通过diamante修改窗体或控件文字时,需要在retranslateUI()方法中进行设置,代码如下:
(5)导入sys模块 ,然后在代码块的最外层创建show_MainWindow()方法,该方法用于显示窗体。代码如下:
(6)在代码块的最外层模拟Python的程序入口,然后调用显示窗体的show_MainWindow()方法。代码如下:
既然是爬票程序,那么一定需要一个爬取的对象,就以12306中国铁路客户服务中心所提供的查票地址来获取火车票的相关信息。在发送请求时,地址中需要填写必要的参数,否则后台无法返回前台所需要的正确信息,所以首先需要分析网页请求参数,步骤如下:
(1)使用火狐浏览器打开12306官网(http://www.12306.cn/),输入出发地和目的地,然后单击查询按钮。快捷键Ctrl+shift+E打开网络监视器。
(2)单机网络请求显示请求细节的窗口,在该窗口中默认会显示消息头的相关数据,此处可以获取完整的请求地址。
(3)在请求地址的上方选择参数选项,将显示该地址中必要参数
得到了请求地址与请求参数后,可以发现请求参数中的出发地和目的地均为车站名的英文缩写。而这个英文缩写的字母是通过输入中文车站名转换而来的,所以需要在网页中仔细查找是否有将车站名自动转换为英文缩写的请求信息。
(1)关闭并重新打开网络监视器,然后按快捷键F5进行余票查询网页的的刷新,此时在网络监视器中选择类型为js的网络请求。在文件类型中仔细分析文件内容是否有,与车站名相关的信息如下:
(2)选中与车站名相关的网络请求,在请求细节中找到该请求的完整地址。然后再网页中打开改地址测试返回数据。
(3)打开pycharm开发工具,在check tickets目录的右键菜单中选择new-Python File命令,创建一个名称为get_stations.py的文件,然后再菜单栏中选择File->Default Settings菜单项,安装requests模块。
(4)在get_stations.py文件中分别导入requests、re以及os模块,然后创建getStation()方法,该方法用于发送获取地址信息的网络请求,并将返回的数据转换为需要的类型。代码如下:
(5)分别创建 write()、read()以及isStation()方法,分别用于写入文件、读取文件以及判断车站文件是否存在,代码如下:
(6)打开window.py文件,首先导入get_stations文件下的所有方法,然后在模拟Python的程序入口处修改代码。首先判断是否有所有车站信息的文件,如果没有该文件,就下载车站信息文件,然后显示窗体,如果有,将直接显示窗体。修改后代码如下:
(7)在window.py文件右键菜单中选择Run Window命令,运行主窗体,主窗体界面显示后再my_pytorch目录下将自动下载station.text文件,改文件可以实现车站名称与对应的英文缩写的转换。
得到了获取车票信息的网络请求地址,然后又分析出请求地址的必要参数以及车站名称转换的文件。接下来就需要将主窗体中输入的出发地、目的地以及出发日3个重要参数配置到查票的请求地址中,然后分析并接受所查询车票的对应信息。
(1)在浏览器中粘贴我们复制的完整查票地址。注意区分,这个是带日期的,和第一个不同。
(2)发现可用数据后,在项目中创建query_request.py文件,在该文件中首先导入get_stations文件下的所有方法,然后分别创建名称为data与type_data的列表(list),分别用于保存整理好的车次信息与分类后的车次信息。代码如下:
从返回的加密信息中可以看出信息很乱,所以需要创建data=[]列表(list)来保存后期整理好的车次信息,然后需要将车次分类,如高铁、动车等,所以需要创建type_data=[]列表(list)来保存分类后的车次信息。
(3)创建query()方法,在调用该方法时需要3个参数,分别为出发日期、出发地以及目的地。然后创建查询请求的完整地址并通过format()方法为地址进行格式化。再将返回的json数据转换为字典类型,最后通过字典类型键值的方法取出对应的数据并进行整理与分类。代码如下:
def query(date, from_station, to_station):
data.clear() # 清空数据
# 查询请求地址
cookie='''_uab_collina=162924939911126111496577; JSESSIONID=CEEECF63AAAFADCE84C14B125B0BAF33; BIGipServerotn=569377290.38945.0000; RAIL_EXPIRATION=1629515371138; RAIL_DEVICEID=WFdNvPKASOeg84FkzqmTwo2V9HOZ6PCi13Lo07y6AhvsDwxtH1RABivB_bw-vQMlxSMsGwtFKOTfbxEc62F2DgkmiF_j_Hlgro1ClJXVYItFRoWurt7-HkK3S634C9mkoLsQfZk9xvy_Y1_-sbJCQvt8VE1XIoU1; BIGipServerpool_passport=199492106.50215.0000; route=9036359bb8a8a461c164a04f8f50b252; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_toStation=%u4E0A%u6D77%2CSHH; _jc_save_fromDate=2021-08-19; _jc_save_toDate=2021-08-18; _jc_save_wfdc_flag=dc'''
header={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0',
'Connection':'keep-alive',
'accept':'*/*',
'Cookie':cookie
}
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=header)
# # 将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 # 返回整理好的车次信息
(4)一次创建获取高铁信息、移除高铁信息、获取动车、移除动车、获取直达、移除直达、获取特快、移除特快、获取快速以及移除快速的方法。以上方法用于车次分类数据的处理,代码如下:
# 获取高铁信息的方法
def g_vehicle():
if len(data) != 0:
for g in data: # 循环所有火车数据
i = g[0].startswith('G') # 判断车次首字母是不是高铁
if i: # 如果是将该条信息添加到高铁数据中
type_data.append(g)
#移除高铁信息的方法
def r_g_vehicle():
if len(data) != 0:
for g in data:
i = g[0].startswith('G')
if i: #移除高铁信息
type_data.remove(g)
# 获取动车信息的方法
def d_vehicle():
if len(data) != 0:
for d in data: # 循环所有火车数据
i = d[0].startswith('D') # 判断车次首字母是不是动车
if i == True: # 如果是将该条信息添加到动车数据中
type_data.append(d)
# 移除动车信息的方法
def r_d_vehicle():
if len(data) != 0:
for d in data:
i = d[0].startswith('D')
if i == True: #移除动车信息
type_data.remove(d)
# 获取直达车信息的方法
def z_vehicle():
if len(data) != 0:
for z in data: # 循环所有火车数据
i = z[0].startswith('Z') # 判断车次首字母是不是直达
if i == True: # 如果是将该条信息添加到直达数据中
type_data.append(z)
# 移除直达车信息的方法
def r_z_vehicle():
if len(data) != 0:
for z in data:
i = z[0].startswith('Z')
if i == True: # 移除直达车信息
type_data.remove(z)
# 获取特快车信息的方法
def t_vehicle():
if len(data) != 0:
for t in data: # 循环所有火车数据
i = t[0].startswith('T') # 判断车次首字母是不是特快
if i == True: # 如果是将该条信息添加到特快车数据中
type_data.append(t)
# 移除特快车信息的方法
def r_t_vehicle():
if len(data) != 0:
for t in data:
i = t[0].startswith('T')
if i == True: # 移除特快车信息
type_data.remove(t)
# 获取快速车数据的方法
def k_vehicle():
if len(data) != 0:
for k in data: # 循环所有火车数据
i = k[0].startswith('K') # 判断车次首字母是不是快车
if i == True: # 如果是将该条信息添加到快车数据中
type_data.append(k)
# 移除快速车数据的方法
def r_k_vehicle():
if len(data) != 0:
for k in data:
i = k[0].startswith('K')
if i == True: # 移除快车信息
type_data.remove(k)
完成了车票信息查询请求的文件后,接下来需要将获取的车票信息显示在快手爬票的主窗体当中。实现步骤如下:
(1)打开window.py文件,导入PyQt5.QtCore模块中的Qt类,然后导入PyQt5.QtWidgets与PyQt5.QtGui模块下的所有方法,再导入query_request文件中的所有方法即可。代码如下:
(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()方法用于判断输入的日期是否是一个有效的日期字符串,代码如下:
(4)依次创建change_G()、change_D()、change_Z()、change_T()、change_K()方法,以上方法均为车次分类复选框的事件处理,由于代码几乎相同,此处提供的关键代码如下:
(5)创建messageDialog()方法,该方法用于显示主窗体非法操作的消息提示框。然后创建displayTable()方法,该方法用于显示车次信息的表格与内容。代码如下:
(6)创建on_click()方法,该方法是查询按钮的单机事件。在该方法中首先要获取出发地、目的地与出发日期3个编辑框的输入内容,然后对3个编辑框中输入 的内容进行合法监测,符合规范后调用query()方法提交车票查询的请求并且将返回的数据赋值给data,最后通过调用displayTable()方法实现在表格中显示车票查询的全部信息。代码如下:
# 查询按钮的单击事件
def on_click(self):
get_from = self.textEdit.toPlainText() # 获取出发地
get_to = self.textEdit_2.toPlainText() # 获取到达地
get_date = self.textEdit_3.toPlainText() # 获取出发时间
# 判断车站文件是否存在
if isStations() == 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] # 目的地
print(from_station)
data = query(get_date, from_station, to_station) # 发送查询请求,并获取返回的信息
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('警告', '未下载车站查询文件!')
(7)在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) #快车选中与取消事件
(8)在Window.py文件右键菜单中选择Run window命令,运行主窗体,然后输入符合规范的出发地、目的地与出发日期,单击“查询”按钮,显示查询的余票信息。