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="">
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值