X(旧Twitter)のアーカイブデータからDMをテキストログに変換する

X DMをWhatsApp風テキストに変換 データ取得 Xアーカイブ direct-messages.js Python変換 JSON解析 時刻変換・整理 テキスト生成 WhatsApp形式 読みやすい会話 解決する課題 • XのDMは複雑なJSON形式 • 時刻がUTC形式で読みにくい • アーカイブファイルは30日制限 • 会話の検索・保存が困難 使用技術 • Python JSON処理 • pytz(時刻変換) • UTF-8エンコーディング • 正規表現処理 得られる効果 • オフライン閲覧可能 • 全文検索機能 • 時系列整理済み • 会話のバックアップ

きっかけは保存したい会話があったから

X(旧Twitter)のダイレクトメッセージには、大切な会話が残っています。グループでの企画会議や、友人との長いやりとり。これらを読みやすい形で保存したい場面があります。

X公式のアーカイブ機能を使えば、すべてのデータをダウンロードできます。しかし、ダウンロードしたファイルはJSON形式で、そのままでは読みにくいのが現状です。

この記事では、XのDMデータをWhatsAppのような読みやすいテキスト形式に変換する方法を紹介します。

Xのアーカイブ機能でデータを取得する

まずは、X公式のアーカイブ機能を使ってDMデータを取得します。

設定画面から「データのアーカイブをダウンロード」を選択し、パスワードを入力して確認します。数時間から1日程度で準備が完了し、メール通知が届きます。

ダウンロードしたZIPファイルを解凍すると、「direct-messages.js」というファイルが含まれています。これがDMの履歴データです。

ただし、このファイルはJavaScript形式で記述されており、人間が読むには適していません。JSON(JavaScript Object Notation)というデータ形式で構造化されていますが、複雑な階層構造になっています。

データの構造を理解する

direct-messages.jsファイルの中身は、次のような構造になっています。

window.YTD.direct_messages.part0 = [
  {
    "dmConversation": {
      "conversationId": "会話のID",
      "messages": [
        {
          "messageCreate": {
            "senderId": "送信者のID",
            "text": "メッセージの内容",
            "createdAt": "2025-05-19T05:49:36.000Z"
          }
        }
      ]
    }
  }
]
Code language: JavaScript (javascript)

この構造を理解すると、必要な情報は次の3つであることがわかります。

  • 送信者のID(senderId)
  • メッセージの内容(text)
  • 送信日時(createdAt)

Pythonスクリプトで変換する

Pythonを使って、この複雑なデータ構造を読みやすいテキストに変換します。

まず、必要なライブラリをインストールします。

pip install pytz

pytzは、時刻を日本時間に変換するために使用します。XのデータはUTC(世界標準時)で記録されているためです。

変換スクリプトの核となる部分は、JSONデータの読み込みです。JavaScript形式のファイルから、JSON部分だけを抽出する必要があります。

# JSファイルを読み込み
with codecs.open(js_file_path, 'r', 'utf-8', 'ignore') as f:
    content = f.read()

# JSONデータを抽出
json_start = content.find('[')
json_data = content[json_start:]
dm_data = json.loads(json_data)
Code language: PHP (php)

この処理により、ファイルの先頭にある「window.YTD.direct_messages.part0 = 」の部分を取り除き、純粋なJSONデータだけを取得できます。

時刻の変換処理

XのタイムスタンプはISO 8601形式で記録されています。これを日本時間に変換し、読みやすい形式にします。

# ISO形式の日時をパース
dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))

# JSTに変換
jst = pytz.timezone('Asia/Tokyo')
dt_jst = dt.astimezone(jst)
timestamp_str = dt_jst.strftime('%Y/%m/%d %H:%M:%S')
Code language: PHP (php)

この変換により、「2025-05-19T05:49:36.000Z」が「2025/05/19 14:49:36」のような日本時間の表記になります。

会話の選択機能

複数のDM会話がある場合、特定の会話だけを抽出したい場合があります。スクリプトでは、利用可能な会話を一覧表示し、ユーザーが選択できるようにしています。

# 会話リストを表示
for i, conversation in enumerate(dm_data):
    conv_id = conversation['dmConversation']['conversationId']
    messages = conversation['dmConversation']['messages']
    print(f"{i+1}. 会話ID: {conv_id} ({len(messages)}メッセージ)")
Code language: PHP (php)

これにより、どの会話を変換するかを明確に選択できます。

出力形式の統一

最終的な出力は、WhatsAppのエクスポート形式に合わせています。

[2025/05/19 14:49:36] ユーザーID: メッセージの内容
Code language: CSS (css)

この形式は、日時、送信者、メッセージが一目で分かる構造になっています。

メディア添付がある場合は、メッセージの末尾に「[メディア添付: X件]」という表記を追加します。実際の画像や動画ファイルは別途保存されているためです。

完成したスクリプト

以下が、実際に使用できる完全なスクリプトです。

# -*- coding: utf-8 -*-
"""
X(Twitter)のdirect-messages.jsをWhatsApp風chat.txt形式に変換するスクリプト
Author: chiilabo
URL: chiilabo.jp
"""

import json
import codecs
from datetime import datetime
import pytz

def convert_x_dm_to_chat(js_file_path, output_file="x_chat_converted.txt", target_conversation_id=None):
    """
    XのDMデータをchat.txt形式に変換
    
    Args:
        js_file_path: direct-messages.jsのパス
        output_file: 出力ファイル名
        target_conversation_id: 特定の会話IDを指定(Noneなら全会話)
    """
    
    try:
        # JSファイルを読み込み
        with codecs.open(js_file_path, 'r', 'utf-8', 'ignore') as f:
            content = f.read()
        
        # JSONデータを抽出
        json_start = content.find('[')
        if json_start == -1:
            print("JSONデータが見つかりません")
            return
        
        json_data = content[json_start:]
        dm_data = json.loads(json_data)
        
        print(f"読み込み完了: {len(dm_data)}個の会話を発見")
        
        # 会話リストを表示
        print("\n利用可能な会話:")
        for i, conversation in enumerate(dm_data):
            conv_id = conversation['dmConversation']['conversationId']
            messages = conversation['dmConversation']['messages']
            print(f"{i+1}. 会話ID: {conv_id} ({len(messages)}メッセージ)")
        
        # 特定の会話を処理するか全体を処理するか
        if target_conversation_id:
            conversations_to_process = [conv for conv in dm_data 
                                     if conv['dmConversation']['conversationId'] == target_conversation_id]
            if not conversations_to_process:
                print(f"会話ID {target_conversation_id} が見つかりません")
                return
        else:
            # 会話を選択
            print("\n処理したい会話番号を入力してください(全て処理する場合は0):")
            choice = input("番号: ").strip()
            
            if choice == "0":
                conversations_to_process = dm_data
            else:
                try:
                    index = int(choice) - 1
                    if 0 <= index < len(dm_data):
                        conversations_to_process = [dm_data[index]]
                    else:
                        print("無効な番号です")
                        return
                except ValueError:
                    print("数字を入力してください")
                    return
        
        # メッセージを抽出・変換
        all_messages = []
        
        for conversation in conversations_to_process:
            conv_id = conversation['dmConversation']['conversationId']
            messages = conversation['dmConversation']['messages']
            
            for message in messages:
                msg_create = message['messageCreate']
                
                # 送信者ID
                sender_id = msg_create['senderId']
                
                # メッセージテキスト
                text = msg_create.get('text', '')
                
                # 時刻(ISO形式からJSTに変換)
                created_at = msg_create['createdAt']
                dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
                jst = pytz.timezone('Asia/Tokyo')
                dt_jst = dt.astimezone(jst)
                timestamp_str = dt_jst.strftime('%Y/%m/%d %H:%M:%S')
                
                # メディア添付の確認
                if 'mediaUrls' in msg_create and msg_create['mediaUrls']:
                    text += f" [メディア添付: {len(msg_create['mediaUrls'])}件]"
                
                all_messages.append({
                    'timestamp': dt_jst,
                    'timestamp_str': timestamp_str,
                    'sender_id': sender_id,
                    'text': text,
                    'conversation_id': conv_id
                })
        
        # 時系列順にソート
        all_messages.sort(key=lambda x: x['timestamp'])
        
        # chat.txt形式で出力
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(f"X(Twitter) DM変換結果\n")
            f.write(f"変換日時: {datetime.now().strftime('%Y/%m/%d %H:%M:%S')}\n")
            f.write(f"総メッセージ数: {len(all_messages)}\n")
            f.write("=" * 50 + "\n")
            
            for msg in all_messages:
                f.write(f"[{msg['timestamp_str']}] {msg['sender_id']}: {msg['text']}\n")
        
        print(f"\n変換完了: {len(all_messages)}件のメッセージを {output_file} に保存しました")
        
        # 統計情報
        senders = {}
        for msg in all_messages:
            sender = msg['sender_id']
            senders[sender] = senders.get(sender, 0) + 1
        
        print("\n参加者別メッセージ数:")
        for sender, count in sorted(senders.items(), key=lambda x: x[1], reverse=True):
            print(f"  {sender}: {count}件")
            
    except Exception as e:
        print(f"エラーが発生しました: {e}")
        import traceback
        traceback.print_exc()

# 使用例
if __name__ == "__main__":
    print("X DM → Chat形式変換ツール")
    print("=" * 40)
    
    js_file = input("direct-messages.jsのパスを入力してください: ").strip()
    output_file = input("出力ファイル名を入力してください(デフォルト: x_chat.txt): ").strip()
    
    if not output_file:
        output_file = "x_chat.txt"
    
    convert_x_dm_to_chat(js_file, output_file)
Code language: PHP (php)

実際の使用手順

スクリプトを使用する手順は次の通りです。

  1. PythonのIDLEまたはコマンドラインでスクリプトを実行します
  2. direct-messages.jsファイルのパスを入力します
  3. 利用可能な会話一覧が表示されるので、変換したい会話番号を選択します
  4. 出力ファイル名を指定します(省略可能)
  5. 変換が完了すると、指定したファイルに結果が保存されます

変換後のファイルは、テキストエディタで開けば、時系列順に整理されたメッセージが確認できます。

注意点とメリット

このスクリプトを使用する際の注意点があります。

アーカイブデータの再取得は30日間隔を空ける必要があります。また、削除されたメッセージはアーカイブに含まれません。

一方で、メリットも多くあります。オフライン環境でメッセージを確認でき、全文検索も可能です。バックアップとしても活用できます。

まとめ

XのDMアーカイブデータをPythonで処理することで、読みやすいテキスト形式に変換できます。JSON構造の理解、時刻変換、文字エンコーディング処理が主要な技術要素となります。変換されたテキストファイルは、会話の保存や検索に活用できる実用的な形式になります。