文章目录

  • 1. 项目背景
  • 2. poplib模块
  • 3. 邮箱登录
  • 4. 获取邮件内容
  • 1. 获取邮件基本信息
  • 2. 获取邮件中的附件
  • 5. 解压zip/rar压缩包
  • 1. 打开zip/rar压缩包
  • 2. 获取压缩包中文件


 

1. 项目背景

目前在做的一个小项目,需要到登录到邮箱获取压缩包,解压压缩包获取文件,并从文件中抽取出有效数据入库,后续会做一些二次加工,最后用到业务风控中。

 

2. poplib模块

Python内建了poplib模块来实现登录邮箱,这个模块提供了一个类POP3_SSL,它支持使用SSL(Secure Socket Layer,安全套阶层,在OSI模型处于会话层)作为底层协议连接POP3服务器。

 

3. 邮箱登录

邮箱登录代码实现:

import poplib

# 通过主机名、端口生成POP3对象
pop = poplib.POP3_SSL(host="", port="")
try:
    # 通过用户名、密码登录邮箱
    pop.user("")
    pop.pass_("")
except poplib.error_proto as e:
    logger.error("Login failed: " + e)
else:
    parse(pop, sys.argv)
finally:
    # 退出邮箱
    pop.quit()

注意,最后程序不管是正常结束还是非正常结束都一定要退出邮箱。

 

4. 获取邮件内容

获取指定邮件有多种方式,可以通过邮件发件人、邮件发件邮箱地址、邮件主题,还可以用邮件索引,使用邮件索引速度最快,这种获取方式就跟在散列表中根据key获取对应value很相似。

1. 获取邮件基本信息

登录邮箱之后,可以获取很多与邮件相关的基本信息,比如邮箱邮件列表、邮件主题、邮件内容、邮件发件人及发件地址、邮件接收时间等等

代码实现:

from email.parser import Parser

def get_mail_list(pop):
    # 查找附件, 并返回下载文件路径
    emails = pop.list()[1]  # 邮箱邮件列表
    email_num = len(emails)  # 邮件数量

    for i in [10, 20, 30]:
        lines = pop.retr(i)[1]  # 获取指定的邮件
        line_bytes = b'\r\n'.join(lines)  # 换行符分割邮件信息
        msg_content = line_bytes.decode('utf-8')  # 解码
        msg = Parser().parsestr(msg_content)  # 每封邮件信息

        # 发件人和邮箱地址
        header, address = parseaddr(msg.get("From", "")) 
        name, address = decode_str(header), decode_str(address)
        # 邮件主题
        email_subject = decode_str(msg.get('Subject', ""))

        # 邮件接收时间
        date_tuple = parsedate_tz(msg.get("Date", ""))
        date_formatted = datetime.fromtimestamp(mktime_tz(date_tuple)).strftime("%Y-%m-%d %H:%M:%S")
        
        compressed_files = list()
        for compressed_file in get_attachment(msg, compressed_files):
            yield compressed_file

2. 获取邮件中的附件

获取邮件附件中的zip或rar压缩包,注意:这里要考虑到一封邮件中有多个压缩包的情况。

代码实现:

def get_attachment(msg, ompressed_files: list):
    if msg.is_multipart() is True:
        # 分层信息
        parts = msg.get_payload()
        for n, part in enumerate(parts):
            res = get_attachment(part, compressed_files)
        return res
    else:
        content_type = msg.get_content_type()
        if content_type == 'application/octet-stream':
            for subpart in msg.walk():  # 遍历消息树(深度优先搜索算法), 获取每个子节点
                file_name_encoder = subpart.get_filename()
                file_name = decode_str(file_name_encoder)  # 解码获取文件名
                # 判断是否是zip或rar格式文件
                if file_name.split(".")[-1] not in ['zip', "rar"]:
                    continue

                data = msg.get_payload(decode=True)  # 附件二进制
                file_data = base64.b64encode(data).decode()  # base64编码
                compressed_files.append({"file_data": file_data, "name": file_name})  # 保存到列表

    return compressed_files


def decode_str(s):
    """
    解码
    :param s:
    :return:
    """
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value

 

5. 解压zip/rar压缩包

在获取到邮件附件的二进制内容之后,就可以使用zipfile模块和rarfile模块解解压zip和rar压缩包了。

1. 打开zip/rar压缩包

处理步骤4中获取的邮件附件,代码如下:

import zipfile
import rarfile
from io import BytesIO

def parse(pop):
    for data in get_mail_list(pop):
        try:
            if data['name'].endswith(".zip"):  # zip格式的压缩文件
                zip_obj = zipfile.ZipFile(BytesIO(base64.b64decode(data['file_data'].encode())))
                for record in get_files_in_zip(zip_obj, item):
                    pass  # 数据处理
            elif data['name'].endswith(".rar"):  # rar格式的压缩文件
                rar_obj = rarfile.RarFile(BytesIO(base64.b64decode(data['file_data'].encode())))
                for record in get_files_in_rar(rar_obj, item):
					pass  # 数据处理
        except Exception as e:
            logger.error(traceback.format_exc())

2. 获取压缩包中文件

打开压缩包之后,便可迭代获取压缩包中文件。不过这里要注意的是,zip/rar压缩中可能会嵌套压缩包,比如zip压缩包中嵌套zip压缩包或rar压缩包,那么就需要使用递归进行解压缩。

代码实现:

import zipfile
import rarfile
from io import BytesIO

def get_files_in_zip(zip_obj):
    # 获取压缩包中文件列表, 同时过滤一些空目录
    files = [file for file in zip_obj.namelist() if re.search("\\..*$", file)]

    # 遍历每个压缩包中的所有文件
    for file in files:
        # 压缩包中嵌套压缩包
        if file.endswith("zip"):
            inner_zip_obj = zipfile.ZipFile(BytesIO(base64.b64decode(base64.b64encode(zip_obj.read(file)).decode().encode())))
            yield from get_files_in_zip(inner_zip_obj)
            continue
        elif file.endswith("rar"):
            inner_rar_obj = rarfile.RarFile(BytesIO(base64.b64decode(base64.b64encode(zip_obj.read(file)).decode().encode())))
            yield from get_files_in_rar(inner_rar_obj)
            continue
            
		pass  # 压缩中文件处理

def get_files_in_rar(rar_obj):
    # 获取压缩包中文件列表, 同时过滤一些空目录
    files = [file for file in rar_obj.namelist() if re.search("\\..*$", file)]

    # 解析每个文件内容
    for file in files:
        # 压缩包中嵌套压缩包
        if file.endswith("rar"):
            inner_rar_obj = rarfile.RarFile(BytesIO(base64.b64decode(base64.b64encode(rar_obj.read(file)).decode().encode())))
            yield from get_files_in_rar(inner_rar_obj)
            continue
        elif file.endswith("zip"):
            inner_zip_obj = zipfile.ZipFile(BytesIO(base64.b64decode(base64.b64encode(rar_obj.read(file)).decode().encode())))
            yield from get_files_in_zip(inner_zip_obj)
            continue
            
		pass  # 压缩中文件处理

解压获取压缩包中文件之后,就可以从文件(这里主要是pdf、html、excel、csv等格式的文件)中提取数据了。