Python爬虫——爬取12306车票信息

爬取12306一是一个非常有乐趣的事情,同时也是爬虫入门程序。
我在爬虫刚入门时,这个程序写了许久才搞明白
今天重新拉出来发个博客

本文写于小白时期,亲测有效,就懒得改了

这个程序就不做GUI窗口了,毕竟我计较懒(其实是不会)

首先,我们要爬取的网站是https://www.12306.cn/index/的车票信息

当我们选择出发地和目的地,点下查询就可以获得相关的车票信息,而我们要将这些信息用Python爬取

假如我输入出发地为北京,目的地为上海,则查询结果如下
Python爬虫——爬取12306车票信息_第1张图片
先看一下程序效果(丑的惨不忍睹)
Python爬虫——爬取12306车票信息_第2张图片
接下来正式开始爬虫

获取车票信息

像这样的信息,一般都是ajax异步加载的,至于什么是ajax异步加载
在这里插入图片描述
其实就是通过js将页面动态渲染,减小了网页负担
那么怎么爬取这种数据呢?

抓包
按下F12打开Google浏览器的开发者模式,找到Network–>XHR,里面可以抓取ajax异步请求

当打开Google Chrome的XHR,按下查询发现了这样的请求
Python爬虫——爬取12306车票信息_第3张图片
点开这个请求,查看他的链接
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2020-04-21&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT
又换了几个地方尝试,发现变化的参数只有train_date、from_station、to_station,即出发日,出发地,目的地

但是发现出发地和目的地是简称,那么怎样获得站名简称呢

刷新一下网页,在all中发现了这个文件
Python爬虫——爬取12306车票信息_第4张图片
https://kyfw.12306.cn/otn/resources/js/framework/favorite_name.js(可能发生变化,以自己看到的为准)

看看他的响应体Preview,发现了我们想要的站名即简称
这样我们要先把这玩意爬下来

get_station.py

import requests
import re

def get_station():
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9142'
    response = requests.get(url).text
    name = re.findall(r'.*?\|(.*?)\|.*?\|.*?\|.*?\|.*?',response)
    referred = re.findall(r'.*?\|.*?\|(.*?)\|.*?\|.*?\|.*?',response)
    station = dict(zip(name,referred))
    file = open('station.txt','w',encoding='utf-8')
    file.write(str(station))
    file.close()
    return station

通过这样一个程序,我们就可以获取站名简称

运行结果如下

{‘北京北’: ‘VAP’, ‘北京东’: ‘BOP’, ‘北京’: ‘BJP’, ‘北京南’: ‘VNP’, ‘北京西’: ‘BXP’, ‘广州南’: ‘IZQ’, ‘重庆北’: ‘CUW’, ‘重庆’: ‘CQW’, ‘重庆南’: ‘CRW’, ‘重庆西’: ‘CXW’, ‘广州东’: ‘GGQ’, ‘上海’: ‘SHH’, ‘上海南’: ‘SNH’,…

此处省略10000字

我们首先访问了这个链接,通过正则表达式获取站名列表和简称列表,并将他们转为字典,写入文件

在获取到简称之后,就可以构造链接了,观察他的请求结果,发现是类似这样的结果
Python爬虫——爬取12306车票信息_第5张图片
我们要获取到result中的结果,同时将含有列车停运的结果去除

get_tickets.py

import requests
import json
from urllib.parse import urlencode

headers = {
     
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36',
    'Cookie': 'JSESSIONID=85ABACBDDEC5EF0D3F4390E49C235DCD; BIGipServerotn=569377290.24610.0000; BIGipServerpool_passport=183304714.50215.0000; RAIL_EXPIRATION=1584746738988; RAIL_DEVICEID=c_HPEh5qqB0-onW7FlqB5a2T-w9tiHZ95ePILEBaXLQ3Nj84j7a4PV1ezmRs7O57oEVHFp3JcbAEi_s3qJb_bqey5sGYiQ-RmKrzrZ0wzbndDLKGidKjF1l5UZ4FjwqSTdhbaSx8ds-5RgV-KxQrm0mINenavAb3; route=c5c62a339e7744272a54643b3be5bf64; _jc_save_fromStation=%u5317%u4EAC%2CBJP; _jc_save_fromDate=2020-03-17; _jc_save_toDate=2020-03-17; _jc_save_wfdc_flag=dc; _jc_save_toStation=%u5929%u6D25%2CTJP'
}

def get_tickets(fromwhere,towhere,startime):
    data = {
     
        'leftTicketDTO.train_date': startime,
        'leftTicketDTO.from_station': fromwhere,
        'leftTicketDTO.to_station': towhere,
        'purpose_codes': 'ADULT',
    }
    request_url = 'https://kyfw.12306.cn/otn/leftTicket/query?' + urlencode(data)
    response = json.loads(requests.get(request_url,headers=headers).text)
    result = response['data']['result']
    new_list = []
    for item in result:
        if not '列车停运' in item:
            new_list.append(item)
        else:
            pass
    return new_list

代码简单,不做详细介绍

信息解密

a1M3%2FUzhyJTMAZT71W6ZfN68662hsWaBMlmBsbvx%2BxVWGWhLebrLW7DzbdUuls20VR%2BVbiySPM6T%0AFmoziK649b%2FqUXE2OU3po2eL3xPDK476PSrF7q%2BfUAW%2F%2BIAEc6VJ%2Fu3rzWLB6rCNYQCw78qbSHsW%0AX3ByQhue1INsKq3bPSSe5DM9S8IlyE2%2FL5RFdu6CEnY37uMZjrjROosZx7OJtQx2i%2FEV9eKb6iWO%0A9Zy9u9b%2BGmSJYXfNR8qTC%2F%2BR9Ub3RcgknRLOr45A5vcNqx5bCIpMNe1k61u%2FARFCIFwW%2FJo%3D|预订|240000D70901|D709|VNP|SHH|VNP|SHH|19:46|07:46|12:00|Y|JmRqJPg0lxl8mWDLziQaL3KoPp3cYuDom7d71dq1TO9zFrfw|20200421|3|P4|01|04|1|0||||有|||||有||有||||O0J0I0|OJI|1|0|||||||||"

这些信息,我们怎样处理出我们想要的信息呢?

我们发现,在预定之后,比对我们看到的信息,发现可以进行处理,将我们想要的信息提取出来

Decrypt.py

import re
def decrypt(string):
    reg = re.compile('.*?\|预订\|.*?\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|.*?\|.*')
    result = re.findall(reg,string)[0]
    return result

main.py

from get_station import get_station
from get_tickets import get_tickets
from Decrypt import decrypt
import datetime

def get_message():
    fw = input("请输入你的出发地>>>")
    tw = input("请输入你的目的地>>>")
    st = input("请输出出发时间;格式:(年-月-日)(默认为今日日期)>>>>")
    if st == '':
        st = datetime.date.today()
        return fw,tw,st
    else:
        today = datetime.date.today()
        date = str(today).split('-')
        list = st.split('-')
        if int(list[0]) < int(date[0]) or int(list[0]) > int(date[0]):
            exit("输入的年份不在我的查询范围之内")
        else:
            if int(list[1]) < int(date[1]) or int(list[1]) > int(date[1])+1:
                exit("你输入的月份不在我的查询范围之内")
            else:
                if int(list[2]) < int(date[2]):
                    exit("你输入的日期不在我的查询范围之内")
                else:
                    if int(list[1]) < 10 and int(list[1][0]) != 0:
                        list[1] = '0' + list[1]
                    if int(list[2]) < 10 and int(list[2][0]) != 0:
                        list[2] = '0' + list[2]
                    return fw,tw,list[0] + '-' + list[1] + '-' + list[2]
message = get_message()

def run():
    station_name = get_station()
    try:
        fromwhere = get_station()[message[0]]
        towhere = get_station()[message[1]]
        startime = message[2]
        tickets = get_tickets(fromwhere,towhere,startime)
        print("\n车次    出发站    到达站    出发时间    到达时间    历时    商务座/特等座    一等座    二等座/二等包座    高级软卧    软卧一等卧    动卧    硬卧/二等卧    软座    硬座    无座    其他")
        for item in tickets:
            result = list(decrypt(item))
            new_dict = {
     v: k for k, v in station_name.items()}
            result[1] = new_dict[result[1]]
            result[2] = new_dict[result[2]]
            print("%s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s    %s"%(result[0],result[1],result[2],result[3],result[4],result[5],result[-1],result[-2],result[-3],result[-4],result[-5],result[-6],result[-7],result[-8],result[-9],result[-10],result[-11]))
    except KeyError as k:
        print("I can't find the city %s"%k)

if __name__ == '__main__':
    run()

这是我之前写的程序,可能有瑕疵,请谅解

你可能感兴趣的:(python)