趣玩爬虫 | 12306火车车次信息爬取分析

点击上方蓝色字关注 [ 啃饼思录 ]~

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 有 -- -- -- --

可以看到数据吻合,这样我们就完成了本篇文章的要求。

原文发布于微信公众号 - 啃饼思录(kbthinking)

原文发表时间:2019-05-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券