우선 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
PREVIOUS POST
이 블로그에 댓글란이 있다고 해보자. 비밀 댓글 구현이 번거로우니, 공개 댓글란에 나만 이해할 수 있는 암호 형식으로 댓글을 받기로 하자. 더 보기