12306购票信息爬虫
本文使用python3实现QQ邮箱爬虫和Email解析,会将设计思路和核心代码分享在此处,欢迎大家多多评论交流,感谢?
一、问题背景
近一年多来,因个人原因,坐火车往返跑了很多地方,也没有记录什么时间去了哪里,一共跑了多少次,频率怎么样,一共花了多少大洋…
我想知道具体的数据,怎么办?
那就爬下来呗?
二、预案探索
怎么爬?
12306网站有查询购票记录的功能,但最多只保存了最近一段时间的,一年多的记录肯定没有。
但每次购票记录,12306都就发送邮件到账号绑定的邮箱。
翻了下我的QQ邮箱,嗯,确实都在。
那现在的问题变成了:QQ邮箱内容爬虫
三、三个方案
1、直接抓包,通过接口直接爬取
2、通过Selenium 爬取页面内容
3、通过python 的POP3包爬取
经过前期探索,第1和第2两个方案,都需要搞清楚QQ邮箱的cookie和一大堆接口参数,预计花费较大时间,弃用。
直接选用第3种方案。
四、QQ邮箱爬虫
1、背景资料
首先要了解邮件格式,详情参考:
邮件格式详解
MIME邮件格式分析及信息提取
SMTP电子邮件格式及源码解析
2、设计思路
1、首先获取邮件
2、解析邮件内容
3、设计数据结构
4、解析关键字段:
邮件发送者send_email、
邮件类型支付/改签/退票type_email、
购票时间date_buy_ticket、
订单号码id_buy_ticket、
姓名name_ticket、
发车日期date_drives、
发车时间time_drive、
出发地址start_city、
目的地址aim_city、
车次number_train、
座号number_seat、
座位类型type_seat、
票价price_ticket、
退票费fee_refund_ticket、
应退票款price_refund_ticket
5、封装关键字解析方法
6、循环遍历所有邮件,只解析12306发送的邮件,如果邮件发送者send_email不是12306,直接跳过
7、打印所有结果
8、将结果数据存储到数据库
五、代码实现过程
1、通过POP3获取邮件
1.1、下面代码是通过POP3获取邮件的示例
import poplib
from POP.utils import get_send_email, get_html_content, get_type_email, get_init_content
from POP.xpath import get_content, analyze_info_ticket
# 邮件地址,密码和POP3服务器地址
email = "66668888@qq.com"
password = "asdfjkhdgjqlkajdgkl"
pop3_server = "pop.qq.com"
# 连接到POP3服务器
server = poplib.POP3_SSL(pop3_server)
# 可以打开或关闭调试信息
server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字
print(server.getwelcome().decode('utf-8'))
# 身份认证
server.user(email)
server.pass_(password)
# stat()返回邮件数量和占用空间
print('Messages: %s. Size: %s' % server.stat())
# list()返回邮件的编号
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82953', b'2 8746', ...]
print(mails)
# 邮件指针编号,索引从1开始
index = len(mails)
# 关闭连接
server.quit()
上述中的password
是QQ邮箱的授权码
1.2、获取初始邮件内容
# 获取初始邮件内容
def get_init_content(num, s):
resp, lines, octets = s.retr(num)
init_content = b'\r\n'.join(lines).decode('utf-8')
msg = Parser().parsestr(init_content)
return msg
1.3、获取初始的html内容
# 获取初始的html内容
def get_html_content(msg):
content = ''
if (msg.is_multipart()):
parts = msg.get_payload()
for n, part in enumerate(parts):
msg = part
content_type = msg.get_content_type()
if content_type == 'text/plain' or content_type == 'text/html':
content = msg.get_payload(decode=True)
charset = guess_charset(msg)
if charset:
content = content.decode(charset)
return content
2、关键方法工具类utils
下面是utils.py的所有关键方法
# coding=utf-8
from email.header import decode_header
from email.parser import Parser
# 从email header中获取信息v
# 比如发件人地址value_from_header: (msg, 'From')
# 如果判断是不是来自与12306的邮件,就可以 value.find('12306'), 如果是返回1,否则返回-1
def value_from_header(msg, v):
value = msg.get(v)
return value
# 获取邮件发送者
def get_send_email(msg):
send_email = value_from_header(msg, 'From')
return send_email
# 获取邮件类型
# 从header中获取邮件主题Subject, 用以获取此邮件类型:支付/退票/改签
def get_type_email(msg):
s = value_from_header(msg, 'Subject')
# 12306 Subject:"网上购票系统--用户支付通知"
# 截取"用户"后面的两个字符:支付/改签/退票
type_email = decode_str(s)[10:12]
return type_email
# 获取初始邮件内容
def get_init_content(num, s):
resp, lines, octets = s.retr(num)
init_content = b'\r\n'.join(lines).decode('utf-8')
msg = Parser().parsestr(init_content)
return msg
# 获取初始的html内容
def get_html_content(msg):
content = ''
if (msg.is_multipart()):
parts = msg.get_payload()
for n, part in enumerate(parts):
msg = part
content_type = msg.get_content_type()
if content_type == 'text/plain' or content_type == 'text/html':
content = msg.get_payload(decode=True)
charset = guess_charset(msg)
if charset:
content = content.decode(charset)
return content
# 邮件中的subject或者Email中包含的名字都是经过编码后的str,要正常显示,必须decode
def decode_str(s):
value, charset = decode_header(s)[0]
if charset:
value = value.decode(charset)
return value
# 获取邮件中的编码格式charset的值
def guess_charset(msg):
charset = msg.get_charset()
if charset is None:
content_type = msg.get('Content-Type', '').lower()
pos = content_type.find('charset=')
if pos >= 0:
charset = content_type[pos + 8:].strip()
return charset
3、邮件内容解析
3.1、解析对象示例
最新的12306邮件截图
上述页面的HTML如下,要从如下HTML中获取到对应的关键字
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>12306通知邮件</title>
<meta name="description" content="">