点击上方蓝色字关注 [ 啃饼思录 ]~
12306火车车次信息爬取分析
本篇我们要进行的是12306火车车次信息的爬取分析。都说12306是目前反爬措施最强的网站,的确如此。博主于2017年专门研究过如何爬取并进行了购票分析,费了很大功夫终于成功地抢到了票,但是很不幸,没过多久12306就进行了大改版,写过的爬虫代码几乎成为了摆设。这里只是爬取车次信息,并不进行购票操作,后续可能会出专门的教程介绍这一块,本篇文章的重点不在于此。
12306网址:https://www.12306.cn/index/
,由于我们爬取的是车次信息,因此首先需要知道所有的车站名称:
通过观察,很容易发现出发地和到达地都是一个弹窗,由JS控制。我们思考一下,如果想要爬取所有的车站名称,是不是爬取这个JS弹窗内的信息就可以?是的,我们就是这样做的。首先我们需要登录账号,接着进行下面的操作。我们按F12或者直接开启开发者模式,先在出发地随便选一个车站,然后观察network处的变化,我们发现了一个名为station_name_v10026.js的文件,就是这样:
接着我们就点击这个js文件,页面就跳转到:https://www.12306.cn/index/script/core/common/station_name_v10026.js
,我们使用fe助手对代码进行格式化,发现这个就是车站名称控件:
接下来就是将里面的车站名称都爬取下面。先点击这里:汉字 Unicode 编码范围,我们发现一般车站都是常见字,因此使用4E00-9FA5区间的unicode编码即可。我们可以使用([\u4e00-\u9fa5]+)\|([A-Z]+)
这样的正则匹配条件来获取我们需要的诸如北京北': 'VAP'
等。相应的代码如下:
import requestsimport re
headers = { 'Cookie': '', 'Host': 'www.12306.cn', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'}def getStationName():
# 爬取12306网站所有车站名称信息
url = 'https://www.12306.cn/index/script/core/common/station_name_v10026.js' # 车站信息控件
r = requests.get(url)
pattern = '([\u4e00-\u9fa5]+)\|([A-Z]+)' # 正则匹配规则
result = re.findall(pattern, r.text)
stationName = dict(result) # 所有车站信息,转换为字典
print(stationName) # return stationNamegetStationName()
运行结果如下(部分信息):
{'北京北': 'VAP', '北京东': 'BOP', '北京': 'BJP', '北京南': 'VNP'}
在前面我们查询到了所有的车站名称,接下来我们就是查询两站之间火车票的信息了。我们随意输入两个城市,上海和天津,时间更是随意,如下所示:
我们注意一下此时url栏的变化,此时url变为:
我们尝试输入北京到上海,再次确认一下url的变化:
说明后面的flag=N,N,Y
是一个常量,如果去掉发现页面就不能正常显示。
也就是说我们需要传入三个参数:date(日期),from_station(出发站),to_station(到达站)。我们需要构造的url应该是这样:
https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=from_station&ts=to_station&date=date&flag=N,N,Y
通过观察发现,其实不带车站的字母代号也是可以查询到票务信息的:
所以你可以简化前面爬取的内容,但是我这里就不了,因为我个人比较喜欢这种方式。
接下来就是构建查询的代码了,结合上面查询到的车站信息,我们就能得到如下代码:
import requestsimport re
headers = { 'Cookie': '', 'Host': 'www.12306.cn', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'}def getStationName():
# 爬取12306网站所有车站名称信息
url = 'https://www.12306.cn/index/script/core/common/station_name_v10026.js' # 车站信息控件
r = requests.get(url)
pattern = '([\u4e00-\u9fa5]+)\|([A-Z]+)' # 正则匹配规则
result = re.findall(pattern, r.text)
stationName = dict(result) # 所有车站信息,转换为字典
# print(stationName)
return stationName
text =getStationName()def get_query_url(text, date, from_station, to_station):
# 构建用于查询列车车次信息的url
# 参数:日期,出发地,到达地
# key为车站名称, value为车站代号
date = date
from_station = from_station+","+text[from_station]
to_station = to_station+","+text[to_station] # 新的url
url = ( "https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&"
"fs={}"
"&ts={}"
"&date={}"
"&flag=N,N,Y"
).format(from_station, to_station, date)
print(url)
get_query_url(text,'2019-04-20','上海','天津')
该程序运行结果为:
https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=上海,SHH&ts=天津,TJP&date=2019-04-20&flag=N,N,Y
拿着这个url去输入栏访问一下,发现可以正常访问了,页面显示票务信息。
现在我们就是获取刚才链接页面出现的票务详情信息了,我们需要明确获取的字段信息:车次、出发站、到达站、出发时间、到达时间、历时、商务/特等座、一等座、二等座、软卧、硬卧、硬座和无座等。我们拿到刚才生成的url,访问一下(开启开发者模式):
我们发现有一个query,然后右边就是我们查询的结果,因此我们可以从这个结果中取出信息:
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2019-04-20&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=TJP&purpose_codes=ADULT
看到这里,你或许会明白,我们其实构造的url最后就是以这个url的查询结果的形式返回,那么我们可不可以直接构造这个url呢?答案是可以的,那么我们对上面的代码进行修改一下:
def get_query_url(text, date, from_station, to_station):
# 构建用于查询列车车次信息的url
# 参数:日期,出发地,到达地
# key为车站名称, value为车站代号
date = date
from_station = text[from_station]
to_station = text[to_station] # 新的url
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)
print(url) return url
运行结果如下:
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2019-04-20&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=TJP&purpose_codes=ADULT
截图就是这样:
也就说结果是一个json形式的dict,我们先根据data这个key取出全部信息,再根据result这个key取出车次信息。图中密密麻麻的就是我们需要的车次信息:
从这个信息中遍历一下,就能得到我们需要的信息,0代表某一辆车的信息,再从0这个一行中取出我们需要的字段信息即可:
我们发现每个字段都是被|
给分割的,因此只需要统计这个|
的位置就能取出相应的信息。相应的代码如下:
import requests
url="https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2019-04-20&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=TJP&purpose_codes=ADULT"r =requests.get(url)
all_trains = r.json()['data']['result'] # 获取所有车次信息for one_train in all_trains: # 遍历取出每辆车的信息
data_list = one_train.split('|')
train_number = data_list[3] # 车次
from_station_code = data_list[6] # 出发站代号
from_station_name = '上海' # 出发站名称
to_station_code = data_list[7] # 到达站代号
to_station_name = '天津' # 到达站名称
go_time = data_list[8] # 出发时间
arrive_time = data_list[9] # 到达时间
cost_time = data_list[10] # 历时
special_class_seat = data_list[32] or '--' # 商务/特等座
first_class_seat = data_list[31] or '--' # 一等座
second_class_seat = data_list[30] or '--' # 二等座
soft_sleep = data_list[23] or '--' # 软卧
hard_sleep = data_list[28] or '--' # 硬卧
hard_seat = data_list[29] or '--' # 硬座
no_seat = data_list[26] or '--' # 无座
print(train_number,from_station_code,from_station_name,to_station_code,to_station_name,go_time,arrive_time,cost_time,special_class_seat,first_class_seat,second_class_seat,soft_sleep,
hard_sleep,hard_seat,no_seat)
from_station_name
,to_station_name
这两个先这样用,这里贴出来就是先测试看看数据是否抓取准确,然后再和前面的链接进行拼接,运行结果如下(部分):
G108 AOH 上海 TIP 天津 07:22 12:45 05:23 5 有 有 -- -- -- --G1232 AOH 上海 TXP 天津 07:34 13:45 06:11 无 无 有 -- -- -- --G120 AOH 上海 TIP 天津 07:51 12:49 04:58 16 有 有 -- -- -- --G112 AOH 上海 TIP 天津 08:05 13:24 05:19 无 有 有 -- -- -- --G212 AOH 上海 TXP 天津 08:42 14:26 05:44 有 有 有 -- -- -- --G1258 AOH 上海 TXP 天津 09:08 14:34 05:26 -- 19 有 -- -- -- --
可以看到数据吻合,这样我们就完成了本篇文章的要求。