はじめに
以前開発したEMLファイル変換ツールで、HTMLメールを処理すると大きな問題が発生していました。HTMLタグがそのまま出力されてしまい、非常に読みにくい状態になっていたのです。
今回はこの問題を解決するため、HTMLメールを見やすいMarkdown形式に変換する機能を追加しました。同時に、ファイル名にメールの受信日時を追加する改善も行いました。
HTMLメール処理の課題
従来の処理では、HTMLメールのコンテンツが次のような状態で出力されていました。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
</head>
<body style="height: 100% !important; width: 100% !important;">
<!-- 以下、大量のHTMLタグが続く -->
Code language: HTML, XML (xml)
これではメールの内容が全く読み取れません。HTMLタグを単純に除去するだけでは、構造化された情報が失われてしまいます。
HTML→Markdown変換機能の実装
html2textライブラリの導入
HTMLをMarkdownに変換するため、html2textライブラリを採用しました。このライブラリは、HTMLの構造を保持しながらMarkdown形式に変換できる優れたツールです。
まず、依存関係を追加します。
# setup.pyに追加
install_requires=[
'tkinterdnd2>=0.3.0',
'html2text>=2020.1.16', # 新規追加
],
Code language: PHP (php)
本文抽出機能の改良
従来のextract_body()メソッドを拡張し、HTMLとプレーンテキストを適切に分離処理するよう改良しました。
def extract_body(self, msg):
"""メールの本文を抽出する"""
body = ""
html_body = ""
text_body = ""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
try:
text_body = payload.decode(charset)
except:
text_body = payload.decode('utf-8', errors='ignore')
elif content_type == "text/html":
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
try:
html_body = payload.decode(charset)
except:
html_body = payload.decode('utf-8', errors='ignore')
# HTMLがある場合はMarkdownに変換、なければプレーンテキストを使用
if html_body:
body = self.convert_html_to_markdown(html_body)
elif text_body:
body = text_body
else:
body = "(本文なし)"
return body.strip()
Code language: PHP (php)
この改良により、HTMLコンテンツとプレーンテキストを明確に分離し、適切な処理を選択できるようになりました。
Markdown変換処理の実装
HTMLからMarkdownへの変換処理を担当する新しいメソッドを追加しました。
def convert_html_to_markdown(self, html_content):
"""HTMLをMarkdownに変換する"""
try:
# html2textの設定
h = html2text.HTML2Text()
h.ignore_links = False # リンクを保持
h.ignore_images = False # 画像を保持
h.body_width = 0 # 行の折り返しを無効化
h.protect_links = True # リンクを保護
h.wrap_links = False # リンクの折り返しを無効化
# HTMLをMarkdownに変換
markdown_content = h.handle(html_content)
# 余分な改行を整理
markdown_content = self.clean_markdown(markdown_content)
return markdown_content
except Exception as e:
self.log(f"HTML変換エラー: {str(e)}")
# エラーの場合は従来のHTMLタグ除去方式にフォールバック
import re
cleaned = re.sub(r'<[^>]+>', '', html_content)
return cleaned.replace(' ', ' ').replace('<', '<').replace('>', '>')
Code language: PHP (php)
このメソッドでは、html2textライブラリを細かく設定しています。リンクや画像を保持し、行の折り返しを無効化することで、元のHTMLの構造を可能な限り維持します。
エラーが発生した場合は、従来の単純なHTMLタグ除去方式にフォールバックする仕組みも実装しました。これにより、予期しないHTML形式でも処理が継続できます。
Markdown内容の整理
変換されたMarkdownの品質を向上させるため、整理処理も追加しました。
def clean_markdown(self, markdown_content):
"""Markdownの内容を整理する"""
# 連続する改行を最大2つまでに制限
import re
markdown_content = re.sub(r'\n{3,}', '\n\n', markdown_content)
# 先頭と末尾の余分な空白を削除
markdown_content = markdown_content.strip()
return markdown_content
Code language: PHP (php)
この処理により、見栄えの良いMarkdown出力が得られます。
ファイル名への日時追加機能
メール受信日時の抽出
ファイル名にメールの受信日時を追加することで、時系列での管理を容易にしました。メールヘッダーから日時情報を抽出し、取得できない場合はファイルの作成日時を使用します。
def extract_email_datetime(self, msg, eml_file_path):
"""メールの受信日時を抽出する(不明な場合はファイル作成日時を使用)"""
try:
# メールヘッダーから日時を取得
date_header = msg.get('Date')
if date_header:
# RFC2822形式の日時をパース
from email.utils import parsedate_tz, mktime_tz
parsed_date = parsedate_tz(date_header)
if parsed_date:
timestamp = mktime_tz(parsed_date)
return datetime.datetime.fromtimestamp(timestamp)
except Exception as e:
self.log(f"デバッグ: 日時解析エラー = {str(e)}")
# メールヘッダーから取得できない場合は、ファイルの作成日時を使用
try:
file_timestamp = os.path.getctime(eml_file_path)
return datetime.datetime.fromtimestamp(file_timestamp)
except:
# それでも失敗した場合は現在時刻を使用
return datetime.datetime.now()
Code language: PHP (php)
RFC2822形式の日時解析には、Pythonの標準ライブラリであるemail.utilsを使用しました。この方式により、様々な形式の日時文字列を正確に処理できます。
ファイル名形式の改善
日時情報をファイル名の先頭に配置し、時系列での並び替えを自然に行えるようにしました。
def format_datetime_for_filename(self, dt):
"""ファイル名用の日時文字列を生成"""
return dt.strftime("%Y-%m-%d-%H%M")
Code language: PHP (php)
この形式により、ファイルは自動的に時系列順に並びます。従来の連番も不要になりました。
出力ファイル名の生成
# メール日時を取得
email_datetime = self.extract_email_datetime(msg, eml_file)
datetime_prefix = self.format_datetime_for_filename(email_datetime)
# 出力ファイル名を生成
safe_subject = self.make_safe_filename(subject)
output_filename = f"{datetime_prefix}_{safe_subject}.txt"
Code language: PHP (php)
最終的なファイル名は「2025-05-20-0017_ミーティング要約.txt」のような形式になります。
重複ファイル処理とフォルダ名の改善
重複ファイルのスキップ機能
同じファイルが既に存在する場合は、自動的にスキップする機能を追加しました。
# 同じファイルが既に存在する場合はスキップ
if os.path.exists(output_path):
self.log(f" ⚠ スキップ: {output_filename} (既に存在)")
skip_count += 1
continue
Code language: PHP (php)
これにより、重複変換を避けて効率的な処理が可能になりました。
フォルダ名の簡素化
出力フォルダ名も改善し、日付のみを含むシンプルな形式に変更しました。
# 従来: 変換済みメール_20250524_143025
# 改善後: 変換済みメール_20250524
timestamp = datetime.datetime.now().strftime("%Y%m%d")
output_dir = os.path.join(first_file_dir, f"変換済みメール_{timestamp}")
Code language: PHP (php)
処理結果の詳細レポート
変換処理の完了時に、成功・スキップ・失敗の件数を分けて表示するようにしました。
# 完了メッセージ
self.log(f"\n変換完了!")
self.log(f"成功: {success_count}個")
self.log(f"スキップ: {skip_count}個")
self.log(f"失敗: {len(self.selected_files) - success_count - skip_count}個")
Code language: PHP (php)
ビルド設定の更新
py2appでのビルド時にhtml2textライブラリが正しく含まれるよう、setup.pyの設定も更新しました。
'includes': [
# 既存の設定に加えて
'html2text',
'html2text.config',
# その他の依存関係...
],
Code language: PHP (php)
実装結果
これらの改善により、HTMLメールが次のような見やすいMarkdown形式で出力されるようになりました。
**重要なお知らせ: 新システム導入について**
田中 様、いつもお世話になっております。
来月より新しい業務システムを導入いたします。主な変更点は以下の通りです:
- ログイン方法の変更
- 新機能の追加
- セキュリティの強化
詳細については添付の資料をご確認ください。
ご不明な点がございましたら、お気軽にお問い合わせください。
システム管理部
TEL: 03-1234-5678
Email: [support@example.com](mailto:support@example.com)
Code language: CSS (css)
HTMLタグは完全に除去され、リンクやテキスト構造は適切に保持されています。ファイル名も「2025-05-20-1430_新システム導入について.txt」となり、時系列での管理が容易になりました。
まとめ
今回の改善により、EMLファイル変換ツールはHTMLメールを適切に処理できるようになりました。html2textライブラリによるMarkdown変換、メール受信日時を活用したファイル名改善、重複ファイルのスキップ機能を実装することで、実用性が大幅に向上しています。
- html2text · PyPI – HTMLをMarkdownに変換するPythonライブラリの公式パッケージページ
- html2text GitHub Repository – html2textライブラリの公式GitHubリポジトリと使用方法の詳細
- email.utils: Miscellaneous utilities — Python 3.13.3 documentation – RFC2822形式の日時解析に使用したPython標準ライブラリの公式ドキュメント
- email.header: Internationalized headers — Python 3.13.3 documentation – MIMEヘッダーのデコードに関するPython標準ライブラリの公式ドキュメント
- py2app – Create standalone Mac OS X applications with Python – macOSアプリケーション作成ツールpy2appの公式ドキュメント
- tkinterdnd2 · PyPI – ドラッグ&ドロップ機能を実現するtkinterdnd2ライブラリの公式パッケージページ
- email.message: Representing an email message — Python 3.13.3 documentation – EMLファイル処理に使用したPython標準ライブラリの公式ドキュメント
- html2text Usage Documentation – html2textライブラリの詳細な使用方法とオプション設定の説明
- py2app Tutorial – py2appを使ったmacOSアプリケーション作成の詳細なチュートリアル