说明文字:
1.本项目只是一个练习,熟悉python爬虫技术,没有任何用途
2.最后运行的结果有时候会成功,有时候会显示错误界面,如下图所示。因为12306怎么可能允许你一直爬它呢
开发工具准备:
- 开发工具:PyCharm
- 内置模块:sys,time,datetime,os,json,re
- 第三方模块:PyQt5,pyqt5-tools,requests,matplotlib
准备工作:
- 下载数据文件:stations.text(车站名称文件)和time.text(起售时间文件)
创建get_station.py文件:
import json
import re #通过正则表达式匹配处理相应的字符串
import os #判断某个路径下的某个文件
import requests #处理网络请求
def get_selling_time():
url='https://www.12306.cn/index/script/core/common/qss_v10082.js'
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]) #解析JSON数据
print(time_js)
write(str(time_js),'time.text') #调用写入方法
def get_station():
#发送请求获取所有车站名称,通过输入的站名称转化查询地址的参数
url='https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9151'
response=requests.get(url,verify=True) #请求并进行验证
#print(response.text)
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, 'stations.text') #调用写入方法
#写入文件
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('stations.text') is False:
get_station() # 下载所有车站文件
if is_stations('time.text') is False:
get_selling_time() # 下载起售时间文件
- 在PyCharm中设置PyQt5工具:
1.Qt Designer:主要进行主窗体的UI设计,最后保存的是ui文件
2.Pyuic工具:将.ui文件转成.py文件
3.Pyrcc工具:将.qrc文件转成.py文件
配置链接:https://blog.youkuaiyun.com/wang_hugh/article/details/88775868
用Qt Designer设计的主窗体效果图:
项目结构:
- img_resources文件夹主要是图片资源文件,主窗体UI设计用到的两个.png图片和一个.qrc图片资源文件
- ui文件夹保存的是Qt Designer设计的窗体ui文件
- window.py是window.ui用Pyuic工具转换的
- img_rc.py是img.qrc用Pyrcc工具转换的
- get_stations.py是下载车站名称与起售时间代码,运行之后会出现stations.text(车站名称文件)和time.text(起售时间文件)
- 最重要的就是:query_request.py查询网络请求代码 以及 show_window.py显示与控制窗体代码。
项目说明:根据上面设计的主窗体效果图,可以看出来,该项目主要分为三大模块:车票查询、卧铺售票分析、车票起售时间。第二个模块最复杂,所以先说简单的两个模块
****小小提示:以下内容把我自己手写总结的图和代码配合起来看,比较容易理解 * ***
模块一:车票查询:
下面这个图是我整理的车票查询的步骤:
关键代码如下:
- 在show_window.py文件中创建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('stations.text') is True:
stations=eval(read('stations.text')) # 读取所有车站并转换为字典类型
#判断所有参数是否为空
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
# 12306官方要求智能查询30天以内的车票
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('警告', '未下载车站查询文件!')
#消息提示框,参数title为提示框标题文字,message为提示信息
def messageDialog(title,message):
msg_box=QMessageBox(QMessageBox.Warning,title,message)
msg_box.exec_()
- 在query_request.py文件中创建query()方法,该方法需要三个参数:出发日期、出发地、目的地。在该方法中,查询请求地址是通过format()方法对地址进行格式化。由于获取到的JSON信息比较乱,所以在获取指定数据时通过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 = '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,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.text') :
stations=eval(read('stations.text')) # 读取所有车站并转换为字典类型
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 # 返回整理好的车次信息
代码中 print(response.url) 如果输出的是:https://www.12306.cn/mormhweb/logFiles/error.html 也就是上面说的错误页面,就说明12306爬虫失败,你的ip应该被锁住了。这种情况很正常,不必担心。
- 在show_window.py文件中创建displayTable()方法,用于将车票信息显示到主窗体的表格中。
#显示车次信息的表格
#train参数为共有多少趟列车,该参数为表格的行
#info参数为每趟列车的具体信息,例如有座、无座、卧铺等,该参数作为表格的列
def dis