メインコンテンツにスキップ

엑셀 자동화

Python의 openpyxl 라이브러리를 사용하면 Excel 파일을 프로그래밍 방식으로 생성하고 편집할 수 있습니다. 반복적인 데이터 입력, 보고서 생성, 데이터 분석 등을 자동화해보겠습니다.

설치하기

# openpyxl 설치
pip install openpyxl

# 차트 기능까지 사용하려면
pip install openpyxl[charts]

기본 작업

워크북 생성 및 저장

from openpyxl import Workbook

# 새 워크북 생성
wb = Workbook()

# 활성 시트 가져오기
ws = wb.active

# 시트 이름 변경
ws.title = "매출 데이터"

# 셀에 값 쓰기
ws['A1'] = "안녕하세요"
ws['B1'] = 42
ws['C1'] = 3.14

# 파일 저장
wb.save('example.xlsx')

기존 파일 읽기

from openpyxl import load_workbook

# 엑셀 파일 열기
wb = load_workbook('example.xlsx')

# 시트 이름 목록
print(wb.sheetnames) # ['매출 데이터', 'Sheet2']

# 특정 시트 선택
ws = wb['매출 데이터']

# 또는 활성 시트
ws = wb.active

# 셀 값 읽기
value = ws['A1'].value
print(value)

셀 다루기

셀 접근 방법

# 방법 1: 셀 좌표 사용
ws['A1'] = "이름"

# 방법 2: row, column 인덱스 사용 (1부터 시작)
ws.cell(row=1, column=1).value = "이름"
ws.cell(row=1, column=2).value = "나이"

# 셀 값 읽기
name = ws['A1'].value
age = ws.cell(row=1, column=2).value

여러 셀에 데이터 쓰기

from openpyxl import Workbook

wb = Workbook()
ws = wb.active

# 행 단위로 데이터 추가
data = [
["이름", "나이", "직업"],
["김철수", 30, "개발자"],
["이영희", 28, "디자이너"],
["박민수", 35, "기획자"]
]

for row in data:
ws.append(row)

wb.save('직원명단.xlsx')

범위로 셀 읽기

# 특정 범위 읽기
for row in ws['A1:C4']:
for cell in row:
print(cell.value, end='\t')
print()

# 모든 행 순회
for row in ws.iter_rows(min_row=2, values_only=True):
print(row)

# 모든 열 순회
for col in ws.iter_cols(min_col=1, max_col=3, values_only=True):
print(col)

# 특정 범위의 값만 가져오기
for row in ws.iter_rows(min_row=2, max_row=5, min_col=1, max_col=3, values_only=True):
name, age, job = row
print(f"{name}: {age}세, {job}")

셀 스타일링

폰트 스타일

from openpyxl.styles import Font

# 굵게, 크기, 색상
ws['A1'].font = Font(
name='맑은 고딕',
size=14,
bold=True,
italic=False,
color='FF0000' # 빨간색 (RGB 헥스)
)

셀 배경색

from openpyxl.styles import PatternFill

# 배경색 채우기
ws['A1'].fill = PatternFill(
start_color='FFFF00', # 노란색
end_color='FFFF00',
fill_type='solid'
)

테두리

from openpyxl.styles import Border, Side

# 테두리 스타일
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)

ws['A1'].border = thin_border

정렬

from openpyxl.styles import Alignment

# 가운데 정렬
ws['A1'].alignment = Alignment(
horizontal='center',
vertical='center',
wrap_text=True # 텍스트 줄바꿈
)

숫자 형식

# 통화 형식
ws['B2'].number_format = '#,##0원'

# 백분율
ws['C2'].number_format = '0.00%'

# 날짜
ws['D2'].number_format = 'yyyy-mm-dd'

# 소수점
ws['E2'].number_format = '0.00'

수식 사용하기

from openpyxl import Workbook

wb = Workbook()
ws = wb.active

# 데이터 입력
ws['A1'] = "상품"
ws['B1'] = "단가"
ws['C1'] = "수량"
ws['D1'] = "합계"

ws['A2'] = "사과"
ws['B2'] = 1000
ws['C2'] = 5

# 수식 입력 (B2 * C2)
ws['D2'] = "=B2*C2"

# SUM 함수
ws['A10'] = "총합"
ws['D10'] = "=SUM(D2:D9)"

# 평균
ws['A11'] = "평균"
ws['D11'] = "=AVERAGE(D2:D9)"

# IF 함수
ws['E2'] = "=IF(D2>5000, '고가', '저가')"

wb.save('수식예제.xlsx')

차트 만들기

막대 차트

from openpyxl import Workbook
from openpyxl.chart import BarChart, Reference

wb = Workbook()
ws = wb.active

# 데이터 입력
data = [
["월", "매출"],
["1월", 1000],
["2월", 1500],
["3월", 1200],
["4월", 1800],
]

for row in data:
ws.append(row)

# 차트 생성
chart = BarChart()
chart.title = "월별 매출"
chart.x_axis.title = "월"
chart.y_axis.title = "매출 (만원)"

# 데이터 범위 설정
data_ref = Reference(ws, min_col=2, min_row=1, max_row=5)
categories = Reference(ws, min_col=1, min_row=2, max_row=5)

chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(categories)

# 차트 추가
ws.add_chart(chart, "D2")

wb.save('차트예제.xlsx')

선 차트

from openpyxl.chart import LineChart, Reference

# 선 차트 생성
chart = LineChart()
chart.title = "주간 방문자 수"
chart.style = 10
chart.y_axis.title = '방문자'
chart.x_axis.title = '요일'

data_ref = Reference(ws, min_col=2, min_row=1, max_col=3, max_row=8)
chart.add_data(data_ref, titles_from_data=True)

categories = Reference(ws, min_col=1, min_row=2, max_row=8)
chart.set_categories(categories)

ws.add_chart(chart, "E2")

원 차트

from openpyxl.chart import PieChart, Reference

chart = PieChart()
chart.title = "판매 비율"

data_ref = Reference(ws, min_col=2, min_row=1, max_row=5)
labels = Reference(ws, min_col=1, min_row=2, max_row=5)

chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(labels)

ws.add_chart(chart, "D2")

실전 예제

1. 자동 매출 보고서 생성

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.chart import BarChart, Reference
from datetime import datetime

def create_sales_report(sales_data, filename):
"""매출 보고서 자동 생성"""

wb = Workbook()
ws = wb.active
ws.title = "매출 현황"

# 제목
ws['A1'] = "월간 매출 보고서"
ws['A1'].font = Font(size=16, bold=True)
ws['A1'].alignment = Alignment(horizontal='center')
ws.merge_cells('A1:E1')

# 작성일
ws['A2'] = f"작성일: {datetime.now().strftime('%Y-%m-%d')}"

# 헤더
headers = ["제품명", "판매수량", "단가", "매출액", "비고"]
ws.append([]) # 빈 행
ws.append(headers)

# 헤더 스타일
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
header_font = Font(color='FFFFFF', bold=True)

for col in range(1, 6):
cell = ws.cell(row=4, column=col)
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center')

# 데이터 입력
total_sales = 0
for item in sales_data:
product, quantity, price = item
sales = quantity * price
total_sales += sales

remark = "목표달성" if sales >= 1000000 else "노력필요"

ws.append([product, quantity, price, sales, remark])

# 데이터 스타일
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)

for row in ws.iter_rows(min_row=4, max_row=ws.max_row, min_col=1, max_col=5):
for cell in row:
cell.border = thin_border
cell.alignment = Alignment(horizontal='center')

# 숫자 형식
for row in range(5, ws.max_row + 1):
ws.cell(row=row, column=2).number_format = '#,##0' # 수량
ws.cell(row=row, column=3).number_format = '#,##0원' # 단가
ws.cell(row=row, column=4).number_format = '#,##0원' # 매출액

# 합계 행
total_row = ws.max_row + 1
ws.cell(row=total_row, column=1).value = "총 매출"
ws.cell(row=total_row, column=4).value = f"=SUM(D5:D{ws.max_row})"
ws.cell(row=total_row, column=1).font = Font(bold=True)
ws.cell(row=total_row, column=4).font = Font(bold=True)
ws.cell(row=total_row, column=4).fill = PatternFill(
start_color='FFC000', end_color='FFC000', fill_type='solid'
)

# 차트 추가
chart = BarChart()
chart.title = "제품별 매출 현황"
chart.y_axis.title = '매출액 (원)'

data_ref = Reference(ws, min_col=4, min_row=4, max_row=ws.max_row - 1)
categories = Reference(ws, min_col=1, min_row=5, max_row=ws.max_row - 1)

chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(categories)

ws.add_chart(chart, "G4")

# 열 너비 조정
ws.column_dimensions['A'].width = 15
ws.column_dimensions['B'].width = 12
ws.column_dimensions['C'].width = 12
ws.column_dimensions['D'].width = 15
ws.column_dimensions['E'].width = 12

wb.save(filename)
print(f"보고서 생성 완료: {filename}")

# 사용 예제
sales_data = [
("노트북", 15, 1200000),
("마우스", 50, 25000),
("키보드", 30, 80000),
("모니터", 20, 350000),
("헤드셋", 25, 120000)
]

create_sales_report(sales_data, "매출보고서.xlsx")

2. 여러 엑셀 파일 통합

from openpyxl import Workbook, load_workbook
import os

def merge_excel_files(folder_path, output_file):
"""여러 엑셀 파일을 하나로 통합"""

wb_output = Workbook()
ws_output = wb_output.active
ws_output.title = "통합 데이터"

# 헤더를 한 번만 추가하기 위한 플래그
header_written = False

# 폴더 내 모든 엑셀 파일 처리
for filename in os.listdir(folder_path):
if not filename.endswith(('.xlsx', '.xls')):
continue

file_path = os.path.join(folder_path, filename)
print(f"처리 중: {filename}")

try:
wb = load_workbook(file_path)
ws = wb.active

# 데이터 복사
for i, row in enumerate(ws.iter_rows(values_only=True), 1):
# 첫 번째 파일의 헤더만 포함
if i == 1:
if not header_written:
ws_output.append(row)
header_written = True
else:
ws_output.append(row)

except Exception as e:
print(f"오류 ({filename}): {e}")

wb_output.save(output_file)
print(f"\n통합 완료: {output_file}")
print(f"총 행 수: {ws_output.max_row}")

# 사용 예제
merge_excel_files('/Users/username/분기별데이터', '통합데이터.xlsx')

3. 송장 자동 생성

from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, Border, Side
from datetime import datetime

def create_invoice(invoice_data, filename):
"""송장 자동 생성"""

wb = Workbook()
ws = wb.active

# 회사 정보
ws['A1'] = "주식회사 테크솔루션"
ws['A1'].font = Font(size=18, bold=True)

ws['A2'] = "서울시 강남구 테헤란로 123"
ws['A3'] = "Tel: 02-1234-5678"
ws['A4'] = "사업자번호: 123-45-67890"

# 송장 제목
ws['A6'] = "거 래 명 세 서"
ws['A6'].font = Font(size=16, bold=True)
ws['A6'].alignment = Alignment(horizontal='center')
ws.merge_cells('A6:F6')

# 고객 정보
ws['A8'] = "받는 분:"
ws['B8'] = invoice_data['customer_name']
ws['A9'] = "날짜:"
ws['B9'] = datetime.now().strftime('%Y년 %m월 %d일')
ws['A10'] = "송장번호:"
ws['B10'] = invoice_data['invoice_number']

# 테이블 헤더
headers = ["번호", "품목", "수량", "단가", "금액", "비고"]
ws.append([]) # 빈 행
ws.append(headers)

# 헤더 스타일
header_row = 12
for col in range(1, 7):
cell = ws.cell(row=header_row, column=col)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')

# 품목 데이터
total = 0
for i, item in enumerate(invoice_data['items'], 1):
product, quantity, price, note = item
amount = quantity * price
total += amount

ws.append([i, product, quantity, price, amount, note])

# 숫자 형식
row = ws.max_row
ws.cell(row=row, column=3).number_format = '#,##0'
ws.cell(row=row, column=4).number_format = '#,##0원'
ws.cell(row=row, column=5).number_format = '#,##0원'

# 테두리
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)

for row in ws.iter_rows(min_row=12, max_row=ws.max_row, min_col=1, max_col=6):
for cell in row:
cell.border = thin_border
cell.alignment = Alignment(horizontal='center')

# 합계
ws.append([])
total_row = ws.max_row + 1
ws.cell(row=total_row, column=4).value = "합계"
ws.cell(row=total_row, column=5).value = total
ws.cell(row=total_row, column=4).font = Font(bold=True, size=12)
ws.cell(row=total_row, column=5).font = Font(bold=True, size=12)
ws.cell(row=total_row, column=5).number_format = '#,##0원'

# VAT (10%)
vat_row = total_row + 1
vat = total * 0.1
ws.cell(row=vat_row, column=4).value = "VAT (10%)"
ws.cell(row=vat_row, column=5).value = vat
ws.cell(row=vat_row, column=5).number_format = '#,##0원'

# 총액
grand_total_row = vat_row + 1
ws.cell(row=grand_total_row, column=4).value = "총액"
ws.cell(row=grand_total_row, column=5).value = total + vat
ws.cell(row=grand_total_row, column=4).font = Font(bold=True, size=14)
ws.cell(row=grand_total_row, column=5).font = Font(bold=True, size=14)
ws.cell(row=grand_total_row, column=5).number_format = '#,##0원'

# 열 너비
ws.column_dimensions['A'].width = 8
ws.column_dimensions['B'].width = 20
ws.column_dimensions['C'].width = 10
ws.column_dimensions['D'].width = 12
ws.column_dimensions['E'].width = 15
ws.column_dimensions['F'].width = 15

wb.save(filename)
print(f"송장 생성 완료: {filename}")

# 사용 예제
invoice_data = {
'customer_name': '(주)글로벌테크',
'invoice_number': 'INV-2024-001',
'items': [
("웹 사이트 개발", 1, 5000000, "메인 프로젝트"),
("유지보수 (1개월)", 1, 500000, "월간"),
("도메인 등록", 1, 15000, "연간"),
("호스팅", 12, 30000, "월간")
]
}

create_invoice(invoice_data, "송장_2024_001.xlsx")

4. 데이터 분석 및 피벗 테이블

from openpyxl import Workbook
from collections import defaultdict

def create_sales_analysis(raw_data, filename):
"""매출 데이터 분석 및 요약"""

wb = Workbook()

# 원본 데이터 시트
ws_raw = wb.active
ws_raw.title = "원본 데이터"

# 헤더
headers = ["날짜", "제품", "카테고리", "수량", "단가", "매출"]
ws_raw.append(headers)

# 데이터 입력
for row in raw_data:
ws_raw.append(row)

# 카테고리별 집계
category_sales = defaultdict(int)
product_sales = defaultdict(int)

for row in raw_data:
date, product, category, quantity, price, sales = row
category_sales[category] += sales
product_sales[product] += sales

# 카테고리별 요약 시트
ws_category = wb.create_sheet("카테고리별 매출")
ws_category.append(["카테고리", "매출액"])

for category, sales in sorted(category_sales.items(), key=lambda x: x[1], reverse=True):
ws_category.append([category, sales])

# 제품별 요약 시트
ws_product = wb.create_sheet("제품별 매출")
ws_product.append(["제품", "매출액"])

for product, sales in sorted(product_sales.items(), key=lambda x: x[1], reverse=True):
ws_product.append([product, sales])

# 스타일 적용 (모든 시트)
for ws in wb.worksheets:
# 헤더 스타일
for cell in ws[1]:
cell.font = Font(bold=True)

# 숫자 형식
if ws.max_row > 1:
for row in range(2, ws.max_row + 1):
if ws.max_column >= 2:
ws.cell(row=row, column=ws.max_column).number_format = '#,##0원'

wb.save(filename)
print(f"분석 완료: {filename}")

# 사용 예제
sales_data = [
["2024-01-01", "노트북", "전자제품", 2, 1200000, 2400000],
["2024-01-02", "마우스", "전자제품", 5, 25000, 125000],
["2024-01-03", "책상", "가구", 3, 150000, 450000],
["2024-01-04", "의자", "가구", 4, 80000, 320000],
["2024-01-05", "키보드", "전자제품", 3, 80000, 240000],
]

create_sales_analysis(sales_data, "매출분석.xlsx")

5. CSV to Excel 변환기

import csv
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill

def csv_to_excel(csv_file, excel_file):
"""CSV 파일을 스타일이 적용된 Excel로 변환"""

wb = Workbook()
ws = wb.active

# CSV 읽기
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.reader(f)

for i, row in enumerate(reader, 1):
ws.append(row)

# 첫 행 스타일 (헤더)
if i == 1:
for cell in ws[i]:
cell.font = Font(bold=True, color='FFFFFF')
cell.fill = PatternFill(
start_color='4472C4',
end_color='4472C4',
fill_type='solid'
)

# 열 너비 자동 조정
for column in ws.columns:
max_length = 0
column_letter = column[0].column_letter

for cell in column:
if cell.value:
max_length = max(max_length, len(str(cell.value)))

adjusted_width = min(max_length + 2, 50)
ws.column_dimensions[column_letter].width = adjusted_width

wb.save(excel_file)
print(f"변환 완료: {csv_file} -> {excel_file}")

# 사용 예제
csv_to_excel('data.csv', 'data.xlsx')

자주 묻는 질문

Q1. .xls 파일도 읽을 수 있나요?

A: openpyxl.xlsx 전용입니다. .xls 파일은 xlrd 라이브러리를 사용하세요.

# .xls 파일 읽기
import xlrd

workbook = xlrd.open_workbook('old_file.xls')
sheet = workbook.sheet_by_index(0)

# 셀 값 읽기
value = sheet.cell_value(0, 0)

Q2. 대용량 엑셀 파일을 어떻게 처리하나요?

A: read_only=Truewrite_only=True 모드를 사용하세요.

# 읽기 전용 모드 (메모리 효율적)
wb = load_workbook('large_file.xlsx', read_only=True)

# 쓰기 전용 모드
wb = Workbook(write_only=True)
ws = wb.create_sheet()

for row in data:
ws.append(row)

wb.save('output.xlsx')

Q3. 기존 엑셀 파일을 수정하면 서식이 사라져요.

A: openpyxl은 일부 고급 서식을 보존하지 못할 수 있습니다. 중요한 서식은 백업하고 작업하세요.

# 파일 복사본 만들기
import shutil
shutil.copy('original.xlsx', 'backup.xlsx')

# 복사본 수정
wb = load_workbook('original.xlsx')
# ... 작업 ...
wb.save('original.xlsx')

Q4. 여러 시트를 한 번에 작업할 수 있나요?

A: 네, 반복문으로 모든 시트를 처리할 수 있습니다.

wb = load_workbook('workbook.xlsx')

for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
print(f"처리 중: {sheet_name}")

# 각 시트 작업
for row in ws.iter_rows(values_only=True):
print(row)

Q5. pandas와 openpyxl 중 어떤 것을 사용해야 하나요?

A: 데이터 분석이 주 목적이면 pandas, 서식과 차트가 중요하면 openpyxl을 사용하세요.

# pandas로 Excel 읽기/쓰기
import pandas as pd

df = pd.read_excel('data.xlsx')
df.to_excel('output.xlsx', index=False)

# openpyxl로 서식 적용
from openpyxl import load_workbook
from openpyxl.styles import Font

wb = load_workbook('output.xlsx')
ws = wb.active
ws['A1'].font = Font(bold=True)
wb.save('output.xlsx')

다음 단계

엑셀 자동화를 배웠다면, 다음 주제로 넘어가세요:

  • 웹 자동화: Selenium으로 브라우저 제어하기
  • 작업 스케줄링: 정기적인 보고서 자동 생성하기
  • 데이터 시각화: matplotlib, seaborn으로 그래프 만들기

엑셀 자동화는 업무 생산성을 크게 향상시킬 수 있는 실용적인 기술입니다. 실제 업무에 적용해보세요!