首页
社区
课程
招聘
[原创].mht文件图片解析工具
发表于: 2020-5-25 09:54 8756

[原创].mht文件图片解析工具

obaby 活跃值
20
2020-5-25 09:54
8756

.mht文件图片解析工具

什么是mht文件:

MHTML文件又称为聚合HTML文档、Web档案或单一文件网页。单个文件网页可将网站的所有元素(包括文本和图形)都保存到单个文件中。这种封装使您可将整个网站发布为单个内嵌MIME (MIME:通过 Internet 连接传递多媒体资源的一列标准。MIME类型通知程序对象所包含的内容(如图形、声音或视频)的聚合HTML文档(MHTML)文件,或将整个网站作为一个电子邮件或附件发送。Internet Explorer 4.0及更高版本支持此格式。
百度百科链接

 

网上找了一下没有找到比较现成的好用的工具,找到一个mht-viewer 的windows下的查看工具,但是实际实用的时候发现啥都看不了,就是个文本编辑器?还是我打开的姿势不对?

 

mht-viewer

 

并且对于中文目录和文件名直接无法显示,我都不知道查看的是什么东西,就这个还尼玛有付费版本?

 

搜索了一下发现了几个python脚本,实际使用效果也一般。网上搜索了一下并没有找到相关的文件格式的说明

 

直接查看文件就可以发现文件格式并不是十分复杂,于是可以遍历来解析文件中的图片

 

已经保存的图片如下:

 

1

 

在文件中的存储结构如下:

 

2

 

虽然现在mht文件中的资源链接已经全部都挂了,但是所有的资源都是本地保存的,所以要还原对应的图片,只需要将对应的base64加密的字符串解密,然后写入文件即可

 

资源的存储方式则是通过 boundary=----------pMKI1vNl6U7UKeGzbfNTyN 进行分隔,在文件的第一行已经定义了边界分隔字符串,通过这个值即可将所有的资源全部分隔出来

  • 第一段

Content-Type: multipart/related; start=op.mhtml.1267442701515.fe60c16c115c15f9@169.254.195.209; boundary=----------pMKI1vNl6U7UKeGzbfNTyN
Content-Location: http://a.10xjw.com/feizhuliu/89905.html
Subject: =?utf-8?Q?=E8=B6=85=E7=BE=8E=E4=B8=9D=E6=8E=A7=E5=A7=90=E5=A6=B9=E8=8A=B1=E7=A7=92=E6=9D=80=E4=BD=A0=E6=B2=A1=E9=97=AE=E9=A2=98[26P]-=2037kxw.com=20-=20=E4=B8=AD=E5=9B=BD=E6=9C=80=E5=A4=A7=E7=9A=84=E8=89=B2=E6=83=85=E5=88=86=E4=BA=AB=E7=BD=91=E7=AB=99?=
MIME-Version: 1.0

 

第一段定义了文件类型 边界 原始url 原始网页标题以及 版本号

第二段则定义了html源码相关的内容,具体可以参考相关的源码

  • 后续段落

    ------------pMKI1vNl6U7UKeGzbfNTyN
    Content-Disposition: inline; filename=c.css
    Content-Type: text/css; charset=gbk; name=c.css
    Content-Location: http://a.10xjw.com/c.css
    Content-Transfer-Encoding: 8bit

后续段落则开始记录相关的资源包括css 图片 js等所有的资源信息。所以mht文件的好处是一个文件记录了所有的内容,并且即使原始网络资源已经无法访问也可以正常的浏览

 

而我这里关注的则只有图片信息, 图片信息结构如下:

------------pMKI1vNl6U7UKeGzbfNTyN
Content-Disposition: inline; filename=771d2ad986.jpg
Content-Type: image/jpeg; name=771d2ad986.jpg
Content-Location: http://himg2.huanqiu.com/attachment/091012/771d2ad986.jpg
Content-Transfer-Encoding: Base64

/9j/4AAQSkZJRgABAQEA8ADwAAD/2wBDAAkGBgYHBgkHBwkNCQcJDQ8LCQkLDxEO
Dg8ODhEUDxAQEBAPFBEUFRYVFBEaGhwcGholJCQkJSgoKCgoKCgoKCj/2wBDAQoJ
CQ4ODhgRERgZFBIUGR8eHh4eHyIfHx8fHyIkISAgICAhJCMkIiIiJCMmJiQkJiYo
KCgoKCgoKCgoKCgoKCj/wAARCALQAeADAREAAhEBAxEB/8QAHAAAAgIDAQEAAAAA
AAAAAAAABAUDBgECBwAI/8QAUhAAAQMCBAMEBwUFBQUFBwQDAQACAwQRBRIhMQYT

 

保存文件所需的所有的信息已经都存在了,包活文件名,文件类型,原始的路径以及图片base64编码

 

所以只要将对应的数据解密然后保存下载就一切都ok了

 

文件名可能存在过长拆分的问题:

------------uNdOxD6YsQZMV8KY8Zldv3
Content-Disposition: inline; filename0="y1pGpLTMzlEMSYejPYdz8DuYH_ttGFJ-9PPezzT7";
filename
1="XtK1BlVN9nlq92nDSZcjEGAGO_N9YAw5PtCWc-aX9QBcPpgcg.jpeg"
Content-Type: image/jpeg; name0="y1pGpLTMzlEMSYejPYdz8DuYH_ttGFJ-9PPezzT7";
name
1="XtK1BlVN9nlq92nDSZcjEGAGO_N9YAw5PtCWc-aX9QBcPpgcg.jpeg"
Content-Location: http://public.blu.livefilestore.com/y1phEb_sfR0Z7hVsySTRZ06sRwVSc7LI_whCIjV-xzTuqm1embz8wPPtC0eXr69ZaLGUC3xzk3Ex6ppUyPb2XG_eg/anri_big18.jpg
Content-Transfer-Encoding: Base64

 

我直接采用了简单粗暴的方式,如果文件名过长直接使用default.jpg进行命名。如果需要原始文件名可以将对应的filename 全部进行拼接即可。

# -*- coding: utf-8 -*-
"""
@author: obaby
@license: (C) Copyright 2013-2020, obaby@mars.
@contact: root@obaby.org.cn
@link: http://www.obaby.org.cn
        http://www.h4ck.org.cn
        http://www.findu.co
@file: baby_mht_image_extractor.py
@time: 2020/5/22 20:46
@desc:
"""

import base64
import getopt
import os
import quopri
import sys

from pyfiglet import Figlet

current_path = os.path.dirname(os.path.abspath(__file__))
dirname, filename = os.path.split(os.path.abspath(sys.argv[0]))
current_path = dirname
OUT_PATH = os.path.join(current_path, 'out')


def convert_mht_to_list(boundary, html_content):
    return str(html_content).split(boundary)


def get_boundary(html_content):
    return '--' + str(html_content).split(';')[-1].split('=')[-1]


def make_dir(floder_name):
    PATH = os.path.join(OUT_PATH, floder_name)
    if not os.path.exists(PATH):
        os.makedirs(PATH)
        os.chdir(PATH)
    return PATH


def save_image_file(image_content, path, file_name):
    try:
        file_path = os.path.join(path, file_name)
        make_dir(path)
        with open(file_path, 'wb') as f:
            f.write(image_content)
            print('[S] 保存图片成功')
        return file_path
    except Exception as e:
        # print(e)
        print('[S] 保存图片失败: ' + str(e))
        return None


def get_content_type(sub_content):
    content_type = 'Unknown'
    for l in sub_content:
        if 'Content-Type' in l:
            content_type = l.split(';')[0].split(':')[1]
            break
    return content_type


def get_content_encoding(sub_content):
    content_encoding = 'unknown'
    pass_count = 0
    for l in sub_content:
        if 'Content-Transfer-Encoding' in l:
            content_encoding = l.split(':')[1].replace(' ', '')
            break
        pass_count += 1
    return content_encoding, pass_count


def get_content_type_and_content(line, sub_path_name, index):
    line = str(line)
    sub_content = line.split('\n')
    if 'Content-Disposition' in line:
        try:
            file_name = sub_content[0].split(';')[1].split('=')[1]
            if 'filename*0' in sub_content[0]:
                file_name = 'default.jpg'
        except:
            file_name = 'default.jpg'

        content_type = get_content_type(sub_content)
        content_encoding, psc = get_content_encoding(sub_content)
        content = ''.join(sub_content[psc+1:])

        if 'image' in content_type:
            filename = str(index) + '_' + file_name
            print('[S] 正在保存图片文件:', filename)
            decoded_body = None
            if content_encoding.lower() == 'quoted-printable':
                decoded_body = quopri.decodestring(content)
            if content_encoding.lower() == 'base64':
                decoded_body = base64.b64decode(content)
            if decoded_body:
                save_image_file(decoded_body, sub_path_name, filename)
            else:
                print('[S] 图片解码失败,无法保存')
    return


def print_usage():
    print('*' * 100)
    # f = Figlet(font='slant')
    f = Figlet()
    print(f.renderText('obaby@mars'))
    print('mht image extractor by obaby')
    print('Verson: 0.5.22')
    print('baby_mht_image_extractor -f <input mht file> -o <output path> -p <input path>')
    print('Need Arguments:')
    print('\t -f <input mht file>')
    print('\t -o <output path> ')
    print('Option Arguments:')
    print('\t -p <input path>')
    print('Blog: http://www.h4ck.org.cn')
    print('*' * 100)


def save_mht_all_images(input_path):
    sub_path_name = os.path.join(OUT_PATH, os.path.basename(input_path).title())
    with open(input_path, 'r', ) as f:
        first_line = f.readline()
        body_content = f.read()
        boundary = get_boundary(first_line)
        content_list = convert_mht_to_list(boundary, html_content=body_content)
        index = 0
        for l in content_list:
            get_content_type_and_content(l, sub_path_name, index)
            index += 1


def main(argv):
    global OUT_PATH
    input_path = ''
    outputpath = ''
    input_file = ''
    try:
        opts, args = getopt.getopt(argv, "hf:o:p:", ["file=", "opath=", "ipath="])
    except getopt.GetoptError:
        print_usage()
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print_usage()
            sys.exit()
        elif opt in ("-f", "--file"):
            input_file = arg
        elif opt in ("-p", "--ipath"):
            input_path = arg
        elif opt in ("-o", "--opath"):
            outputpath = arg

    if input_file == '' and input_path == '':
        print_usage()
        sys.exit(2)

    if outputpath != '':
        OUT_PATH = outputpath

    print('*' * 100)
    print('[S] 开始任务......')
    print('[C] 输入文件:' + input_file)
    print('[C] 输入目录:' + input_path)
    print('[C] 输出目录:' + OUT_PATH)

    if os.path.isfile(input_file):
        save_mht_all_images(input_file)
        print('[D] 导出全部完成。')
        print('*' * 100)
    else:
        if os.path.isdir(input_path):
            for root, dirs, files in os.walk(input_path):
                for file in files:
                    print('[S] 开始处理文件:', file)
                    save_mht_all_images(os.path.join(root, file))
                    print('-' * 80)
            print('[D] 导出全部完成。')
            print('*' * 100)
        else:
            print_usage()


if __name__ == '__main__':
    main(sys.argv[1:])

github地址:
https://github.com/obaby/mht-image-extractor


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-5-25 10:23 被obaby编辑 ,原因: 更新图片链接地址
上传的附件:
收藏
免费 0
支持
分享
最新回复 (4)
雪    币: 97697
活跃值: (200839)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
2
support!
2020-5-25 10:02
0
雪    币: 14983
活跃值: (5285)
能力值: ( LV15,RANK:880 )
在线值:
发帖
回帖
粉丝
3
linhanshi support!
thanks
2020-5-25 10:20
0
雪    币: 7148
活跃值: (3736)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
asd
4
逹葢薾的旗帜
2020-5-25 13:36
0
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
支持。。
2020-5-26 12:01
0
游客
登录 | 注册 方可回帖
返回
//