代码采用python3编写,本来用PyQt5做了一个方便的查询界面,不过这篇文章主要来介绍爬取功能,就不把Qt代码贴出来了,可以先看一下效果。运行后只需输入起始站、终点站和日期就可以看到如下效果,和火车站的显示屏有点相似。
查询车次的脚本还是比较简单的,只是访问一下车次信息而已,不需要用到Cookie和Session这些信息。
首先把过程写出来:
1.分析查询需要的信息参数
2.找到查询的链接
3.写代码
4.测试
首先分析网站
通过抓包可以知道查询请求的网址链接,比如:https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date=2019-01-24&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT
因为12306的网站信息是经常变化的,所以需要实时地在代码中修改它,到时候再查看即可。其中有三个参数:
train_date
from_station
to_station
我们可以看到其中的车站信息都不是以汉字的形式给出的,比如上面的BJP和SHH。所以需要把车站名和这些代号的联系找出来。
获取车站信息
这些信息储存再一个js里面,链接:https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9091
再查询的界面抓包也可以找到,打开这个链接可以看到这样的界面:
把这些信息截取下来:
import re
import requests
from pprint import pprint
def main():
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9091'
# 获取信息,且不判断证书
response = requests.get(url, verify=False)
# 使用正则表达式提取所有的站点
stations = dict(re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text))
# 分别把车站名和代号提取出来
pprint(stations.keys())
pprint(stations.values())
if __name__ == '__main__':
main()
新建一个stations.py来储存这些信息并且建立双向关系:
names = ['北京北', '北京东', '北京', '北京南', '北京西'......]
codes = ['VAP', 'BOP', 'BJP', 'VNP', 'BXP'......]
#关系转换
def get_name(code):
return names[telecodes.index(code)]
def get_code(name):
return codes[names.index(name)]
写代码进行提取
爬取需要用到的库:
requests:爬虫必用的发送请求,获取HTML网页内容的库。
发挥爬取功能只需要这个库就绰绰有余,为了显示方便我们加上一个显示库:
prettytable:能让你的数据像MySQL的命令行显示数据的格式一样。
本次爬取不需要请求头也能正常进行,当然加上也可以。
以下是主要代码,可以根据需求自己更改,如果是直接复制下来可能无法成功运行,毕竟写了有一段时间了,12306每隔一段时间就会更改网站信息,所以看情况更改。当然,重在学习知识。
这是在win10上面编写的,要在linux上运行就加上必要的头信息。
import requests
from prettytable import PrettyTable
import stations
from colorama import init, Fore
#请求信息
f_station = stations.get_code(input('起始站:'))
t_station = stations.get_code(input('终点站:'))
date = input('出行时间:')
# 构造请求地址
url = ('https://kyfw.12306.cn/otn/leftTicket/queryZ?'
'leftTicketDTO.train_date={}&'
'leftTicketDTO.from_station={}&'
'leftTicketDTO.to_station={}&'
'purpose_codes=ADULT').format(date, f_station, t_station)
# 不判断证书
r = requests.get(url, verify=False)
r.encoding = r.apparent_encoding
# requests得到的是一个json格式的对象
# 原始火车数据
raw_trains = r.json()['data']['result']
header = ('车次 车站 时间 历时 特等座 一等 二等 高级软卧 软卧 硬卧'
+ '软座 硬座 无座 备注').split()
# 历时转换
def get_duration(raw_trains):
duration = raw_train[10].replace(':', '小时') + '分'
if duration.startswith('00'):
return duration[4:]
if duration.startswith('0'):
return duration[1:]
return duration
# 返回每个车次的基本信息
def trains(raw_train):
for raw_train in raw_trains:
# 列车号
train_no = raw_train[3]
# 得到什么列车并小写
initial = train_no[0].lower()
# 反转station所对应的字典
stations_re = dict(zip(stations.values(), stations.keys()))
# 将车次的信息保存到列表中
# train 出发地
begin_station = stations_re.get(raw_train[4])
# train 目的地
end_station = stations_re.get(raw_train[5])
# your 出发地
from_station = stations_re.get(raw_train[6])
# your 目的地
to_station = station_re.get(raw_train[7])
# 判断是起始还是经过
begin_flag = __check_equals(begin_station, from_station)
end_flag = __check_equals(end_station, to_station)
train = [
train_no,
# 时间
get_duration(raw_train),
# 历时
raw_train[9],
# 特等座
__show_color(raw_train[31]),
# 一等
__show_color(raw_train[30]),
# 二等
__show_color(raw_train[22]),
# 高级软卧
__show_color(raw_train[23]),
# 软卧
__show_color(raw_train[25]),
# 软座
__show_color(raw_train[24]),
# 硬座
__show_color(raw_train[29]),
# 无座
__show_color(raw_train[26]),
# 备注
__show_color(raw_train[1]),
]
# 更改不运行车次的时间和历时
if raw_train[14] == 'null':
train[2] = '--\n--'
train[3] = '--'
for i, item in enumerate(train):
if not item:
train[i] = '--'
yield train
def __check_equals(from_station, to_station):
if from_station == to_station:
return '始'
else:
return '过'
def __get_color(color, content):
return color + content + Fore.RESET
def __show_color(content):
if content == '有':
return Fore.GREEN + content + Fore.RESET
else:
return content
#打印信息
def pretty_print():
pt = PrettyTable()
pt._set_field_names(self.header)
for train in self.trains():
pt.add_row(train)
print(pt)
if __name__ == '__main':
time = get_duration(raw_trains)
部分参考来源:https://blog.csdn.net/tigaoban/article/details/78558417