欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Python大神教你 】实现12306余票监控

程序员文章站 2024-03-17 21:48:10
...

前言

由于经常会遇到没票等现象,所以需要使用软件进行抢票,但由于一些软件有优先级问题,对于不舍得花钱的我来讲,并不是很好的体验,所以想自己尝试写一个类似的功能。

 

环境/插件

  • Python3.6
  • urllib(用于请求数据)
  • smtplib(发送邮件)

 

文件目录

GrabTicket文件夹

  • city.py(12306城市)

  • GrabTicket.py(入口文件)

  • GrabTicketOperation.py(主文件)

  • GrabTicketSmtp.py(发送邮件文件)

 

分析

首先我们打开12306余票查询窗口

【Python大神教你 】实现12306余票监控

 

上图红色框的地方,就是表示列车有无车票的地方,我们需要根据这里边的数据来判断。

这里边有一些需要注意的就是,里边表示有票的有字符串“有”和数字“2”,所以我们需要对这两种情况进行判断。

接下来我们使用浏览器的开发者工具,来检查看看是否有接口可以使用:

【Python大神教你 】实现12306余票监控

这里我们可以看出,12306的列车查询是使用接口调用的,我们再来看看接口返回的数据:

 

【Python大神教你 】实现12306余票监控

我们可以清晰的看到,我们需要的列车数据是在data里边的result里边的数组,所以后面,我们只需要获取这里边的数据来判断就可以了。

 

分析数据

首先,我们得先分析数据,得出我们需要的数据字段,所以我们先写一段程序用来分析:

【python学习qq裙:10667510 送入门学习资料,萌新程序媛大本营】
# 复制接口数据result里边的一条数据出来分析
results = ["null|23:00-06:00系统维护时间|6i000D312606|D3126|IOQ|NJH|IOQ|AOH|07:00|18:43|11:43|IS_TIME_NOT_BUY|qYF9CwzWBb4rPwv7Upcl6nOKai0yleG2FqmgmU4EFKXjmLhu|20180721|3|Q6|01|28|1|0|||||||有||||有|无|||O0M0O0|OMO|0"]
 
# 初始化数组键值
c = 0

# 对结果集进行循环
for i in results:
        # 将数据拆分成新的数组,并进行循环
	for n in i.split('|'):
                # 输出数组中每一个的数据n,以及下标值c
		print('[%s] %s' %( c,n ))
                # 下标值+1
		c += 1
        # 重置下标值c
	c = 0
        # 多个数据换行
	print('\n\t')

运行代码,我们来看看效果图:

【Python大神教你 】实现12306余票监控

测试多几次之后,我们可以得出我们需要的数据所在的位置,接下来我们修改下程序进行输出:

# 复制接口数据result里边的一条数据出来分析
results = ["null|23:00-06:00系统维护时间|6i000D312606|D3126|IOQ|NJH|IOQ|AOH|07:00|18:43|11:43|IS_TIME_NOT_BUY|qYF9CwzWBb4rPwv7Upcl6nOKai0yleG2FqmgmU4EFKXjmLhu|20180721|3|Q6|01|28|1|0|||||||有||||有|无|||O0M0O0|OMO|0"]
j = 1

# 初始化数组下标值
c = 0

# 初始化列车数组的下标值
index = 0

# 初始化列车数组
trains = []

# 对结果集进行循环
for i in results:

	# 为列车数组新增一个空数组元素
	trains.append([])

	# 将数据拆分成新的数组,并进行循环
	for n in i.split('|'):

		# 输出数组中每一个的数据n,以及下标值c
		print('[%s] %s' %( c,n ))

		# 将每一个数据依次放入到列车数组中
		trains[index].append(n)

		# 下标值+1
		c += 1

	# 重置下标值c
	c = 0

	# 多个数据换行
	print('\n\t')

	# 列车数组下标值+1
	index += 1

# 对处理好的列车数组进行循环遍历
for train in trains:
	
	# 打印我们所需要的数据
	print('火车:%s' %(train[3]))
	print('出发地:%s' %(train[6]))
	print('目的地:%s' %(train[7]))
	print('发车时间:%s' %(train[8]))
	print('到达时间:%s' %(train[9]))
	print('历时时间:%s' %(train[10]))
	print('商务座/特等座:%s' %(train[32]))
	print('一等座:%s' %(train[31]))
	print('二等座:%s' %(train[30]))
	print('高级软卧:%s' %(train[21]))
	print('软卧:%s' %(train[23]))
	print('硬卧:%s' %(train[28]))
	print('硬座:%s' %(train[29]))
	print('无座:%s' %(train[26]))
	print('\n\t')

运行,我们来看看结果:

【Python大神教你 】实现12306余票监控

这里边就是我们的结果集了,我们去12306页面对照一下:

【Python大神教你 】实现12306余票监控

看,我们的数据能对的上,证明我们已经分析对了数据,接下来,我们就可以实现我们的爬虫代码了:

from urllib import request
import ssl
import json

# 通过爬虫爬取数据
def getTrains():

	# 请求地址
	url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2018-07-21&leftTicketDTO.from_station=SZQ&leftTicketDTO.to_station=SHH&purpose_codes=ADULT'
	# 请求头
	headers = {
		'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
	}
	# 设置请求
	req = request.Request(url, headers=headers)
	# 发送请求
	html = request.urlopen(req).read().decode('utf-8')

	# 格式化数据
	dict = json.loads(html) 
	# 获取想要的数据
	result= dict['data']['result']

	return result

# 复制接口数据result里边的一条数据出来分析
results = getTrains()

# 初始化数组下标值
c = 0

# 初始化列车数组的下标值
index = 0

# 初始化列车数组
trains = []

# 对结果集进行循环
for i in results:

	# 为列车数组新增一个空数组元素
	trains.append([])

	# 将数据拆分成新的数组,并进行循环
	for n in i.split('|'):

		# 将每一个数据依次放入到列车数组中
		trains[index].append(n)

		# 下标值+1
		c += 1

	# 重置下标值c
	c = 0

	# 列车数组下标值+1
	index += 1

# 对处理好的列车数组进行循环遍历
for train in trains:

	# 打印我们所需要的数据
	print('火车:%s' %(train[3]))
	print('出发地:%s' %(train[6]))
	print('目的地:%s' %(train[7]))
	print('发车时间:%s' %(train[8]))
	print('到达时间:%s' %(train[9]))
	print('历时时间:%s' %(train[10]))
	print('商务座/特等座:%s' %(train[32]))
	print('一等座:%s' %(train[31]))
	print('二等座:%s' %(train[30]))
	print('高级软卧:%s' %(train[21]))
	print('软卧:%s' %(train[23]))
	print('硬卧:%s' %(train[28]))
	print('硬座:%s' %(train[29]))
	print('无座:%s' %(train[26]))
	print('\n\t')

我们运行一下,看看结果:

【Python大神教你 】实现12306余票监控

这样,我们就能得到我们每一辆列车的数据了。

 

分析URL地址

第一步,我们已经分析出了我们的数据,现在我们开始写爬虫,再写之前,我们还需要分析一下12306列车接口URL的规律,这样才方便我们组合URL,查询不同城市、时间点的列车数据:

 

# 12306接口地址

https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2018-07-21&leftTicketDTO.from_station=SZQ&leftTicketDTO.to_station=SHH&purpose_codes=ADULT

 

这里我们主要注意以下几个参数的用途:

  • leftTicketDTO.train_date:出发时间
  • leftTicketDTO.from_station:出发地
  • leftTicketDTO.to_station:目的地

 

获取城市json文件

我们同时还注意到,我们的城市给转换成了大写的英文字母,这是12306自己的转换机制,所以我们需要试着来找一下12306是否有保存城市的json文件:

【Python大神教你 】实现12306余票监控

这样,我们就找到了12306的城市json文件了,我们可以将它保存下来,现在我们就准备就绪了。

 

根据城市名,获取城市代号

用户在输入了城市之后,我们需要获取到用户的城市代号,从上例的代码中,我们可以看到我们引入了一个city.py的文件,我就是将城市处理放在这个文件里边进行的:

# -*- coding: UTF-8 -*-
# @Time             : 2018/04/04 14:58
# @Author           : 小罗
# @File             : city.py
# @Software         : PyCharm
# @Python Version           : 3.6
# @About            : 12306城市文件
 
 
# 获取城市列表
def getCitys(city):
    # 城市数据(城市数据过多,这里只显示一部分,请自行去12306处获取完整的城市数据)
    favorite_names = '@bjb|北京北|VAP|beijingbei|bjb|aaa@qq.com|北京东|BOP|beijingdong|bjd|aaa@qq.com|北京|BJP|beijing|bj|aaa@qq.com|北京南|VNP|beijingnan|bjn|aaa@qq.com|北京西|BXP|beijingxi|bjx|aaa@qq.com|广州南|IZQ|guangzhounan|gzn|aaa@qq.com|重庆北|CUW|chongqingbei|cqb|aaa@qq.com|重庆|CQW|chongqing|cq|aaa@qq.com|重庆南|CRW|chongqingnan|cqn|aaa@qq.com|重庆西|CXW|chongqingxi|cqx|aaa@qq.com|广州东|GGQ|guangzhoudong|gzd|10';
    # 遍历所有城市
    for i in favorite_names.split( '@'):
        if i:
            tmp = i.split( '|')
            if city == tmp[1]:
                return tmp[2]
 
    return False

这样,我们就可以通过调用city.getCitys()方法,传入我们输入的城市名称,就可以准确的获取我们的城市代号了。

 

实现用户输入

首先,我们需要让用户输入自己需要查询的出发地、目的地、出发时间,并对这些数据进行判断是否合格:

import GrabTicketOperation
import ssl
import city
import time
import re
from datetime import datetime

# 关闭ssh证书验证
ssl._create_default_https_context = ssl._create_unverified_context 

# 输入城市
def cityStation(ntype = 1):

        # 换行
	print('\n\t')

	# 初始化提示语
	passtext = ''
	# 判断是出发地还是目的地
	if (ntype == 1):
		passtext = '出发地'
	else:
		passtext = '目的地'
	# 开始无限循环,保证用户输对为止
	while(1):
		# 获取用户输入的数据
		city_station = input('请输入%s:' %( passtext ))
		# 检查输入的城市
		city_stations = city.getCitys(city_station)
		# 判断输入是否正确
		if (city_stations == False):
			# 不正确,提示,并且重新输入
			print('找不到 %s 这个城市' %(city_station))
		else:
			# 输入正确,跳出循环
			break
	# 返回正确的城市编号
	return city_stations

# 验证时间
def timeStation():

        # 换行
	print('\n\t')

	# 开始无限循环,保证用户输对为止
	while(1):
		# 获取用户输入的数据
		setOutTime = input('请输入出发时间(例:2018-04-04):')
		# 判断时间格式是否正确
		if (checkTimeFormat(setOutTime) == False):
			print('请输入正确的时间格式,如:2018-04-04')
		else:
			# 将用户输入的日期转化为时间戳
			timeArray = time.strptime(setOutTime, "%Y-%m-%d")
		    # 转换为时间戳:
			timeStamp = int(time.mktime(timeArray))
			# 获取当前时间的时间戳
			nowtime = time.time()
			# 获取当天0点的时间戳
			nowtimeStamp = int(nowtime - nowtime % 86400 - 28800)
			# 判断时间大小
			if (timeStamp < nowtimeStamp):
				print('出发日期不能小于当前时间')
			else:
				break

	# 返回正确的城市编号
	return setOutTime

def checkTimeFormat(setOutTime):

	# 判断日期格式
	date_text = re.search(r"(\d{4}-\d{2}-\d{2})",setOutTime)
	# 判断时间格式是否正确
	try:

		if date_text == None:
			return False

		date_text = date_text.group(0)

		if date_text != datetime.strptime(date_text, "%Y-%m-%d").strftime('%Y-%m-%d'):
			return False
		else:
			return True

	except ValueError:
		return False



# 出发地
from_station = cityStation(1)
# 目的地
to_station = cityStation(2)
# 出发时间
setOutTime = timeStation()
# 实例化类(后面需要编写这个类)
grabTicket = GrabTicketOperation.GrabTicket(from_station, to_station, setOutTime)
# 输入数据
grabTicket.callQueryTrains()

我们运行这个程序(需要将GrabTicketOperation.py,这个类的引入和使用给注释掉,后期会增加这个类),来看看结果:

【Python大神教你 】实现12306余票监控

 

编写操作类

接下来就是编写我们的操作类了,这也是主要的文件

# -*- coding: UTF-8 -*-
# @Time 			: 2018/04/04 14:58
# @Author			: 小罗
# @File 			: GrabTicketOperation.py
# @Software			: PyCharm
# @Python Version	: 3.6
# @About 			: 12306抢票操作类

from splinter.browser import Browser
import urllib
from urllib import request
import ssl
import city
import json
from GrabTicketSmtp import GrabTicketSmtp

class GrabTicket:

	# 出发地
	from_station = ''

	# 目的地
	to_station = ''

	# 出发时间
	setOutTime = ''

	# 123056列车请求路径
	durl = 'https://kyfw.12306.cn/otn/leftTicket/query?'

	# 构造函数
	# @string from_station 出发地
	# @string to_station 目的地
	# @string time 时间,如:2018-04-04
	def __init__(self, from_station, to_station, setOutTime):

		# 出发城市
		self.from_station = from_station

		# 目的地城市
		self.to_station = to_station

		# 出发时间
		self.setOutTime = setOutTime

	# 拼接URL地址
	def getSplicingUrl(self):

		url = self.durl + 'leftTicketDTO.train_date=' + urllib.parse.quote(self.setOutTime) + '&leftTicketDTO.from_station=' + urllib.parse.quote(self.from_station) + '&leftTicketDTO.to_station=' + urllib.parse.quote(self.to_station) + '&purpose_codes=ADULT'

		return url

	# 抓取数据
	def curlTrainsInfo(self):
		
		# 获取链接
		url = self.getSplicingUrl()
		# 请求头
		headers = {
			# 'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
			'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
		}
		# 设置请求
		req = request.Request(url, headers=headers)
		# 发送请求
		html = request.urlopen(req).read().decode('utf-8')

		# 格式化数据
		dicts = json.loads(html)
		# 获取想要的数据
		result = dicts['data']['result']

		return result

	# 处理数据
	def handleResultDatas(self, datas):
		j = 1
		c = 0
		index = 0
		trains = []
		for i in datas:
			trains.append([])
			for n in i.split('|'):
				#print('[%s] %s' %( c,n ))
				trains[index].append(n)
				c += 1
			c = 0
			#print('\n\t')
			j += 1
			index += 1

		return trains

	# 判断是否大于0
	def isCheckValueInt(self, value):

		value = int(value)

		if value > 0:
			# 内容
			self.intTrain = True
			return True

		return False

	# 将数据转化为整形
	def StringTurnInt(self, train):

		if train[32].isdigit():
			if self.isCheckValueInt(train[32]):
				return True

		if train[31].isdigit():
			if self.isCheckValueInt(train[31]):
				return True

		if train[30].isdigit():
			if self.isCheckValueInt(train[30]):
				return True

		if train[21].isdigit():
			if self.isCheckValueInt(train[21]):
				return True

		if train[23].isdigit():
			if self.isCheckValueInt(train[23]):
				return True

		if train[28].isdigit():
			if self.isCheckValueInt(train[28]):
				return True
	
		if train[29].isdigit():
			if self.isCheckValueInt(train[29]):
				return True

		if train[26].isdigit():
			if self.isCheckValueInt(train[26]):
				return True

	# 输出数据
	def outputResults(self, trains):

		content = ''

		# 初始化序号
		num = 1

		for train in trains:

			self.isIntTrain = False

			self.StringTurnInt(train)

			if train[32] == '有' or train[31] == '有' or train[30] == '有' or train[21] == '有' or train[23] == '有' or train[28] == '有' or train[29] == '有' or train[26] == '有' or self.isIntTrain == True:

				self.traincontents = []
				# 内容前缀
				traincontent_prefixs = [
					'<tr>',
					'<td>' + str(num) + '</td>',
					'<td>' + train[3] + '</td>',
					'<td>' + train[6] + '</td>',
					'<td>' + train[7] + '</td>',
					'<td>' + train[8] + '</td>',
					'<td>' + train[9] + '</td>',
					'<td>' + train[10] + '</td>'
				]
				traincontent_prefix = ''.join(traincontent_prefixs)
				# 内容后缀
				traincontent_suffix = '</tr>'

				num = num + 1

				# 商务座
				self.getIsStandbyTicket(train[32])

				# 一等座
				self.getIsStandbyTicket(train[31])

				# 二等座
				self.getIsStandbyTicket(train[30])

				# 高级软卧:
				self.getIsStandbyTicket(train[21])

				# 软卧:
				self.getIsStandbyTicket(train[23])

				# 硬卧:
				self.getIsStandbyTicket(train[28])

				# 硬座:
				self.getIsStandbyTicket(train[29])

				# 无座:
				self.getIsStandbyTicket(train[26])

				traincontent = ''.join(self.traincontents)

				content = content + traincontent_prefix + traincontent + traincontent_suffix

		if content == '':

			return False

		return content

	# 获取是否有无余票
	def getIsStandbyTicket(self, value):

		if value.isdigit():
			# 内容
			self.traincontents.append('<td style="color: #26a306;font-weight: 400;">' + value + '</td>')
		elif value == '有':
			# 内容
			self.traincontents.append('<td style="color: #26a306;font-weight: 400;">有</td>')
		else:
			self.traincontents.append('<td>无</td>')

	# 获取邮件内容 - 标题
	def getEmailContentTitle(self):

		emailTitle = '<tr><th colspan="30">12306余票监控</th></tr>'

		return emailTitle

	# 获取邮件内容 - 列表标题
	def getEmailContentListTitle(self):

		emaillistTitles = [
			'<tr>',
			'<td>序号</td>',
			'<td>列车</td>',
			'<td>出发地</td>',
			'<td>目的地</td>',
			'<td>发车时间</td>',
			'<td>到达时间</td>',
			'<td>历时时间</td>',
			'<td>商务座/特等座</td>',
			'<td>一等座</td>',
			'<td>二等座</td>',
			'<td>高级软卧</td>',
			'<td>软卧</td>',
			'<td>硬卧</td>',
			'<td>硬座</td>',
			'<td>无座</td>',
			'</tr>'
		]

		return ''.join(emaillistTitles)

	# 发送邮件
	def sentEmail(self, trains):

		# 获取标题
		emailTitle = self.getEmailContentTitle()

		# 获取列表标题
		emaillistTitle = self.getEmailContentListTitle()

		# 获取列表内容
		emailListContent = self.outputResults(trains)

		if emailListContent == False:

			return False

		# 拼接数据
		emailContents = [
			'<table>',
			'<thead>',
			emailTitle,
			emaillistTitle,
			'</thead>',
			'<tbody>',
			emailListContent,
			'</tbody>',
			'</table>'
		]

		emailContent = ''.join(emailContents)

		# 实例化邮件类
		grabTicket = GrabTicketSmtp('aaa@qq.com', emailContent)
		# 输入数据
		grabTicket.sendEmail()

		return True


	# 调用函数
	def callQueryTrains(self):
		
		# 抓取数据
		result = self.curlTrainsInfo()

		# 处理数据
		trains = self.handleResultDatas(result)

		# 输出数据
		self.sentEmail(trains)

 

编写邮件发送类

 
【python学习qq裙:10667510 送入门学习资料,萌新程序媛大本营】
# -*- coding: UTF-8 -*-
# @Time 			: 2018/04/04 14:58
# @Author			: 小罗
# @File 			: GrabTicketSmtp.py
# @Software			: PyCharm
# @Python Version	: 3.6
# @About 			: 发送邮件类

import smtplib  
from email.header import Header  
from email.mime.text import MIMEText

class GrabTicketSmtp:

    # SMTP服务器
    mail_host = "smtp.163.com"

    # 用户名
    mail_user = "aaa@qq.com"

    # 授权密码,非登录密码
    mail_pass = ""

    # 发件人邮箱(最好写全, 不然会失败)  
    sender = "aaa@qq.com"

    # 邮箱标题
    title = '好消息!列车有余票呀!'

    # 构造函数
    # @string receivers 收件人
    # @string content 邮箱内容
    def __init__(self, receivers, content):

        # 邮件内容
        self.content = content

        # 收件人
        self.receivers = [receivers]

    # 获取邮箱标题
    def getTitle(self):

        return self.title
  
    # 发送邮件
    def sendEmail(self): 

        print(self.mail_host)
        print(self.mail_user)
        print(self.mail_pass)
        print(self.sender)
        print(self.title)
        print(self.receivers)

        # 内容, 格式, 编码
        message = MIMEText(self.content, 'html', 'utf-8') 
        message['From'] = "{}".format(self.sender)  
        message['To'] = ",".join(self.receivers)  
        message['Subject'] = self.getTitle()  
      
        try: 
            # 启用SSL发信, 端口一般是465
            smtpObj = smtplib.SMTP_SSL(self.mail_host, 465)  
            # 登录验证
            smtpObj.login(self.mail_user, self.mail_pass)
            # 发送 
            smtpObj.sendmail(self.sender, self.receivers, message.as_string())
            # 发送成功
            print("发送成功")  
        except smtplib.SMTPException as e: 
            # 发送失败
            print(e)   
  
if __name__ == '__main__':  
    sendEmail()
【python学习qq裙:10667510 送入门学习资料,萌新程序媛大本营】

 

完善调用类

import GrabTicketOperation
import ssl
import city
import time
import re
from datetime import datetime

# 关闭ssh证书验证
ssl._create_default_https_context = ssl._create_unverified_context 

# 输入城市
def cityStation(ntype = 1):
	# 初始化提示语
	passtext = ''
	# 判断是出发地还是目的地
	if (ntype == 1):
		passtext = '出发地'
	else:
		passtext = '目的地'
	# 开始无限循环,保证用户输对为止
	while(1):
		# 获取用户输入的数据
		city_station = input('请输入%s:' %( passtext ))
		# 检查输入的城市
		city_stations = city.getCitys(city_station)
		# 判断输入是否正确
		if (city_stations == False):
			# 不正确,提示,并且重新输入
			print('找不到 %s 这个城市' %(city_station))
		else:
			# 输入正确,跳出循环
			break
	# 返回正确的城市编号
	return city_stations

# 验证时间
def timeStation():

	# 开始无限循环,保证用户输对为止
	while(1):
		# 获取用户输入的数据
		setOutTime = input('请输入出发时间(例:2018-04-04):')
		# 判断时间格式是否正确
		if (checkTimeFormat(setOutTime) == False):
			print('请输入正确的时间格式,如:2018-04-04')
		else:
			# 将用户输入的日期转化为时间戳
			timeArray = time.strptime(setOutTime, "%Y-%m-%d")
		    # 转换为时间戳:
			timeStamp = int(time.mktime(timeArray))
			# 获取当前时间的时间戳
			nowtime = time.time()
			# 获取当天0点的时间戳
			nowtimeStamp = int(nowtime - nowtime % 86400 - 28800)
			# 判断时间大小
			if (timeStamp < nowtimeStamp):
				print('出发日期不能小于当前时间')
			else:
				break

	# 返回正确的城市编号
	return setOutTime

def checkTimeFormat(setOutTime):
	# 判断日期格式
	date_text = re.search(r"(\d{4}-\d{2}-\d{2})",setOutTime)
	# 判断时间格式是否正确
	try:

		if date_text == None:
			return False

		date_text = date_text.group(0)

		if date_text != datetime.strptime(date_text, "%Y-%m-%d").strftime('%Y-%m-%d'):
			return False
		else:
			return True

	except ValueError:
		return False



# 出发地
from_station = cityStation(1)
# 目的地
to_station = cityStation(2)
# 出发时间
setOutTime = timeStation()
# 实例化类
grabTicket = GrabTicketOperation.GrabTicket(from_station, to_station, setOutTime)
# 输入数据
grabTicket.callQueryTrains()

  

执行程序

接下来我们执行调用类文件,来看看我们的结果:

【Python大神教你 】实现12306余票监控

这里边显示我们的邮件已经发送成功,接下来我们来看看我们的邮件:

【Python大神教你 】实现12306余票监控

我们再来对照一下12306的数据,看是否一致:

【Python大神教你 】实现12306余票监控

这样,我们就能简单的实现12306的余票监控了。