iMessage로 계좌 내역 문자 파싱하기 (기록용)

by Dongeun Paeng
Jul 18, 2025 · 만 35세

우선 Rust로 된 패키지(링크)를 찾아 설치 후 아래 스크립트 실행.


imessage-exporter \  --format txt \  --export-path ./imessage_output  --start-date 2025-01-01 --end-date 2025-07-18


이렇게 하면 폴더 안에 기간 내 모든 문자가 txt로 쌓이는데, 그 중 내가 원하는 은행 문자 선택.


이후 아래 파이썬 스크립트 실행.

import reimport pandas as pddef parse_transactions(file_path: str) -> pd.DataFrame:    """    지정한 텍스트 파일을 읽어들여 거래 내역을 파싱하고    DataFrame으로 반환합니다.    Parameters:    ----------    file_path : str        파싱할 거래 내역이 담긴 텍스트 파일 경로    Returns:    -------    pd.DataFrame        컬럼: ['날짜', '출금액', '입금액', '잔액', '내역', '계좌끝3자리']    """    # 파일 읽기    with open(file_path, 'r', encoding='utf-8') as f:        text = f.read()    # 거래 블록 분리    blocks = [b.strip() for b in text.strip().split("\n\n") if b.strip()]    rows = []    for block in blocks:        lines = block.splitlines()        if any('SMS통지수수료' in line for line in lines):            # 날짜: 첫 줄의 영어 형식 날짜            try:                date_str = re.sub(r"\s*\(.*\)$", "", lines[0]).strip()                date = pd.to_datetime(date_str)            except Exception:                date = None            # 출금액: SMS통지수수료 금액            m_amt = re.search(r"SMS통지수수료\s*([\d,]+)원", block)            withdrawal = int(m_amt.group(1).replace(',', '')) if m_amt else 0            deposit = 0            # 잔액: 콜론 유무 상관없이 파싱            m_bal = re.search(r"잔액[:]?([\d,]+)원", block)            balance = int(m_bal.group(1).replace(',', '')) if m_bal else None            # 내역            desc = 'SMS통지수수료'            # 계좌번호 끝 3자리: SMS라인 다음 또는 잔액 뒤 다음 줄            # 일반적으로 lines[5]에 위치            acc_line = ''            for ln in lines:                m_acc = re.search(r"(\d{3})$", ln)                if m_acc and '***' in ln:                    acc_line = ln                    break            acc_last3 = m_acc.group(1) if (acc_line and (m_acc := re.search(r"(\d{3})$", acc_line))) else ''            rows.append({                '날짜': date,                '출금액': withdrawal,                '입금액': deposit,                '잔액': balance,                '내역': desc,                '계좌끝3자리': acc_last3            })            continue        # 날짜 (YYYY/MM/DD HH:MM)        date_line = next((l for l in lines if re.match(r"\d{4}/\d{2}/\d{2} \d{2}:\d{2}", l)), None)        date = pd.to_datetime(date_line) if date_line else None        # 출금/입금 금액 파싱 (robust)        trans_line = next((l for l in lines if l.startswith("출금") or l.startswith("입금")), None)        if trans_line:            parts = trans_line.split()            if len(parts) >= 2:                kind = parts[0]                amt_str = parts[1]                try:                    amt = int(amt_str.replace(',', '').replace('원', ''))                except ValueError:                    amt = 0            else:                kind = ''                amt = 0        else:            kind = ''            amt = 0        # 입금액/출금액 분리        withdrawal = amt if kind == '출금' else 0        deposit = amt if kind == '입금' else 0        # 잔액        bal_line = next((l for l in lines if l.startswith("잔액")), None)        if bal_line:            bal_parts = bal_line.split()            if len(bal_parts) >= 2:                try:                    balance = int(bal_parts[1].replace(',', '').replace('원', ''))                except ValueError:                    balance = None            else:                balance = None        else:            balance = None        # 내역 (잔액 다음 줄)        desc = ''        if bal_line and bal_line in lines:            idx = lines.index(bal_line)            if idx + 1 < len(lines):                desc = lines[idx + 1].strip()        else:            idx = -1        # 계좌번호 끝 3자리 (내역 다음 줄)        acc_last3 = ''        if idx >= 0 and idx + 2 < len(lines):            acc_line = lines[idx + 2]            m = re.search(r"(\d{3})$", acc_line)            if m:                acc_last3 = m.group(1)        rows.append({            '날짜': date,            '출금액': withdrawal,            '입금액': deposit,            '잔액': balance,            '내역': desc,            '계좌끝3자리': acc_last3        })    df = pd.DataFrame(rows)    # 형식에 맞지 않는 문자는 빈칸을 만들어내므로 dropna로 제거    df.dropna(how='any', inplace=True)    return dfif __name__ == "__main__":    import sys    # 파일 경로를 인수로 받되, 없으면 기본 파일명 사용    file_path = sys.argv[1] if len(sys.argv) > 1 else 'transactions.txt'    df = parse_transactions(file_path)    df.to_csv('transactions.csv', encoding='utf-8-sig', index=False)

NEXT POST

최근 ETL 작업하며 느낀 점

Aug 13, 2025 · 만 35세

다양한 스펙의 다양한 Open API로부터 데이터 받아올 수 있는 범용 프로그램을 만들고 있다. 더 보기

PREVIOUS POST

RSA 암호화 방식의 수학 원리

Apr 12, 2025 · 만 35세

이 블로그에 댓글란이 있다고 해보자. 비밀 댓글 구현이 번거로우니, 공개 댓글란에 나만 이해할 수 있는 암호 형식으로 댓글을 받기로 하자. 더 보기