python爬虫----我来给你查火车票

工具和环境

  • pycharm
  • import requests
  • import json
  • from prettytable import PrettyTable

分析12306 官网

  • https://www.12306.cn/index/ 进入官网

  • 填好参考的出发地 、到达地、出发时间
    在这里插入图片描述

  • 点击查询
    在这里插入图片描述

  • 看到如上画面

  • F12 找到 Network一栏,ctrl+R 刷新一次,在Name列表里找到

  • query?leftTicket…… 文件 查看他的 Response或者Preview,查看data

  • 如未找到 继续刷新
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GSquXT0j-1591242625128)()]

  • result 的值即为我们所要获取的值

  • 经过上面的分析,我们要得到的内容即为该网页(query?leftTicket……)的 response

  • 查看 Headers,查看 Requests headers

建立爬虫基础模型

  • 通过 heardes 内容 我们写出基本爬虫代码:(仔细看注释)

    import requests
    # 下面我们是对 headers 内容的复制 和建立 字典以便传入请求
    # 注意:有一些键值对 对我们的结果没有影响,不考虑
    cookies = {
        'JSESSIONID': '0F137F1CCFEF0D45BCE1395B91E3AF02',
        '_jc_save_wfdc_flag': 'dc',
        # 下面是我们的出发地,
        '_jc_save_fromStation': '%u6DF1%u5733%2CSZQ',
        
        'BIGipServerotn': '1859715338.64545.0000',
        'RAIL_EXPIRATION': '1591478460680',
        'RAIL_DEVICEID': 'KUmHXGT8KZoXi1oy1CXW0M61gVb55jGcLbYj_BrzfzCDxsdqe70FMmeOoqXPO884_8IBHLo0l_aLRM5USvx_L1_Vhz6w12yNVDkpkrl5tbZILluFoGC74mpkWllOaZyGRU3V1XcbMgbz9jWFxTL4KmlKSIQLex_M',
        'BIGipServerpassport': '971505930.50215.0000',
        'route': '9036359bb8a8a461c164a04f8f50b252',
        
        # 时间,即为我们输入的查询时间
        '_jc_save_toDate': '2020-06-06',
        '_jc_save_fromDate': '2020-06-06',
        
        # 下面使我们的到达地
        '_jc_save_toStation': '%u90B5%u9633%2CSYQ',
    }
    
    
    

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8VjPpPtf-1591242625130)()]

    • 我们可以发现 %2C后面部分为站点代码,后面我们会解释到,根据多次实验发现,我们删掉逗号和后面的内容请求结果依然正确

    • 我们也发现,在这运用的编码方式,因而我们可以类似的写出编码函数:

      def cn_unicode(station):
      	return str(station.encode('unicode_escape')).replace("\\\\",'%')
      
      # 代码检验:
      print(cn_unicode('深圳'))
      # 结果如下,大小写不影响请求结果的正确性,读者可以自行将其转换成大写
      

在这里插入图片描述

  • 说明:为何需要得到编码函数,因为我们后面查找车次需要输入不同的地址,将其的编码结果我们替换即可请求

  • 在headers中我们也看到了被编码的站点名称

    headers = {
        'Connection': 'keep-alive',
        'Cache-Control': 'no-cache',
        'Accept': '*/*',
        'Sec-Fetch-Dest': 'empty',
        'X-Requested-With': 'XMLHttpRequest',
        'If-Modified-Since': '0',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
        'Sec-Fetch-Site': 'same-origin',
        'Sec-Fetch-Mode': 'cors',
        'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=%E6%B7%B1%E5%9C%B3,SZQ&ts=%E9%82%B5%E9%98%B3,SYQ&date=2020-06-03&flag=N,N,Y',
        
        # 这一部分中 %E6%B7%B1%E5%9C%B3,SZQ %E9%82%B5%E9%98%B3,SYQ
        # 我们同样可以发现这就是我们的站点
        
        'Accept-Language': 'zh-CN,zh;q=0.9',
    }
    
    
    

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kdr8YZl5-1591242625132)()]

    • 我们同样可以得到他的编码函数:

      def cn_utf8(station):
      	return str(st.encode('utf-8')).replace("\\x",'%') 
      # 代码检验
      print(cn_utf8('深圳'))
      # 结果如下
      

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uiqmfhEo-1591242625132)()]

    • 对于params 部分我们主要解决站点代码替换问题
    • 我们可以在 Sources 栏 通过一下目录找到文件
    • kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9143
      - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOSk0PaL-1591242625133)()]
    • 提取 station_names 内容我们做成字典,以便通过站点名得到其代码
    params = (
        ('leftTicketDTO.train_date', '2020-06-06'),# 时间
        ('leftTicketDTO.from_station', 'SZQ'),#出发地代码
        ('leftTicketDTO.to_station', 'SYQ'),#到达地代码
        ('purpose_codes', 'ADULT'),# 成人票
        #('purpose_codes','0X00'), # 学生票
    )
    
    response = requests.get('https://kyfw.12306.cn/otn/leftTicket/query', headers=headers, params=params, cookies=cookies)
    
    • def station_dict():
          # 截取部分作为参考
          # 实际请求我们需要传入所有站点
      	station_names = "@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2@bjn|北京南|VNP|beijingnan|bjn|3@bjx|北京西|BXP|beijingxi|bjx|4@gzn|广州南|IZQ|guangzhounan|gzn|5@cqb|重庆北|CUW|chongqingbei|cqb|6@cqi|重庆|CQW|chongqing|cq|7@cqn|重庆南|CRW|chongqingnan|cqn|8@cqx|重庆西|CXW|chongqingxi|cqx|9@gzd|广州东|GGQ|guangzhoudong|gzd|10"
          result_list = station_names.split("@")
          # print(result_list)
          del result_list[0]
          # print(result_list)
          dict = {}
          for each in result_list:
              dict[each.split("|")[1]] = each.split("|")[2]
          return dict
      # 通过上面函数我们得到有效的数据:
      dict = station_dict()
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRhs3LBM-1591242625134)()]

  • 通过上面的分析我们得到完整的请求模型,读者可以优化,封装。

    #coding:utf-8
    import requests
    
    # 中文转 unicode 传给 cookies
    def cn_unicode(st):
        return str(st.encode('unicode_escape')).replace("\\\\",'%')
    
    # 中文转 utf8  传给headers
    def cn_utf8(st):
        return str(st.encode('utf-8')).replace("\\x",'%')
    
    # 参数的传递没有足够严谨
    def get_response(st_dict): # 参数传给 params
        cookies = {
            'JSESSIONID': '0F137F1CCFEF0D45BCE1395B91E3AF02',
            '_jc_save_wfdc_flag': 'dc',
            '_jc_save_fromStation': cn_unicode(fs),
            'BIGipServerotn': '1859715338.64545.0000',
            'RAIL_EXPIRATION': '1591478460680',
            'RAIL_DEVICEID': 'KUmHXGT8KZoXi1oy1CXW0M61gVb55jGcLbYj_BrzfzCDxsdqe70FMmeOoqXPO884_8IBHLo0l_aLRM5USvx_L1_Vhz6w12yNVDkpkrl5tbZILluFoGC74mpkWllOaZyGRU3V1XcbMgbz9jWFxTL4KmlKSIQLex_M',
            'BIGipServerpassport': '971505930.50215.0000',
            'route': '9036359bb8a8a461c164a04f8f50b252',
            '_jc_save_toDate': date,
            '_jc_save_fromDate': date,
            '_jc_save_toStation': cn_unicode(ts),
        }
    
        headers = {
            'Connection': 'close',
            'Cache-Control': 'no-cache',
            'Accept': '*/*',
            'Sec-Fetch-Dest': 'empty',
            'X-Requested-With': 'XMLHttpRequest',
            'If-Modified-Since': '0',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
            'Sec-Fetch-Site': 'same-origin',
            'Sec-Fetch-Mode': 'cors',
            'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs={0},SZQ&ts={1},SYQ&date={2}&flag=N,N,Y'.format(cn_utf8(fs),cn_utf8(ts),date),
            'Accept-Language': 'zh-CN,zh;q=0.9',
        }
    
        params = (
            ('leftTicketDTO.train_date', date),
            ('leftTicketDTO.from_station', st_dict[fs]),
            ('leftTicketDTO.to_station', st_dict[ts]),
            ('purpose_codes', 'ADULT'),
        )
    
        response = requests.get('https://kyfw.12306.cn/otn/leftTicket/query', headers=headers,params=params, cookies = cookies)
        response.encoding = 'utf-8'
        return response.text
    
    

结果分析和提取有用成分

  • 运行上面的模型:
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RyVAQwEU-1591242625135)()]

  • 提取出 response 部分结果集:

    {"httpstatus":200,"data":{"result":["2dE%2ByheYtEEN5J%2BISuDPWgjB%2Bn%2BfI0u%2FxjDCheFHi50zqdaKD6ajsl5hYjYjfITHpjGM%2BFwG2Ubh%0Aqb7P72EE18fdODM1pgMx%2BcPvAa6f89dN4tKzfkb3uZF3k0I3IvVmu6pGFJ0qqbsJaavVB3hhuyLv%0A36ufwuMKTZUzOOIQlvxX%2B86fVvm1SUcIRAaGSoEbpyc4kxsbaNNRqGUBfDk5nCA4PB2s9SigRWqX%0AvzWMT4edxjnXKVLw0iLqqo4g1G%2BeWnJLxZz84s%2BZdmZ%2F%2Fy9wAQu7AumQJ%2F%2F1VKIP3fOMaFxfl1u1%0AUNtdClzmlw8%3D|预订|64000K900530|K9005|XFA|BJQ|CSQ|BJQ|00:05|09:46|09:41|N|oSMfXgi%2Bz7E77Ucay%2FknFz1891F0CeCvRjj9xHBgJyNsJs%2Fp6oaeXaTtZ%2BE%3D|20200617|3|Q6|09|14|0|0||||无|||无||无|无|||||304010W0|3411|1|1||3026900000404200000010142000001014203000|||||||0","zpzis8Y%2FlhFToFfW4knUBU3%2Fn9gWgS61E6GOqJfuzjgbGwZb0XTfTiPr84XkNTSrSzQi0CoRWiEk%0Aw4cEhUoOOIMyQDq5PK8gDSBLBiNnq1uaxkUJKxZBz09sa8i%2FT5Rpg6BwuLyyQeJwf5edID5BdY7A%0Af%2Fb6BPDB%2FcC5mfEM7kM0eSsc1WIWHpp8Kq2WQ2W2iLc0TOAIoXuzrscc7emMuFRfq7MZ%2BybXk0pF%0A%2BKzUidwUOTNPZ7SLoL9pwT531X9%2FfFWCZnwUcQ4oELkFZBFCYY%2FyM09m3CoX5luoH8TphWsChGyV%0AUf%2Bokw%3D%3D|预订|6a000K90750F|K9075|VGQ|SZQ|CSQ|SZQ|00:26|10:28|10:02|N|jpcUdDsEdkXWsZd1QWzPwfqp0VH7TsnbKGiUha9adOhgdekpJ65XcaDbjAk%3D|20200617|3|Q7|03|07|0|0||||无|||无||无|无|||||403010W0|4311|1|1||4042400000302730000010146000001014603000|||||||",后面省略未给出],"flag":"1","map":{"CSQ":"长沙","NZQ":"福田","BJQ":"深圳东","IOQ":"深圳北","SZQ":"深圳","OSQ":"深圳西","CWQ":"长沙南"}},"messages":"","status":true}
    
    
    • 我可以看到他的结果集类似 我们的 字典

    • 但是这里我们如果想提取出来 通过 response[“data”][“result”] 按照正常思维我们可以提取出车次信息

    • 我们得到 response 后进行 以下测试:

          try:
              print(response['data']['result'])
          except Exception as e:
              print("测试代码出错,出错信息:",e)
          finally:
              print((type(response)))
      
              response = json.loads(response) # import json
              print(type(response))
              print(response["data"]["result"])
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TlyNVdFQ-1591242625135)()]

  • 根据结果我们可以看到 我们得到的 看似为 dict 的 response 其实是 str

  • 我们通过 json 模块的 loads 方法即可转换

  • 我们看到 response 结果集的:

    • response[‘data’][‘result’] 信息即为车次信息

结果制表输出

  • 接 基础模型代码 我们将 get_response() 得到的 response 传入函数 make_table()

  • def make_table(response):
        result_dict = json.loads(response)	# 将 str 转换为 dict
    
        result_list = result_dict["data"]["result"]
    
    
        # 创建表格
        pt = PrettyTable() # from prettytable import PrettyTable
    
        # 添加表头 对照官网车票表格制表
        pt.field_names = (
        ["车次", "出发站", "到达站", "出发时间", "到达时间", "历时", "商务座/特等座", "一等座", "二等座/二等包座", "高级软卧", "软卧一等座", "动卧", "硬卧二等座", "软座", "硬座",
         "无座", "其他"])
    
        for each in result_list: #  对提取的车次结果进行 逐趟 分析
            res = each.split("|") # 可以发现车次结果信息由 ”|“ 区分,即通过”|“ 切割制成列表,方便操作
            del res[0]
            del res[11]
            del res[1]
            del res[3:5]
            del res[7:15]
            del res[-13:-1]
            del res[0]  # 对照官网车票表格删除无用信息
    
            high_train = ["" for _ in range(17)] # 高铁/动车  生成列表占位
            low_train = ["" for _ in range(17)] # 普通火车
            
            # if 语句判断原因,高铁动车只有商务 一等 二等 无座,其他通过占位列表无需更改
            # 可能因疫情期间 暂时 无座 均为 无,及为默认 空,不进行更改
            if res[0][0] == 'G' or 'D':# 判断 是否为 高铁/动车
    
                high_train[0:6] = res[0:6]
                high_train[6] = res[18]
                high_train[7] = res[17]
                high_train[8] = res[16]
    
                pt.add_row(high_train) # 从头到尾 填充表格
            else:
    
                low_train[0:6] = res[0:6]
                low_train[9] = res[7]
                low_train[10] = res[9]
                low_train[11] = res[-2]
                low_train[12] = res[-7]
                low_train[14] = res[-6]
                low_train[15] = res[12]
    
                pt.add_row(low_train)
    
        return pt
    
  • 调用 main:

if __name__ == '__main__':
    dict = get_abbreviation()

    fs = input("请输入你初始站:")
    ts = input("请输入你终点站:")
    date = input("你要查询哪天的车次:")

    response = get_response(dict)

    pt = make_table(response)

    print(pt)

在这里插入图片描述

  • 我们看到 出发站、到达站 均为站点代码,此时我们可以在 make_table 函数里面 调用我们的 station_dict的代码值来查找中文名称键
def make_table(response,dict):
    result_dict = json.loads(response)

    result_list = result_dict["data"]["result"]
    # map_ = dict["data"]["map"]

    # 创建表格
    pt = PrettyTable()

    # 添加表头
    pt.field_names = (
    ["车次", "出发站", "到达站", "出发时间", "到达时间", "历时", "商务座/特等座", "一等座", "二等座/二等包座", "高级软卧", "软卧一等座", "动卧", "硬卧二等座", "软座", "硬座",
     "无座", "其他"])

    for each in result_list:
        res = each.split("|")
        del res[0]
        del res[11]
        del res[1]
        del res[3:5]
        del res[7:15]
        del res[-13:-1]
        del res[0]
		
		# 添加如下部分 即可
        for i in range(len(res)):
            for k,v in dict.items():
                if res[i] == v: # 通过字典的值,来得到键
                    res[i] = k

        high_train = ["" for _ in range(17)]
        low_train = ["" for _ in range(17)]
        if res[0][0] == 'G':

            high_train[0:6] = res[0:6]
            high_train[6] = res[18]
            high_train[7] = res[17]
            high_train[8] = res[16]

            pt.add_row(high_train)
        else:

            low_train[0:6] = res[0:6]
            low_train[9] = res[7]
            low_train[10] = res[9]
            low_train[11] = res[-2]
            low_train[12] = res[-7]
            low_train[14] = res[-6]
            low_train[15] = res[12]

            pt.add_row(low_train)

    return pt
  • 此时,我们可以看到如下结果
    在这里插入图片描述
  • 注意:我们写的出发地,可能是我们查询结果的 经停站

输出结果的美观,我们可以熟练运用 prettytable 模块 进行修改

说明

各位读者:
部分代码冗余,解释啰嗦,或不得当之处
多多指教

												欢迎留言!
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,可以使用Python编写爬虫程序来实现火车票询GUI。以下是一个简单的示例代码: ```python import requests from bs4 import BeautifulSoup import tkinter as tk def search_train(): # 获取用户输入的出发站和到达站 start_station = start_entry.get() end_station = end_entry.get() # 构造请求URL url = f"https://www.12306.cn/index/otn/leftTicket/query?leftTicketDTO.train_date=2022-01-01&leftTicketDTO.from_station={start_station}&leftTicketDTO.to_station={end_station}&purpose_codes=ADULT" # 发送请求并获取响应 response = requests.get(url) data = response.json() # 解析响应数据 train_list = data['data']['result'] # 清空结果列表 result_listbox.delete(0, tk.END) # 将火车票信息添加到结果列表 for train in train_list: result_listbox.insert(tk.END, train) # 创建GUI窗口 window = tk.Tk() window.title("火车票询") window.geometry("400x300") # 创建标签和输入框 start_label = tk.Label(window, text="出发站:") start_label.pack() start_entry = tk.Entry(window) start_entry.pack() end_label = tk.Label(window, text="到达站:") end_label.pack() end_entry = tk.Entry(window) end_entry.pack() # 创建询按钮 search_button = tk.Button(window, text="询", command=search_train) search_button.pack() # 创建结果列表 result_listbox = tk.Listbox(window) result_listbox.pack() # 运行GUI窗口 window.mainloop() ``` 这段代码使用了requests库发送HTTP请求,使用BeautifulSoup库解析HTML响应,使用tkinter库创建GUI窗口。用户可以在窗口中输入出发站和到达站,点击询按钮后,程序会向12306网站发送请求并获取火车票信息,然后将结果显示在窗口中的列表框中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值