๐Ÿ›ก๏ธ Python์œผ๋กœ ์Šค๋งˆํŠธ Excel ๋ฐ์ดํ„ฐ ํด๋ฆฌ๋„ˆ ๊ตฌ์ถ• (๋‹จ๊ณ„๋ณ„)

๋ฐœํ–‰: (2026๋…„ 1์›” 17์ผ ์˜ค์ „ 10:00 GMT+9)
7 min read
์›๋ฌธ: Dev.to

Iโ€™m happy to translate the article for you, but I need the text youโ€™d like translated. Could you please paste the articleโ€™s content (excluding the source line you already provided) here? Once I have the text, Iโ€™ll translate it into Korean while preserving the original formatting, markdown, and code blocks.

๊ตฌ์ถ•ํ•  ๋‚ด์šฉ

  • Excel ์ •๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฐ์Šคํฌํ†ฑ GUI ์•ฑ
  • ์ž๋™ ๊ฒฐ์ธก๊ฐ’ ์ฒ˜๋ฆฌ
  • ์ค‘๋ณต ํƒ์ง€
  • ํœด๋ฆฌ์Šคํ‹ฑ โ€œ๋ฐ์ดํ„ฐ ๊ฑด๊ฐ•โ€ ์ ์ˆ˜ ๋งค๊ธฐ๊ธฐ
  • ๊ฒฐ๊ณผ๋ฅผ Excel, PDF, JSON, ๋ฐ TXT ํ˜•์‹์œผ๋กœ ๋‚ด๋ณด๋‚ด๊ธฐ

GitHub ์ €์žฅ์†Œ (์ „์ฒด ์Šคํฌ๋ฆฝํŠธ):
๐Ÿ‘‰

๐Ÿงฐ ์ „์ œ ์กฐ๊ฑด

  • Pythonโ€ฏ3.9+
  • ๊ธฐ๋ณธ Python ์ง€์‹

ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜

pip install pandas numpy openpyxl ttkbootstrap reportlab

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

SmartExcelGuardian/
โ”‚โ”€โ”€ main.py
โ”‚โ”€โ”€ logo.ico
โ”‚โ”€โ”€ excelguardian.log

1๏ธโƒฃ Import Required Libraries

# Core
import os
import sys
import threading
import json
import tkinter as tk
from tkinter import filedialog

# UI
import ttkbootstrap as tb
from ttkbootstrap.constants import *

# Misc
from datetime import datetime

Why these modules?

ModulePurpose
tkinterGUI ๊ธฐ๋ฐ˜
ttkbootstrapํ˜„๋Œ€์ ์ธ ๋‹คํฌ UI ํ…Œ๋งˆ
threading์ •๋ฆฌ ์ž‘์—… ์ค‘ UI ์‘๋‹ต์„ฑ ์œ ์ง€
pandas๋ฐ์ดํ„ฐ ์ •๋ฆฌ
numpy์ˆ˜์น˜ ์—ฐ์‚ฐ
openpyxlExcel ๋‚ด๋ณด๋‚ด๊ธฐ ๋ฐ ์„œ์‹ ์ง€์ •
re์—ด ์ด๋ฆ„ ์ •๊ทœํ™”
reportlab์ „๋ฌธ PDF ๋ณด๊ณ ์„œ ์ƒ์„ฑ
# Data & Excel
import pandas as pd
import numpy as np
import re
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Font
from openpyxl.utils.dataframe import dataframe_to_rows

# PDF Export
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.colors import red, orange, green, black

2๏ธโƒฃ ์ „์—ญ ์ƒํƒœ ๋ฐ ๋กœ๊น…

stop_event      = threading.Event()          # Allows canceling cleanup
cleanup_results = {}                         # Shared export data
log_file        = os.path.join(os.getcwd(), "excelguardian.log")

3๏ธโƒฃ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ—ฌํผ ํ•จ์ˆ˜

๋ฆฌ์†Œ์Šค ๋กœ๋” (ํŒจํ‚ค์ง•๋œ ์•ฑ์šฉ)

def resource_path(file_name):
    """Return absolute path for bundled resources (PyInstaller support)."""
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, file_name)

์ปฌ๋Ÿผโ€‘์ด๋ฆ„ ํด๋ฆฌ๋„ˆ

def clean_column_name(name):
    """Normalize column names: strip, lowerโ€‘case, remove punctuation, replace spaces with '_'."""
    name = name.strip().lower()
    name = re.sub(r"[^\w\s]", "", name)   # Remove nonโ€‘alphanumeric chars
    name = re.sub(r"\s+", "_", name)      # Replace spaces with underscores
    return name

์˜ˆ์‹œ

์›๋ณธ์ •๋ฆฌ๋œ
Total Sales ($)total_sales

NumPy โ†’ JSON ๋ณ€ํ™˜๊ธฐ

def convert_numpy(obj):
    """Make NumPy types JSONโ€‘serialisable."""
    if isinstance(obj, np.integer):
        return int(obj)
    if isinstance(obj, np.floating):
        return float(obj)
    if isinstance(obj, np.ndarray):
        return obj.tolist()
    raise TypeError(f"Object of type {type(obj)} is not JSON serialisable")

4๏ธโƒฃ ๋ฉ”์ธ ์œˆ๋„์šฐ ๋งŒ๋“ค๊ธฐ

app = tb.Window(themename="darkly")
app.title("SmartExcelGuardian v1.1.0")
app.geometry("1100x650")

์™œ ttkbootstrap์ธ๊ฐ€?

  • ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ˜„๋Œ€์ ์ธ ์Šคํƒ€์ผ๋ง
  • ๋‚ด์žฅ ๋‹คํฌ ๋ชจ๋“œ ์ง€์›
  • ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ ๋„์šฐ๋ฏธ

5๏ธโƒฃ ์ œ๋ชฉ ์„น์…˜

tb.Label(app,
         text="SmartExcelGuardian",
         font=("Segoe UI", 22, "bold")).pack(pady=(10, 2))

tb.Label(app,
         text="Professional Excel Data Guardian Tool",
         font=("Segoe UI", 10, "italic"),
         foreground="#9ca3af").pack(pady=(0, 8))

์•ฑ ํ—ค๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

6๏ธโƒฃ Excel ํŒŒ์ผ ์„ ํƒ๊ธฐ

file_path = tk.StringVar()

# Row container (youโ€™ll need to create `row1` as a Frame first)
tb.Entry(row1,
        textvariable=file_path,
        width=60).pack(side="left", padx=6)

tb.Button(row1,
          text="๐Ÿ“„ Excel File",
          command=lambda: file_path.set(
              filedialog.askopenfilename(
                  filetypes=[("Excel Files", "*.xlsx *.xls")]
              )
          )).pack(side="left")

์‚ฌ์šฉ์ž๊ฐ€ Excel ์›Œํฌ๋ถ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

7๏ธโƒฃ ์ •๋ฆฌ ์ œ์–ด ๋ฒ„ํŠผ

start_btn = tb.Button(row2,
                     text="๐Ÿ›ก CLEAN DATA",
                     bootstyle="success")

stop_btn = tb.Button(row2,
                    text="๐Ÿ›‘ STOP",
                    bootstyle="danger-outline",
                    state="disabled")
  • CLEAN DATA โ†’ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๋ฅผ ์‹œ์ž‘ํ•˜์—ฌ ์ •๋ฆฌ ์—”์ง„์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • STOP โ†’ ์ฒ˜๋ฆฌ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค.

8๏ธโƒฃ Results Table (Treeview)

cols = (
    "column", "original_type", "suggested_type",
    "cleaned_type", "missing_values",
    "duplicates_detected", "heuristic_score",
    "rename_suggestion"
)

tree = tb.Treeview(row3, columns=cols, show="headings")

์—ด๋ณ„ ๊ฑด๊ฐ• ๋ถ„์„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

9๏ธโƒฃ ํœด๋ฆฌ์Šคํ‹ฑ ์ ์ˆ˜ ์ฒด๊ณ„

def heuristic_score(missing, duplicates, type_issue):
    """Return a risk score from 0โ€‘100."""
    score = 0
    score += min(30, missing * 2)          # Missing values (max 30)
    score += min(30, duplicates * 2)       # Duplicates (max 30)
    score += 40 if type_issue else 0       # Typeโ€‘mismatch (max 40)
    return min(score, 100)
์ ์ˆ˜์œ„ํ—˜ ์ˆ˜์ค€์ง€ํ‘œ
0โ€‘30์ •์ƒ๐ŸŸข
31โ€‘70๋ณดํ†ต๐ŸŸ 
71โ€‘100๊ณ ์œ„ํ—˜๐Ÿ”ด

๐Ÿ”Ÿ ๋ฐ์ดํ„ฐโ€‘ํด๋ฆฌ๋‹ ์—”์ง„

def assess_and_clean(df):
    """Iterate over columns, assess health, and clean data inโ€‘place."""
    for col in df.columns:
        series = df[col]

        # ---------- Numeric Columns ----------
        coerced = pd.to_numeric(series, errors="coerce")
        if coerced.notna().any():                     # At least one numeric value
            cleaned_series = coerced.fillna(coerced.mean())
            df[col] = cleaned_series
            continue

        # ---------- Text Columns ----------
        cleaned_series = series.astype("string").fillna(series.mode()[0])
        df[col] = cleaned_series

ํ•ต์‹ฌ ์ž‘์—…

  • Numeric columns โ†’ ์ˆซ์ž๋กœ ๊ฐ•์ œ ๋ณ€ํ™˜ํ•˜๊ณ , ๊ฒฐ์ธก๊ฐ’์„ ํ•ด๋‹น ์—ด์˜ ํ‰๊ท ์œผ๋กœ ์ฑ„์›๋‹ˆ๋‹ค.
  • Text columns โ†’ ๋ฌธ์ž์—ด ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ๊ฒฐ์ธก๊ฐ’์„ ๊ฐ€์žฅ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฐ’(๋ชจ๋“œ)์œผ๋กœ ์ฑ„์›๋‹ˆ๋‹ค.

๋‹ค์Œ ๋‹จ๊ณ„ (๋ฐœ์ทŒ๋ณธ์— ํ‘œ์‹œ๋˜์ง€ ์•Š์Œ)

  • ํœด๋ฆฌ์Šคํ‹ฑ ์ ์ˆ˜์™€ ์ด๋ฆ„ ๋ณ€๊ฒฝ ์ œ์•ˆ์„ Treeview์— ์ฑ„์›Œ ๋„ฃ๊ธฐ.
  • CLEAN DATA ๋ฒ„ํŠผ์„ ๋ฐฐ๊ฒฝ ์Šค๋ ˆ๋“œ์—์„œ assess_and_clean์„ ์‹คํ–‰ํ•˜๋„๋ก ์—ฐ๊ฒฐํ•˜๊ธฐ.
  • ์œ„์—์„œ ์ •์˜ํ•œ ํ—ฌํผ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•ด Excel, PDF, JSON, TXT ๋‚ด๋ณด๋‚ด๊ธฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ.
  • excelguardian.log์— ์ ์ ˆํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ์™€ ๋กœ๊น… ์ถ”๊ฐ€ํ•˜๊ธฐ.

ํ–‰๋ณตํ•œ ํด๋ฆฌ๋‹! ๐ŸŽ‰

1๏ธโƒฃ ์Šค๋ ˆ๋“œ ๊ธฐ๋ฐ˜ ์ •๋ฆฌ ์‹คํ–‰

threading.Thread(
    target=run_cleanup,
    daemon=True
).start()

์™œ ์Šค๋ ˆ๋”ฉ์ธ๊ฐ€?

  • UI๊ฐ€ ๋ฐ˜์‘์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค
  • ๋Œ€์šฉ๋Ÿ‰ Excel ํŒŒ์ผ์—์„œ ๋ฉˆ์ถค์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค

2๏ธโƒฃ ์ˆ˜์‹์ด ํฌํ•จ๋œ Excel ๋‚ด๋ณด๋‚ด๊ธฐ

sum_formula  = f"=SUM(A2:A{ws.max_row})"
mean_formula = f"=AVERAGE(A2:A{ws.max_row})"

์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค:

  • SUM
  • AVERAGE

3๏ธโƒฃ ์กฐ๊ฑด๋ถ€ ์„œ์‹

fill = PatternFill(start_color="FF9999", fill_type="solid")
cell.font = Font(bold=True)

์œ„ํ—˜๋„๊ฐ€ ๋†’์€ ์—ด์€:

  • ๊ฐ•์กฐ ํ‘œ์‹œ ๐Ÿ”ด
  • ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ๊ตต๊ฒŒ ํ‘œ์‹œ

4๏ธโƒฃ PDF ๋ณด๊ณ ์„œ ๋‚ด๋ณด๋‚ด๊ธฐ

def score_color(score):
    if score >= 71:
        return red
    elif score >= 31:
        return orange
    else:
        return green

๋‹ค์ค‘ ํŽ˜์ด์ง€ PDF ๊ฐ์‚ฌ ๋ณด๊ณ ์„œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • ์ƒ‰์ƒ์œผ๋กœ ๊ตฌ๋ถ„๋œ ์ ์ˆ˜
  • ์—ด ์š”์•ฝ
  • ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ

5๏ธโƒฃ ์ •๋ณด ๋ฐ ๋„์›€๋ง ์ฐฝ

tb.Label(frame, text="How to Use", font=("Segoe UI", 12, "bold"))

Provides:

  • Feature overview
  • Usage steps
  • Developer info

๐Ÿš€ ์ตœ์ข… ๊ฒฐ๊ณผ

์ด์ œ ๋‹ค์Œ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค:

  • ์ „๋ฌธ์ ์ธ Excel ํด๋ฆฌ๋„ˆ
  • ๋ฐ์Šคํฌํ†ฑ GUI
  • ํœด๋ฆฌ์Šคํ‹ฑ ์ ์ˆ˜ ์‹œ์Šคํ…œ
  • ๋‹ค์ค‘ ํ˜•์‹ ๋‚ด๋ณด๋‚ด๊ธฐ

๐Ÿ“Œ ๋‹ค์Œ ๊ฐœ์„  ์‚ฌํ•ญ

  • ์‹œํŠธ๋ณ„ ์„ ํƒ ์ถ”๊ฐ€
  • ์ฐจํŠธ ์ถ”๊ฐ€ (๋ฐ์ดํ„ฐ ๊ฑด๊ฐ• ์ถ”์„ธ)
  • ์‚ฌ์šฉ์ž ํ”„๋ฆฌ์…‹ ์ €์žฅ
  • .exe ํŒŒ์ผ๋กœ ํŒจํ‚ค์ง•

๐Ÿ”— Full Source Code

SmartExcelGuardian on GitHub

SmartExcelGuardian

Back to Blog

๊ด€๋ จ ๊ธ€

๋” ๋ณด๊ธฐ ยป

Python, Tkinter, MSS๋ฅผ ์‚ฌ์šฉํ•œ ํ™”๋ฉด ์บก์ฒ˜ ๋ฐ ์Šค์ฝ”ํ”„ ๋„๊ตฌ ๋งŒ๋“ค๊ธฐ

์‹ค์‹œ๊ฐ„ ํ™”๋ฉด ์บก์ฒ˜ GUI์™€ ์Šค์ฝ”ํ”„: ์ด ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™”๋ฉด์„ ์บก์ฒ˜ํ•˜๊ณ  ๋น„๋””์˜ค ์Šค์ฝ”ํ”„ ๋ฒกํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ž‘์€ GUI ๋„๊ตฌ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Python๊ณผ Tkinter๋กœ ๊ฐ„๋‹จํ•œ ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ ๋งŒ๋“ค๊ธฐ โ€“ FileMate Explorer

๐Ÿ“‚ FileMate Explorer โ€“ ๊ฐ€๋ฒผ์šด ํŒŒ์ด์ฌ ํŒŒ์ผ ๊ด€๋ฆฌ์ž. ์™„์ „ํžˆ ํŒŒ์ด์ฌ์œผ๋กœ ๋งŒ๋“  ๊ฐ€๋ฒผ์šด ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ๋ฅผ ์›ํ•˜์…จ๋‚˜์š”? Tkinter ๊ธฐ๋ฐ˜ FileMate Explorer๋ฅผ ๋งŒ๋‚˜๋ณด์„ธ์š”.