Python基础之12306车票信息抓取案例

注:12306上请求网址链接经常变化,需要随时检查更改(我已经修改三遍了),请求的数据也有小几率发生变动,所以也需要检查更改
2018-10-22 第四遍修改 修改url地址
2019-02-11 第五遍修改 12306抓取地址修改,重新获取地址,数据结构未变
2020-10-09 第六遍修改 12306增加cookie认证,数据结构未变
2020-12-05 第七遍修改 处理获取车站编号函数的路径问题(感谢 MikeR794 的提醒,Thanks♪(・ω・)ノ)
注2:这只是一个初学python时写的demo,功能也不健全。建议了解爬虫机制后,尝试自己采集12306数据(12306只有频率检测,算是爬虫开发中最友好的网站了)

1.准备工作

(1)安装模块

本案例需要使用Python内置模块之外的docopt模块、requests模块、prettytable模块
安装方法:使用管理员身份打开控制台,使用指令 pip install 模块名分别安装三个模块


(2)简易教程

docopt:

docopt:用来帮助开发人员定义对应的终端运行指令集,当开发人员通过终端输入执行命令时,此时docopt方法会自动根据指令集完成对终端指令的匹配,之后将匹配结果以字典的形式返回给开发人员
Usage:指令管理,用户输入后系统会从上到下依次匹配
Options:指令注释,可省略
-abc:代表选择项,最终对应的结果是True或False
<from>:代表参数from,用来接收终端输入的参数数据
[]:代表里面的内容是可选的
():代表里面的内容是必须的
a|b:代表ab两种内容选择一种输入
<from >...:此时代表可变参数,该参数对应的数据是一个列表,可以用来接受任意多个参数数据

"""DOC OPT
Usage:doc_opt.py startdoc_opt.py [-gdkzt] <from> <to> <time>Options:-g      高铁-d      动车-k      普快-z      直达-t      特快
"""

注意:
1. 指令前必须加入该文件名称
2. Usage和Option间应加入回车,否则不会执行指令
3. 该段文字写在文件最上部


prettytable

prettytable用于将数据整理成表格输出到控制台,只适用于控制台输出

# 创建表格对象
table = PrettyTable(['name', 'age', 'sex', 'birth'])
# 添加行
table.add_row(['小呆呆', 20, '男', '2018-9-18'])
# 显示
print(table)

prettytable中有很多属性,在此不再一一讲解,有兴趣可以百度查找


requests

requests:完成网络请求的模块
网络请求:服务器和客户端进行数据交互的过程
网络请求需要两部分内容
网址(url):用来帮助开发人员找到指定的服务器
参数:用来告知服务器需要提供的服务内容,可以保证服务器能够提供精准的服务

在网络请求中有两个非常流行的请求方式,分别是:GET和POST
GET:客户端向服务器发起请求同时把服务器需要的数据一并传给服务器。因此GET请求的网址和参数通过"?"组合在一起,此种方式不利于数据的安全。同时GET请求只能向服务器发送最多288个字节的数据,因此数据传输量比较小。综上GET一般用来向服务器获取数据
POST:客户端向服务器发去链接请求,链接成功后再向服务器发送数据,因此POST请求的网址和参数是分开的,这样可以保证数据的安全性。同时POST向服务器最多发送5M的数据,数据传输量比较大。综上POST一般用来向服务器传送数据。

import requests
url = "https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2018-08-08&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=AYF&purpose_codes=ADULT"
result = requests.get(url)
# result.json()将网址对应返回的JSON数据解析成当前语言能够识别的数据格式(JSON解析),注意返回的数据必须是JSON字符串
# JSON数据是前段和后台约定好的一种数据结构,该数据由[]和{}相互嵌套组合形成的一种字符串数据,JSON数据一般不能直接被某一种语言识别,需要通过JSON解析之后才能被识别
print(result.json())
# 结果
# {'data': {'flag': '1', 'map': {'AYF': '安阳', 'BXP': '北京西'}, 'result': ['|预订|240000K1790Q|K179|BXP|ZZF|BXP|AYF|22:20|04:05|05:45|N|%2FE0zQ4Fqe6WU%2B6NQ1gdbxJZZMS6EvlV2EOsFfoORYKYpvImszsOgHJyG2k8%3D|20180808|3|PB|01|04|0|0||||无|||无||无|无|||||10401030|1413|0']}, 'httpstatus': 200, 'messages': '', 'status': True}

注:这里只是简易的教程,想要深入学习请自行百度


2.获得数据源

步骤一:使用拥有开发者模式的浏览器打开目标网站,本案例打开为12306

步骤二:使用查询功能,并筛选开发者调试窗口获得的数据,得到**车次信息url及相关内容

步骤三:得到url信息后会发现,url中参数并不是我们设置的汉字地名,而是服务器提供的地名简写,那下一步就是获取地名简写信息

首先同样使用开发者工具筛选需要的文件及其url

根据该url可获取大量信息,但信息杂乱,需要对信息进行处理

# 获得信息
response = requests.get("https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9061")
# 将信息头部和尾部删掉
data_str = response.text.strip("var station_names =';")
# 使用@符号分割成列表(为什么要用@?可查看具体数据)
data_list = data_str.split("@")[1:]
data_dict = {}
# 遍历列表
for item in data_list:# 再次使用|分割list_temp = item.split("|")# 存入字典data_dict[list_temp[1]] = list_temp[2]
print(data_dict)

由于数据量过大,不再展示结果,可自行尝试


##3.编写程序
编写程序中需要大量分析,比如分析获得的数据中第几位是硬座,第几位是软座。分析无法描述,请自行分析,以下直接展示结果:

# 票类
# tickets.py# 票类
# tickets.pyimport os
import requests
import json
from prettytable import PrettyTable# 定义票类
class Ticket(object):def __init__(self, **kw):# 初始化PrettyTableself.__table = PrettyTable(["车次", "出发站\到达站", "出发时间\到达时间", "历时", "商务座\特等座", "一等座", "二等座", "高级软卧", "软卧", "动卧", "硬卧", "软座", "硬座", "无座", "是否有票", "备注"])# 起点self.__from = kw.get('from')# 终点self.__to = kw.get('to')# 日期self.__time = kw.get('time')# 初始化车站信息self.__station_VPN = self.get_station_vpn()# 初始化车次信息self.__trains = self.check_ticket()# 定义方法完成车站VPN编号的获取def get_station_vpn(self):path = os.getcwd() + "/data"file_path = path + "/data.txt"# 判断data文件是否存在if not os.path.exists(file_path):# # 如果没有文件夹,创建if not os.path.exists(path):os.mkdir(path)# 向服务器发起请求并且返回数据进行处理url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9061"response = requests.get(url)# 去除字符串左右两端指定的字符,只获取''之间的内容data_str = response.text.strip("var station_names =';")# 将车站数据安装@符号分割data_list = data_str.split("@")[1:]# 将列表中的字符串再次处理,最终转为成字典结构data_dict = {}for item in data_list:list_temp = item.split("|")data_dict[list_temp[1]] = list_temp[2]# 将处理之后的数据转换成json字符串(JSON归档,在Python中我们一般只对列表和字典进行json归档)data_str = json.dumps(data_dict)# 将数据写入到文件file = open(file_path, "w")file.write(data_str)file.close()return data_dictelse:# 之前已经获取过数据,此时只需将数据读取出来即可file = open(file_path, "r")data_str = file.read()file.close()# 将读取的数据做json解析data_dict = json.loads(data_str)return data_dict# 定义方法完成车票查询操作def check_ticket(self):# 初始化session, 获取cookieticket_request = requests.session()res = ticket_request.get("https://kyfw.12306.cn/otn/leftTicket/init")cookies = res.cookies.items()# cookie = requests.utils.dict_from_cookiejar(cookies)cookie_list = []for item in cookies:cookie_list.append("{}={}".format(item[0], item[1]))cookie = ";".join(cookie_list)# 初始化包含各种车次的字典trains = {'g': [], 'd': [], 'k': [], 't': [], 'z': []}# url地址及参数(这里经常变化)url = "https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={time}&leftTicketDTO.from_station={from_st}&leftTicketDTO.to_station={to}&purpose_codes=ADULT".format(time=self.__time, from_st=self.__station_VPN.get(self.__from), to=self.__station_VPN.get(self.__to))# 头部信息header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36","Referer": "https://kyfw.12306.cn/otn/leftTicket/init","Host": "kyfw.12306.cn","Cookie": cookie}# 获取数据response = ticket_request.get(url, headers=header)# 将字符串解析json转为字典# print(response.text)data_dict = response.json()# 判断是否在范围时间内# 取出字典中列车数据data_list = data_dict.get('data').get('result')if len(data_list) != 0:# 遍历处理列车数据for string in data_list:# 创建临时列表list_temp = []# 将字符串类型的列车信息根据'|'分割成列表item = string.split("|")# 将数据添加到临时列表中,因为12306数据可能会更新,所以下标不一定和现在一样,最好自己根据各列车数据分析list_temp.append(item[3]) # 车次start = {v: k for k, v in self.__station_VPN.items()}[item[6]] # 出发站end = {v: k for k, v in self.__station_VPN.items()}[item[7]] # 到达站if item[4] == item[6]:start = "始 " + startelse:start = "过 " + startif item[5] == item[7]:end = "终 " + endelse:end = "过 " + endlist_temp.append(start + "\n" + end)# {v: k for k, v in self.__station_VPN.items()}[item[4]] # 始发站# {v: k for k, v in self.__station_VPN.items()}[item[5]] # 终点站# item[8] # 出发时间# item[9] # 到达时间list_temp.append(item[8] + "\n" + item[9])list_temp.append(item[10]) # 历时list_temp.append(item[32]) # 商务座list_temp.append(item[31]) # 一等座list_temp.append(item[30]) # 二等座list_temp.append(item[21]) # 高级软卧list_temp.append(item[23]) # 软卧list_temp.append(item[33]) # 动卧list_temp.append(item[28]) # 硬卧list_temp.append(item[24]) # 软座list_temp.append(item[29]) # 硬座list_temp.append(item[26]) # 无座# 是否有票if item[11] == 'Y':list_temp.append("是")else:list_temp.append("否")list_temp.append(item[1]) # 备注信息,可以预定则默认‘预定’# 将无内容的项填充符号'--'for index in range(len(list_temp)):if not list_temp[index]:list_temp[index] = '--'# 根据车次类型将数据存入相关车次类型列表train_type = list_temp[0][0]if train_type == 'G':trains['g'].append(list_temp)elif train_type == 'D':trains['d'].append(list_temp)elif train_type == 'Z':trains['z'].append(list_temp)elif train_type == 'T':trains['t'].append(list_temp)else:trains['k'].append(list_temp)else:print("区间内没有相关车次")exit()return trains# 显示结果  kinds用来存储需要查询的列车类别,比如['k','g']:查询普快和高铁def show_ticket(self, kinds):# 如果kinds为空这默认查询全部if len(kinds) == 0:kinds = ['k', 'g', 'z', 't', 'd']# 循环kindsfor kind in kinds:# 将kinds中的信息作为标识,读取字典中信息for item in self.__trains[kind]:# 使用prettytable加载信息self.__table.add_row(item)# 设置左对齐,为了美观self.__table.align = 'l'# 显示表格print(self.__table)
# 操作文件
# main_operate.py"""Main Operate
Usage:main_operate.py [-gkdtzGKDTZ] <from> <to> <time>Options:from    出发站to      到达站time    日期(格式 yyyy-mm-dd)-g|-G   高铁-d|-D   动车-k|-K   普快-z|-Z   直达-t|-T   特快
"""from docopt import docopt
# 这里的tickets不能加前缀,docopt是按照当前目录查找文件的
from tickets import Ticket
import timeif __name__ == "__main__":arguments = docopt(__doc__)# 根据用户输入的选择项,完成对应类别列车的信息查询kinds = []if arguments.get('-g') or arguments.get('-G'):kinds.append('g')if arguments.get('-z') or arguments.get('-Z'):kinds.append('z')if arguments.get('-t') or arguments.get('-T'):kinds.append('t')if arguments.get('-k') or arguments.get('-K'):kinds.append('k')if arguments.get('-d') or arguments.get('-D'):kinds.append('d')# 根据时间判断是否在正确区间内# 获取当前时间time_date = time.strftime("%Y-%m-%d", time.localtime())# 获取用户输入时间并分割time_input = arguments.get("<time>").split('-')# 将用户输入的月份和日期变成两位表示time_input = list(map(lambda x: ("0" + x) if len(x) == 1 else x, time_input))# 拼接成字符串time_input = "-".join(time_input)# 获得目标时间的时间戳target_time = time.mktime(time.strptime(time_input, "%Y-%m-%d"))# 获取当前时间时间戳now_time = time.time()# 获取时间间隔time_space = target_time - now_time# 时间间隔在今天到之后29天之间(共30天)if -(24 * 60 * 60) <= time_space <= 29 * 24 * 60 * 60:dic = {"from": arguments.get('<from>'), "to": arguments.get('<to>'), "time": time_input}ticket = Ticket(**dic)ticket.show_ticket(kinds)else:print("超出时间范围")

测试结果
在控制台中进入文件所在目录,输入python 主入口文件.py [from] [to] [time] [-kzdgt] 进行测试

Python基础之12306车票信息抓取案例相关推荐

  1. 用PyQt5编辑 12306车票信息爬取程序

    1.搭载QT环境 按win+R输入 pip install pyqt5 下载QT5 当然也可以去Qt的官网的下载 ,使用命令行更快捷方便 所以建议使用命令行 ,去官网下载安装有它的好处就是不用自己安装 ...

  2. Python爬虫 - 解决动态网页信息抓取问题

    作者:K同学啊 时间:2020年7月29日 写在前面:本文仅供参考学习之用,请勿用作其他用途. 1.嵌入式网页爬取 举例:最常见的分页式网页 这里我用天津市的信访页面来做示例,(地址:http://w ...

  3. Python爬取12306车票信息

    Python3爬取12306车票信息 第一次写爬虫,咱从入门级--12306车票爬取 开始 我们要爬取的信息是https://www.12306.cn/index/上的车票信息 当我们选择出发地和目的 ...

  4. python爬虫资源大全_Python爬虫抓取纯静态网站及其资源(基础篇)

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 以下文章来源于腾讯云 作者:程序员宝库 **( 想要学习Python?Python ...

  5. 12306车站信息爬取(4)——添加车票的票价信息

    在前三篇文章的基础上: 12306车站信息爬取(1)--输入条件的判断,包括出发站,到达站,和出发时间,并获取车次信息的链接 12306车站信息爬取(2)--输入出发站,到达站和出发时间,获取车次信息 ...

  6. Python爬虫之XPath基础教程:用代码抓取网页数据

    Python爬虫之XPath基础教程:用代码抓取网页数据 在网络时代,网页数据是获取信息和进行分析的最重要的来源之一.Python的爬虫技术让我们可以轻松抓取网页数据,并进行数据处理.XPath是一种 ...

  7. python——爬虫实现网页信息抓取

    首先实现关于网页解析.读取等操作我们要用到以下几个模块 import urllib import urllib2 import re 我们可以尝试一下用readline方法读某个网站,比如说百度 de ...

  8. python京东图书信息抓取

    import requests from bs4 import BeautifulSoup from fake_useragent import UserAgent #京东图书信息抓取 def get ...

  9. 12306车站信息爬取(1)——输入条件的判断,包括出发站,到达站,和出发时间,并获取车次信息的链接

    12306车站信息的爬取是一个比较复杂的系统,爬取需要的信息不是很难,但是要将最终的结果做的完善和美观却不是那么容易.作为一个学习Python的新手,我想把练习和整理结合起来,希望大家可以相互交流和探 ...

最新文章

  1. 转iOS性能优化:Instruments使用实战
  2. python学习笔记(六)——函数的作用域和装饰器
  3. mysql 连接校对_教你轻松的掌握 MYSQL连接字符集和校对
  4. alphac测试和bata测试区别_电缆识别仪与电缆故障测试仪的区别
  5. docker网络之macvlan
  6. 如何向.js文件传变量(如session)
  7. [Usaco2009 Feb]Bullcow 牡牛和牝牛
  8. oracle分组后合并(wm_concat)其中一个字段
  9. php中获取随机数的方法,PHP实现获取随机数的方法
  10. 美男子第一次的JAVA博客
  11. fork、vfork、clone 三者的区别
  12. 机器学习-预测之BP神经网络模型原理及实战
  13. 安装WPS后,word文件无法预览,无法右键新建的解决办法
  14. 私有云厂商云宏破解金融行业转型“数字底座”难题
  15. NLP自然语言处理之情感分析分析讲解、知识构建
  16. stm32控制步进电机
  17. [读书笔记]读<<观止>>有感
  18. Git问题:右键没有Git Bash Here的解决办法
  19. QT连接SQL server 数据库
  20. 拓嘉辰丰电商:拼多多无货源开店该怎么做

热门文章

  1. 关于RikiRobot ROS机器人的介绍
  2. 单目标应用:基于蛇群优化算法(SO)的无人机(UAV)三维路径规划(提供MATLAB代码)
  3. STLink V2烧录SWIM和SWD接口接线图
  4. 362-git的远程仓库操作
  5. 编译原理——DFA的编程实现
  6. layui数据表格怎么传参过去
  7. 关于THINKPAD X1 Carbon(6th) 卡顿问题
  8. Infineon Aurix TC397启动过程学习
  9. vue导出json为excel
  10. 边缘计算网关EasyNVR及云服务EasyCVR、EasyDSS等联合打造TSINGSEE青犀视频云边端协同一体化