python爬取12306(⽕车票分析助⼿)
说明⽂字:
1.本项⽬只是⼀个练习,熟悉python爬⾍技术,没有任何⽤途
2.最后运⾏的结果有时候会成功,有时候会显⽰错误界⾯,如下图所⽰。因为12306怎么可能允许你⼀直爬它呢
开发⼯具准备:
开发⼯具:PyCharm
内置模块:sys,time,datetime,os,json,re
第三⽅模块:PyQt5,pyqt5-tools,requests,matplotlib
准备⼯作:
下载数据⽂件:(车站名称⽂件)和(起售时间⽂件)
创建get_⽂件:
import json
import re #
通过正则表达式匹配处理相应的字符串
import os #
判断某个路径下的某个⽂件
import requests #
处理⽹络请求
def get_selling_time():
url='/index/script/core/common/qss_'
response=requests.get(url,verify=True) #
请求并进⾏验证
print(response.text)
print(type(response.text))
json_str=re.findall('{[^}]+}',response.text) #
匹配括号内所有内容
print(json_str)
time_js=json.loads(json_str[0]) #JSO
解析数据
print(time_js)
write(str(time_js),'') #
调⽤写⼊⽅法
def get_station():
#
发送请求获取所有车站名称,通过输⼊的站名称转化查询地址的参数
url='/otn/resources/js/framework/station_?station_version=1.9151'
response=requests.get(url,verify=True) #
请求并进⾏验证
#print()
print(type(response.text))
#[u4e00-u9fa5]+
表⽰匹配给定字符中任意⼀个汉字
stations=re.findall('([u4e00-u9fa5]+)|([A-Z]+)',response.text) #
获取需要的车站名称
#print(stations)
#print(type(stations))
stations=dict(stations) #
转换为字典
#print(stations)
stations=str(stations) #
转换为字符串类型否则⽆法写⼊⽂件
#print(stations)
write(stations, '') #
调⽤写⼊⽅法
#
写⼊⽂件
def write(stations,file_name):
file=open(file_name,'w',encoding='utf_8_sig') #
以写模式打开⽂件
file.write(stations) #
写⼊数据
file.close()
#
读⽂件
def read(file_name):
file=open(file_name,'r',encoding='utf_8_sig') #
以读模式打开⽂件
data=file.readline() #
读取⽂件
file.close()
return data
#
判断⽂件是否存在
def is_stations(file_name):
is_stations=os.path.exists(file_name)
return is_stations
if __name__=='__main__':
if is_stations('') is False:
get_station() #
下载所有车站⽂件
if is_stations('') is False:
get_selling_time() #
下载起售时间⽂件
在PyCharm中设置PyQt5⼯具:
Designer:主要进⾏主窗体的UI设计,最后保存的是ui⽂件
⼯具:将.ui⽂件转成.py⽂件
⼯具:将.qrc⽂件转成.py⽂件
配置链接:
⽤Qt Designer设计的主窗体效果图:
项⽬结构:
img_resources⽂件夹主要是图⽚资源⽂件,主窗体UI设计⽤到的两个.png图⽚和⼀个.qrc图⽚资源⽂件
ui⽂件夹保存的是Qt Designer设计的窗体ui⽂件
是⽤Pyuic⼯具转换的
img_是⽤Pyrcc⼯具转换的
get_是下载车站名称与起售时间代码,运⾏之后会出现(车站名称⽂件)和(起售时间⽂件)
最重要的就是:query_查询⽹络请求代码 以及 show_显⽰与控制窗体代码。
项⽬说明:根据上⾯设计的主窗体效果图,可以看出来,该项⽬主要分为三⼤模块:车票查询、卧铺售票分析、车票起售时间。第⼆
个模块最复杂,所以先说简单的两个模块
**** * ***
⼩⼩提⽰:以下内容把我⾃⼰⼿写总结的图和代码配合起来看,⽐较容易理解
模块⼀:车票查询:
下⾯这个图是我整理的车票查询的步骤:
关键代码如下:
1. 在show_⽂件中创建on_click()⽅法,在该⽅法中:⾸先获取输⼊的内容,然后进⾏参数审核,接着发送查询请求调⽤
query()⽅法,最后将查询结果显⽰在窗体表格中(调⽤displayTable()⽅法)。
#
主窗体的查询按钮
def on_click(self):
get_from=self.textEdit.toPlainText() #
获取出发地
get_to=self.textEdit_2.toPlainText() #
获取到达地
get_date=self.textEdit_3.toPlainText() #
获取出发时间
#
判断车站⽂件是否存在
if is_stations('') is True:
stations=eval(read('')) #
读取所有车站并转换为字典类型
#
判断所有参数是否为空
if get_from!="" and get_to!="" and get_date!="":
#
判断输⼊的车站名称是否都存在,以及时间格式是否正确
if get_from in stations and get_to in stations and self.is_valid_date(get_date):
#
计算时间差
time_difference=self.time_difference(self.get_time(),get_date).days
# 1230630
官⽅要求智能查询天以内的车票
if 0 <= time_difference <= 29:
#
在所有车站⽂件中到对应的参数、出发地
from_station=stations[get_from]
#print(from_station)
to_station=stations[get_to]
#print(to_station)
#
发送查询请求,并获取返回信息
data=query(get_date,get_from,from_station,get_to,to_station)
print('正确1')
self.checkBox_default()
if len(data) !=0:
#
将车票信息显⽰到表格中
print('正确2')
self.displayTable(len(data),16,data)
else:
messageDialog('警告','没有返回的⽹络数据!')
else:
messageDialog('警告', '超出查询⽇期的范围!')
else:
messageDialog('警告', '输⼊的站名不存在,或⽇期格式不正确!')
else:
messageDialog('警告', '请填写车站名称!')
else:
messageDialog('警告', '未下载车站查询⽂件!')
#titlemessage
消息提⽰框,参数为提⽰框标题⽂字,为提⽰信息
def messageDialog(title,message):
msg_box=QMessageBox(QMessageBox.Warning,title,message)
msg_box.exec_()
2. 在query_⽂件中创建query()⽅法,该⽅法需要三个参数:出发⽇期、出发地、⽬的地。在该⽅法中,查询请求地址是通
过format()⽅法对地址进⾏格式化。由于获取到的JSO信息⽐较乱,所以在获取指定数据时通过split()进⾏分割,通过与浏览器余票
查询页⾯中的数据逐个对⽐出数据所对应的位置。
5-7 ⽬的地 3 车次 6 出发地 8 出发时间 9 到达时间 10 历时 26 ⽆坐 29 硬座
24 软座 28 硬卧 33 动卧 23 软卧 21 ⾼级软卧 30 ⼆等座 31 ⼀等座 32 商务座特等座
data=[] #
保存整理好的车次信息
type_data=[] #
保存分类后的车次信息(如⾼铁,动车等)
def query(date,get_from,from_station,get_to,to_station):
data.clear() #
清空数据
type_data.clear()
#
查询请求地址
url = '/otn/leftTicket/query?_date={}&_station={}&_station={}&purpose_codes
=ADULT'.format(date, from_station, to_station)
response=requests.get(url,headers=header,verify=False) #
发送查询请求
if response.text.startswith(u'ufeff'):
response.text = response.text.encode('utf8')[3:].decode('utf8')
response.encoding = 'utf-8'
print(response.url)
result = json.loads(response.text)
result=result['data']['result']
#
判断车站⽂件是否存在
if is_stations('') :
stations=eval(read('')) #
读取所有车站并转换为字典类型
if len(stations)!=0: #
判断返回数据是否为空
for i in result:
tmp_list=i.split('|') #
分割数据并添加到列表中
print(tmp_list)
#
因为查询结果中出发站和到达站为站名的缩写字母,所以需要在车站库中到对应的车站名称
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]]
print(seat)
newSeat=[]
#“--”
循环将座位信息中的空值改成
for s in seat:
if s=="":
s="--"
else:
s=s
newSeat.append(s) #
保存新的座位信息
data.append(newSeat)
print(newSeat)
return data #
返回整理好的车次信息
3. 在show_⽂件中创建displayTable()⽅法,⽤于将车票信息显⽰到主窗体的表格中。
#
显⽰车次信息的表格
#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)
4. on_click()⾥⾯调⽤的⼀些基本⽅法如下(很好理解):_valid_date()判断输⼊的⽇期是否合法;_difference()计算
两个⽇期相差的天数;_time()获取当前⽇期;ox_default()车次类型的复选框取消勾选。
#
判断是否是⼀个有效的⽇期字符串
def is_valid_date(self, str):
try:
time.strptime(str, "%Y-%m-%d")
return True
except:
return False
#
获取系统当前时间并转换请求数据所需要的格式
def get_time(self):
#
获得当前时间时间戳
now = int(time.time())
# ,:"%Y-%m-%d %H:%M:%S"
转换为其它⽇期格式如
timeStruct = time.localtime(now)
strTime = time.strftime("%Y-%m-%d", timeStruct)
return strTime
# 29
计算购票时间差,因为只能提前购买天的车票
def time_difference(self, in_time, new_time):
# struct_time
将字符串⽇期转换为时间对象
in_time = time.strptime(in_time, "%Y-%m-%d")
new_time = time.strptime(new_time, "%Y-%m-%d")
# struct_timedatetime
将时间对象转换为对象
in_time = datetime.datetime(in_time[0], in_time[1], in_time[2])
new_time = datetime.datetime(new_time[0], new_time[1], new_time[2])
#
返回两个变量相差的值,就是相差天数
return new_time - in_time
#
将所有车次分类复选框取消勾选
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)
运⾏结果如下图:(运⾏不出来的话,也别担⼼,我也只是偶尔⼀次运⾏成功了。这个图⽚是⽹上到的,就是让⼤家看看结果是什么样
⼦,有便于更好的理解代码,⼼态放好哈哈哈哈哈)
模块⼆:车票起售时间查询
这个模块⽐较简单些,我整理的步骤如下:
1. 在show_中创建query_time_click()⽅法,查询并显⽰车票起售时间。⾸先调⽤query_time()⽅法查询起售车站对应的站
名与起售时间,然后将⽹格布局清空,接着创建控件并设置属性,最后加载控件并且显⽰
#
车票起售时间查询按钮的事件处理
def query_time_click(self):
station=self.lineEdit.text() #
获取需要查询的起售车站
#print(station)
stations_time=eval(read('')) #
读取所有车站与起售时间并转换为字典类型
stations=eval(read('')) #
读取所有车站并转换为字典类型
if station in stations_time:
#
查询起售车站对应的站名与起售时间
name_lit,time_list=query_time(stations.get(station))
#
每次点循环删除管理器的控件
if self.gridLayout.count()!=0:
while self.gridLayout.count():
item=self.gridLayout.takeAt(0) #
获取第⼀个控件
widget=item.widget() #
删除控件
widget.deleteLater()
i=-1 #
⾏数标记
for n in range(len(name_lit)):
x=n % 4 # x 012,3 4
确定每⾏显⽰的个数,,每⾏个
# x0 +1
当为的时候设置换⾏⾏数
if x==0:
i+=1
self.widget=QtWidgets.QWidget() #
创建布局
self.widget.setObjectame("widget"+str(n)) #
给布局命名
#
设置布局样式
self.widget.setStyleSheet('QWidget#' + "widget" + str(n) + "{border:2px solid rgb(175, 175, 175);background-color: rgb(255, 255, 255);}")
# Qlabel QWidget
创建个控件⽤于显⽰图⽚设置控件在中
self.label=QtWidgets.QLabel(self.widget)
self.label.setAlignment(QtCore.Qt.AlignCenter)
#
设置⼤⼩
self.label.setGeometry(QtCore.QRect(10,10,210,65))
font=QtGui.QFont() #
创建字体对象
font.setPointSize(11) #
设置字体⼤⼩
font.setBold(True) #
开启粗体属性
font.setWeight(75) #
设置⽂字粗细
self.label.setFont(font) #
设置字体
#
设置显⽰站名与起售时间
self.label.setText(name_lit[n]+' '+time_list[n])
# widegtgridLayout ix
把动态创建的布局添加到中,分别代表:⾏数以及每⾏的个数
self.gridLayout.addWidget(self.widget,i,x)
# 300
设置⾼度为动态⾼度根据⾏数确定⾼度每⾏
self.scrollAreaWidgetContents_2.setMinimumHeight((i+1)*100)
#
设置⽹格布局控件动态⾼度
self.gridLayoutWidget.setGeometry(QtCore.QRect(0,0,950,((i+1)*100)))
2. 在query_中⾸先创建两个⽤于保存车站名称与起售时间的列表,然后创建query_time()⽅法,⽤于发送查询车票起售时间
的⽹络请求,这⾥是post请求,⽽且会⽤到表单参数{“station_telecode”:station}。将返回的信息添加⾄对应的列表当中,最后将
两个列表信息返回。
station_name_list=[] #
保存起售车站名称列表
station_time_list=[] #
保存起售车站对应时间列表
#
查询车票起售时间
def query_time(station):
station_name_list.clear()
station_time_list.clear()
#
读取所有车站并转换为字典类型
stations=eval(read(''))
url='/index/otn/index12306/queryScSname'
#station
表单参数,参数为需要搜索车站的英⽂缩写
form_data={"station_telecode":station}
response=requests.post(url,data=form_data,verify=True) #
请求并进⾏验证
response.encoding='utf-8' #
对请求所返回的数据进⾏编码
json_data=json.loads(response.text) # json
解析数据
data=json_data.get('data') # json
获取中可⽤数据,也就是查询车站所对应的站名
for i in data: #
遍历查询车站所对应的所有站名
if i in stations: #
在站名时间⽂件中,判断是否存在该站名
station_name_list.append(i) #
有该站名就将站名添加⾄列表中
for name in station_name_list: #
遍历筛选后的站名
time=stations.get(name) #
通过站名获取对应的时间
station_time_list.append(time) #
将时间保存⾄列表
return station_name_list,station_time_list
运⾏结果如图:(这个是可以运⾏出来的,因为它和模块⼀车票查询⽤到的url是不⼀样的,不会对12306造成损失。如果运⾏不出结果,
就说明代码有问题了)
模块三:卧铺售票分析(⽐较难理解)
先给出我整理的复杂的步骤,看这密密⿇⿇的字就知道内容很多
主要分为两个部分:部分⼀是卧铺售票分析区域。第⼆个部分卧铺车票数量折线图。下⾯分别来说这两个部分:
模块三部分⼀:卧铺售票的查询与分析
1. 在show_⽂件中创建query_ticketing_analysis_click()⽅法,作为卧铺售票分析查询按钮的事件处理⽅法。该⽅法先获取
输⼊的出发地和⽬的地。再调⽤query_ticketing_analysis()⽅法分别查询今天、三天内、五天内的卧铺信息。
(query_ticketing_analysis()⽅法在后⾯的代码5)
#
卧铺售票分析查询按钮的事件处理⽅法
def query_ticketing_analysis_click(self):
self.info_table=[] #
保存窗体表格的车次信息
today_car_list.clear() #
清空今天列车信息,已处理是否有票
three_car_list.clear() #
清空三天列车信息,已处理是否有票
five_car_list.clear() #
清空五天列车信息,已处理是否有票
today_list.clear() #
清空今天列车信息,未处理是否有票
three_list.clear() #
清空三天列车信息,未处理是否有票
five_list.clear() #
清空五天列车信息,未处理是否有票
get_from=self.textEdit_analysis_from.toPlainText() #
获取出发地
get_to=self.textEdit_analysis_to.toPlainText() #
获取到达地
stations=eval(read('')) #
读取所有车站并转换为字典类型
if get_from!="" and get_to!="":
if get_from in stations and get_to in stations:
from_station=stations[get_from] #
在所有车站⽂件中到对应的参数,出发地
to_station=stations[get_to] #
⽬的地
today=datetime.datetime.now() #
获取当天⽇期
three_set=datetime.timedelta(days=+2) #
三天内偏移天数
five_set=datetime.timedelta(days=+4) #
五天内偏移天数
three_day=(today+three_set).strftime('%Y-%m-%d') #
三天格式化后的⽇期
five_day=(today+five_set).strftime('%Y-%m-%d') #
五天格式化后的⽇期
today=today.strftime('%Y-%m-%d') #
今天格式化后的⽇期
#
发送查询今天卧铺票信息的⽹络请求,并获取返回的信息
query_ticketing_analysis(today,from_station,to_station,1)
#
发送查询三天内卧铺票信息的⽹络请求,并获取返回的信息
query_ticketing_analysis(three_day,from_station,to_station,3)
#
发送查询五天内卧铺票信息的⽹络请求,并获取返回的信息
query_ticketing_analysis(five_day,from_station,to_station,5)
上⾯这个代码⽐较好理解。假如这⾥的today为2020-08-19,那么three_day就是2020-08-21,five_day就是2020-08-23。
2. 然后在query_ticketing_analysis_click()⽅法中,将所有车次信息进⾏整合及筛选。这⾥⽤到了集合set。因为集合最好的⼀个⽤途
就是去掉重复元素,集合中每个元素都是唯⼀的。主要是两个for循环
info_set=set() #
创建筛选车次集合,将相同车次进⾏整合,查看共有⼏趟列车
for i in today_car_list+three_car_list+five_car_list:
#
因为在集合中必须是字符串才能整合,所以将车次信息转换为字符串类型,⽅便车次整合
info_set.add(str(i[0:6]))
for info in info_set: #
遍历车次信息
info=eval(info) #
将车次信息再次转换成列表
is_today_ture=False #
判断今天是否存在某趟列车的标记
for i in today_car_list: #
遍历今天的车次信息,该车次信息是没有筛选的信息
if info[0] in i: #
判断整合后的车次,在今天的车次信息中是否存在
is_today_ture=True #
存在就进⾏标记
info.append(i[6]) #
如果存在就将车次信息中是否有卧铺的信息添加⾄整合后的车次信息中
break
if is_today_ture==False: # --
如果今天没有某⼀趟车信息就标记为
info.append('--')
is_three_true=False
for i in three_car_list:
if info[0] in i:
is_three_true=True
info.append(i[6])
break
if is_three_true==False:
info.append('--')
is_five_true=False
for i in five_car_list:
if info[0] in i:
is_five_true=True
info.append(i[6])
break
if is_five_true==False:
info.append('--')
self.info_table.append(info) #
将最后的结果添加⾄窗体表格的列表中
这⾥可能⽐较难理解⼀些!有的⼈可能会问为什么要整合筛选车次信息呢?怎么整合的呢?举个例⼦吧,⽐较好理解⼀些。
假如上⾯1的代码调⽤query_ticketing_analysis()⽅法之后today_car_list,three_car_list,five_car_list的结果分别是:
today_car_list=[['k105','北京西','深圳','23:18','04:20','29:02','⽆']]
three_car_list=[['k105','北京西','深圳','23:18','04:20','29:02','有'],
['z313','北京','深圳','08:30','12:50','28:20','有'],]
five_car_list=[['k105','北京西','深圳','23:18','04:20','29:02','有'],
['z313','北京','深圳','08:30','12:50','28:20','有'],]
上⾯三个列表关于车次k105的⼀些基本信息(车次、出发地、⽬的地、出发时间、到达时间、历时)重复了三次,有些累赘;⽽车次z313
重复了两次。这些重复信息只出现⼀次就可以了,所以要⽤set集合对他们进⾏整合筛选。
for i in today_car_list+three_car_list+five_car_list:
#
因为在集合中必须是字符串才能整合,所以将车次信息转换为字符串类型,⽅便车次整合
info_set.add(str(i[0:6]))
如果是同⼀个车次,那么today_car_list,three_car_list,five_car_list三个列表中前6个元素(i[0]到i[5])的内容都是相同的。这是第⼀
个for循环,每次将i[0]到i[5]的内容(也就是车次、出发地、⽬的地、出发时间、到达时间、历时)转成字符串,再添加到集合⾥,可以过
滤掉多余的重复信息。
也就是说today_car_list,three_car_list,five_car_list三个列表的最后⼀个 ‘有’或’⽆’ 不在info_set⾥⾯。这时候info_set⾥的内
容就是下图所⽰,每个车次的基本信息只出现⼀次,⽽没有重复。
{"['k105', '北京西', '深圳', '23:18', '04:20', '29:02']",
"['z313', '北京', '深圳', '08:30', '12:50', '28:20']"}
代码2的第2个for循环是将today_car_list,three_car_list,five_car_list三个列表中的最后⼀个元素 ‘有’或’⽆’ 添加到info⾥⾯。
如果列表中没有该车次,就将’- -‘添加到info⾥⾯。就如today_car_list没有z313车次,所以在info⾥⾯z313车次的“今天”卧铺票信
息就是’- -’。执⾏第2个for循环后,info⾥的内容就是下图所⽰
[['k105', '北京西', '深圳', '23:18', '04:20', '29:02', '⽆', '有', '有'],
['z313', '北京', '深圳', '08:30', '12:50', '28:20', '--', '有', '有']]
3. 然后在query_ticketing_analysis_click()⽅法中,将已经筛选好的车次信息与对应卧铺是否有票信息显⽰在主窗体的表格中,然后对
车次与卧铺信息进⾏积分计算。
self.tableWidget.setRowCount(len(self.info_table)) #
设置表格⾏数
self.tableWidget.setColumnCount(9)
#
设置表格内容⽂字⼤⼩
font=QtGui.QFont()
font.setPointSize(12)
self.tableWidget.setFont(font)
#
根据窗体⼤⼩拉伸表格
self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
#
遍历最终的信息
for row in range(len(self.info_table)):
fraction=0 #
分数,根据该分数判断列车的紧张程度
for column in range(9):
if column==6: #
如果某趟列车当天⽆票
if self.info_table[row][column]=='⽆' or self.info_table[row][column]=='--':
fraction+=3 #
计三分
if column==7:
if self.info_table[row][column]=='⽆' or self.info_table[row][column]=='--':
fraction+=2
if column==8:
if self.info_table[row][column]=='⽆' or self.info_table[row][column]=='--':
fraction+=1
#5
分数⼤于或等于分的车次为红⾊,说明该车次卧铺⾮常紧张
if fraction>=5:
#
定位是哪趟车次符合该条件,遍历该车次信息
for i in range(len(self.info_table[row])):
#
表格中的信息
item=QtWidgets.QTableWidgetItem(self.info_table[row][i])
item.setBackground(QColor(255,0,0)); #
设置该车次背景颜⾊
self.tableWidget.setItem(row,i,item) #
设置表格显⽰的内容
#
橙⾊,说明改车次卧铺紧张
if 1 <= fraction <= 4:
#
定位是哪趟车次符合该条件,遍历该车次信息
for i in range(len(self.info_table[row])):
#
表格中的信息
item=QtWidgets.QTableWidgetItem(self.info_table[row][i])
item.setBackground(QColor(255,170,0)); #
设置该车次背景颜⾊
self.tableWidget.setItem(row,i,item) #
设置表格显⽰的内容
#
说明该车次卧铺不紧张
if fraction==0:
#
定位是哪趟车次符合该条件,遍历该车次信息
for i in range(len(self.info_table[row])):
#
表格中的信息
item=QtWidgets.QTableWidgetItem(self.info_table[row][i])
item.setBackground(QColor(85,170,0)); #
设置该车次背景颜⾊
self.tableWidget.setItem(row,i,item) #
设置表格显⽰的内容
根据该代码就可以得出k105得分是3分,属于卧铺紧张。z313也是3分,属于卧铺紧张。
4. query_ticketing_analysis_click()⽅法的整体代码如下
#
卧铺售票分析查询按钮的事件处理⽅法
def query_ticketing_analysis_click(self):
self.info_table=[] #
保存窗体表格的车次信息
self.info_table=[] #
保存窗体表格的车次信息
today_car_list.clear() #
清空今天列车信息,已处理是否有票
three_car_list.clear() #
清空三天列车信息,已处理是否有票
five_car_list.clear() #
清空五天列车信息,已处理是否有票
today_list.clear() #
清空今天列车信息,未处理是否有票
three_list.clear() #
清空三天列车信息,未处理是否有票
five_list.clear() #
清空五天列车信息,未处理是否有票
get_from=self.textEdit_analysis_from.toPlainText() #
获取出发地
get_to=self.textEdit_analysis_to.toPlainText() #
获取到达地
stations=eval(read('')) #
读取所有车站并转换为字典类型
if get_from!="" and get_to!="":
if get_from in stations and get_to in stations:
from_station=stations[get_from] #
在所有车站⽂件中到对应的参数,出发地
to_station=stations[get_to] #
⽬的地
today=datetime.datetime.now() #
获取当天⽇期
three_set=datetime.timedelta(days=+2) #
三天内偏移天数
five_set=datetime.timedelta(days=+4) #
五天内偏移天数
three_day=(today+three_set).strftime('%Y-%m-%d') #
三天格式化后的⽇期
five_day=(today+five_set).strftime('%Y-%m-%d') #
五天格式化后的⽇期
today=today.strftime('%Y-%m-%d') #
今天格式化后的⽇期
#
发送查询今天卧铺票信息的⽹络请求,并获取返回的信息
query_ticketing_analysis(today,from_station,to_station,1)
#
发送查询三天内卧铺票信息的⽹络请求,并获取返回的信息
query_ticketing_analysis(three_day,from_station,to_station,3)
#
发送查询五天内卧铺票信息的⽹络请求,并获取返回的信息
query_ticketing_analysis(five_day,from_station,to_station,5)
info_set=set() #
创建筛选车次集合,将相同车次进⾏整合,查看共有⼏趟列车
for i in today_car_list+three_car_list+five_car_list:
#
因为在集合中必须是字符串才能整合,所以将车次信息转换为字符串类型,⽅便车次整合
info_set.add(str(i[0:6]))
for info in info_set: #
遍历车次信息
info=eval(info) #
将车次信息再次转换成列表
is_today_ture=False #
判断今天是否存在某趟列车的标记
for i in today_car_list: #
遍历今天的车次信息,该车次信息是没有筛选的信息
if info[0] in i: #
判断整合后的车次,在今天的车次信息中是否存在
is_today_ture=True #
存在就进⾏标记
info.append(i[6]) #
如果存在就将车次信息中是否有卧铺的信息添加⾄整合后的车次信息中
break
if is_today_ture==False: # --
如果今天没有某⼀趟车信息就标记为
info.append('--')
is_three_true=False
for i in three_car_list:
if info[0] in i:
is_three_true=True
info.append(i[6])
break
if is_three_true==False:
info.append('--')
is_five_true=False
for i in five_car_list:
if info[0] in i:
is_five_true=True
info.append(i[6])
break
if is_five_true==False:
info.append('--')
info.append('--')
self.info_table.append(info) #
将最后的结果添加⾄窗体表格的列表中
self.tableWidget.setRowCount(len(self.info_table)) #
设置表格⾏数
self.tableWidget.setColumnCount(9)
#
设置表格内容⽂字⼤⼩
font=QtGui.QFont()
font.setPointSize(12)
self.tableWidget.setFont(font)
#
根据窗体⼤⼩拉伸表格
self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
#
遍历最终的信息
for row in range(len(self.info_table)):
fraction=0 #
分数,根据该分数判断列车的紧张程度
for column in range(9):
if column==6: #
如果某趟列车当天⽆票
if self.info_table[row][column]=='⽆' or self.info_table[row][column]=='--':
fraction+=3 #
计三分
if column==7:
if self.info_table[row][column]=='⽆' or self.info_table[row][column]=='--':
fraction+=2
if column==8:
if self.info_table[row][column]=='⽆' or self.info_table[row][column]=='--':
fraction+=1
#5
分数⼤于或等于分的车次为红⾊,说明该车次卧铺⾮常紧张
if fraction>=5:
#
定位是哪趟车次符合该条件,遍历该车次信息
for i in range(len(self.info_table[row])):
#
表格中的信息
item=QtWidgets.QTableWidgetItem(self.info_table[row][i])
item.setBackground(QColor(255,0,0)); #
设置该车次背景颜⾊
self.tableWidget.setItem(row,i,item) #
设置表格显⽰的内容
#
橙⾊,说明改车次卧铺紧张
if 1 <= fraction <= 4:
#
定位是哪趟车次符合该条件,遍历该车次信息
for i in range(len(self.info_table[row])):
#
表格中的信息
item=QtWidgets.QTableWidgetItem(self.info_table[row][i])
item.setBackground(QColor(255,170,0)); #
设置该车次背景颜⾊
self.tableWidget.setItem(row,i,item) #
设置表格显⽰的内容
#
说明该车次卧铺不紧张
if fraction==0:
#
定位是哪趟车次符合该条件,遍历该车次信息
for i in range(len(self.info_table[row])):
#
表格中的信息
item=QtWidgets.QTableWidgetItem(self.info_table[row][i])
item.setBackground(QColor(85,170,0)); #
设置该车次背景颜⾊
self.tableWidget.setItem(row,i,item) #
设置表格显⽰的内容
5.在query_中 创建query_ticketing_analysis()⽅法,在该⽅法中⾸先发送查询请求,然后分别对今天、三天内、五天内的车
票信息进⾏处理与分类。
today_car_list=[] #
保存今天列车信息,已经处理是否有票
three_car_list=[] #
保存三天列车信息,已经处理是否有票
five_car_list=[] #
保存五天列车信息,已经处理是否有票
today_list=[] #
保存今天列车信息,未处理是否有票
three_list=[] #
保存三天列车信息,未处理是否有票
five_list=[] #
保存五天列车信息,未处理是否有票
#
查询卧铺售票分析数据
def query_ticketing_analysis(date,from_station,to_station,which_day):
#
查询请求地址
url = '/otn/leftTicket/query?_date={}&_station={}'
'&_station={}&purpose_codes=ADULT'.format(date,from_station,to_station)
#
发送查询请求
response=requests.get(url)
# json
将数据转换为字典类型,通过键值对取数据
result=response.json()
result=result['data']['result']
#
判断车站⽂件是否存在
if is_stations('') :
stations=eval(read('')) #
读取所有车站并转换为字典类型
if len(stations)!=0: #
判断返回数据是否为空
for i in result:
#
分割数据并添加到列表中
tmp_list=i.split('|')
#print(tmp_list)
#
因为查询结果中出发站和到达站为站名的缩写字母,所以需要在车站库中到对应的车站名称
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[21],tmp_list[23],tmp_list[28]]
#print(seat)
#
判断今天的车次信息
if which_day==1:
#C
将⾼铁、动、开头的车次排除
if seat[0].startswith('G') is False and seat[0].startswith('D') is False and seat[0].startswith('C') is False:
#
将⾼级软卧、软卧、硬卧未处理信息添加⾄列表中
today_list.append(seat)
#
判断某车次是否有票
new_seat=is_ticket(tmp_list,from_station,to_station)
#
将判断后的车次信息添加⾄对应的列表中
today_car_list.append(new_seat)
#
判断三天的车次信息
if which_day==3:
if seat[0].startswith('G') is False and seat[0].startswith('D') is False and seat[0].startswith('C') is False:
three_list.append(seat)
new_seat=is_ticket(tmp_list,from_station,to_station)
three_car_list.append(new_seat)
#
判断五天的车次信息
if which_day==5:
if seat[0].startswith('G') is False and seat[0].startswith('D') is False and seat[0].startswith('C') is False:
five_list.append(seat)
new_seat=is_ticket(tmp_list,from_station,to_station)
five_car_list.append(new_seat)
此代码运⾏完之后,得到的6个列表的数据类似于下图:
today_list、three_list、five_list包含的信息主要的是:车次、出发地、⽬的地、出发时间、到达时间、历时、⾼级软卧票数、软卧票数、
硬卧票数
today_car_list、three_car_list、five_car_list包含的信息主要的是:车次、出发地、⽬的地、出发时间、到达时间、历时、是否有卧铺票
today_list=[['k105','北京西','深圳','23:18','04:20','29:02','--','⽆','⽆']]
today_car_list=[['k105','北京西','深圳','23:18','04:20','29:02','⽆']]
three_list=[['k105','北京西','深圳','23:18','04:20','29:02','--','有','有'],
['z313','北京','深圳','08:30','12:50','28:20','--','⽆','有'],]
three_car_list=[['k105','北京西','深圳','23:18','04:20','29:02','有'],
['z313','北京','深圳','08:30','12:50','28:20','有'],]
five_list=[['k105','北京西','深圳','23:18','04:20','29:02','有','有','有'],
['z313','北京','深圳','08:30','12:50','28:20','⽆','10','有'],]
five_car_list=[['k105','北京西','深圳','23:18','04:20','29:02','有'],
['z313','北京','深圳','08:30','12:50','28:20','有'],]
today_list、three_list、five_list三个列表主要⽤于后⾯的计算并显⽰卧铺数量的折线图
today_car_list、three_car_list、five_car_li列表主要⽤某车次是否还有卧铺车票以及分析车票的紧张程度
6. 上⾯的query_ticketing_analysis()⽅法调⽤了is_ticket()⽅法,⽤于判断某车次是否还有卧铺车票。如果⾼级软卧、软卧、硬卧其中
有⼀个有票的话(对应的是’有’或者是数字),就说明该类车次有卧铺车票。否则就是没票
#
判断⾼级软卧、软卧、硬卧是否有票
def is_ticket(tmp_list,from_station,to_station):
#
判断⾼级软卧、软卧、硬卧任何⼀个有票的话,就说明该趟车有卧铺票
if tmp_list[21]=='有' or tmp_list[23]=='有' or tmp_list[28]=='有':
tmp_tem='有'
else:
#
判断⾼级软卧、软卧、硬卧对应的如果是数字说明也有票,其他为⽆票
if tmp_list[21].isdigit() or tmp_list[23].isdigit() or tmp_list[28].isdigit():
tmp_tem='有'
else:
tmp_tem='⽆'
#
创建新的座位列表,显⽰某趟车是否有卧铺票
new_seat=[tmp_list[3],from_station,to_station,tmp_list[8],tmp_list[9],tmp_list[10],tmp_tem]
return new_seat
运⾏结果如下图:(如果运⾏不出来结果是正常的,还是因为12306不让你爬取它哦,主要还是理解代码)
模块三部分⼆:卧铺车票折线图绘制
1. 在show_⽂件中创建show_brokenn_line()⽅法,⽤于显⽰卧铺车票数量的折线图。主要会⽤到today_list、
three_list、five_list这三个列表的信息。
⾸先对每个车次中今天、三天内、五天内的卧铺数量分别进⾏统计(调⽤statistical_quantity()⽅法),然后实现当车次信息量⼤
时,添加滚动条扩⼤折线图⾼度,然后创建⾃定义的画布对象,在调⽤broken_line()⽅法实现折线图的绘制,最后将折线图显⽰在主
窗体的⽔平布局中。
#
显⽰卧铺车票数量折线图
def show_broken_line(self):
train_number_list=[] #
保存车次
tickets_number_list=[] #
保存今天,三天内,五天内所有车次的卧铺数量
#
遍历车次信息
for train_number in self.info_table:
number_list=[] #
临时保存车票数量
if self.horizontalLayout.count()!=0:
#
每次点循环删除管理器的组件
while self.horizontalLayout.count():
item=self.horizontalLayout.takeAt(0) #
获取第⼀个组件
widget=item.widget() #
删除组件
widget.deleteLater()
is_today_true=False #
判断今天是否存在某趟列车的标记
for today in today_list:
#
判断今天的车次信息中是否有该车次
if train_number[0] in today:
is_today_true=True #
存在就进⾏标记
number=self.statistical_quantity(today[6:9]) #
调⽤统计车票数量的⽅法
number_list.append(number) #
将车票数量添加⾄临时列表中
break
if is_today_true==False: # 0
如果今天没有某⼀趟列车,说明该车次⽆票为
number_list.append(0)
is_three_true = False
for three_today in three_list:
if train_number[0] in three_today:
is_three_true = True
number = self.statistical_quantity(three_today[6:9])
number_list.append(number)
break
if is_three_true == False:
number_list.append(0)
is_five_true = False
for five_today in five_list:
if train_number[0] in five_today:
is_five_true = True
number = self.statistical_quantity(five_today[6:9])
number_list.append(number)
break
if is_five_true == False:
number_list.append(0)
tickets_number_list.append(number_list)
train_number_list.append(train_number[0])
#
车次信息⼤时,添加滚动条扩⼤折线图⾼度
if len(train_number_list)>=9:
self.scrollAreaWidgetContents.setMinimumHeight(len(train_number_list)*30)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(0,0,951,(len(train_number_list)*30)))
#
创建画布对象
line=PlotCanvas()
line.broken_line(tickets_number_list,train_number_list) #
调⽤折线图⽅法
self.horizontalLayout.addWidget(line) #
将折线图添加⾄底部⽔平布局当中
2. 在show_⽂件中创建statistical_quantity()⽅法,⽤于统计车票数量。如果是有,增加20个车票;如果是⽆,增加0个车
票;如果是数字,就增加对应的数字。
#
统计车票数量
def statistical_quantity(selfself,msg):
number=0 #
车票初始值
for i in msg:
if i=='有': # 20
如果是有,增加个车票
number+=20
if i=='⽆' or i=='': # 0
如果是⽆或者空,就增加个车票
number+=0
if i.isdigit(): #
如果是数字,就直接增加对应的数字
number+=int(i)
return number
3. 创建⽂件,在该⽂件中创建PlotCanvas类,通过__init()__()⽅法进⾏初始化,创建图形以及初始化画布。然后创建
broken_line()⽅法,⽤于显⽰车票⾛势的折线图。(感觉基本上都是固定的写法)
import matplotlib #
导⼊图表模块
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas #
图形画布
import matplotlib.pyplot as plt #
导⼊绘图模块
class PlotCanvas(FigureCanvas):
def __init__(self,parent=one,width=0,height=0,dpi=100):
#
避免中⽂乱码
matplotlib.rcParams['-serif']=['SimHei']
matplotlib.rcParams['e_minus']=False
#
创建图形
fig=plt.figure(figsize=(width,height),dpi=dpi)
#
初始化图形画布
FigureCanvas.__init__(self,fig)
self.setParent(parent) #
设置⽗类
def broken_line(self,number,train_list):
"""
linewidth:折线的宽度
marker:折点的形状
markerfacecolor:折点实⼼颜⾊
markersize:折点⼤⼩
"""
day_x=['今天','三天内','五天内'] # x
轴折线点
for index,n in enumerate(number):
#
绘制折线
plt.plot(day_x,n,linewidth=1,marker='o',markerfacecolor='blue',markersize=8,label=train_list[index])
plt.legend(bbox_to_anchor=(-0.03,1)) #
让图例⽣效。并设置图例显⽰位置
plt.title('卧铺车票数量⾛势图')
运⾏结果如下图:(同样运⾏不出来折线图是正常的)
哎呀呀呀!我终于写完了。
感觉写的好长,⾃⼰完成的⼀个⼩项⽬,记录⼀下吧。
有什么问题或者需要源代码的,可以评论。我看到的就会回复
本文发布于:2023-05-25 21:03:05,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/falv/fa/82/113228.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |