업무 자동화 ① 엑셀 (2) — 서식을 입히고 함수로 묶는다
Article
앞 강의에서 폴더를 훑어 엑셀 표를 만들었다. 표는 나오지만 밋밋하다. 제목 줄이 데이터와 구분이 안 되고, 열이 좁아 글자가 잘려 보인다.
이번 강의는 두 가지다. 서식을 입히는 것과 함수로 묶는 것.
파일은 계속 ai-course 폴더에 넣는다. 이번 강의는 61번부터다.
1. 서식을 입힌다 — 여기서 「있어 보인다」
네 가지만 더하면 사람이 쓸 만한 문서가 된다. 앞부분은 앞 강의와 같고, wb.save 앞에 네 덩어리가 들어갔다.
import os
import glob
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
rows = []
for path in sorted(glob.glob("서류/*.txt")):
with open(path, encoding="utf-8") as f:
text = f.read()
rows.append({
"파일명": os.path.basename(path),
"크기KB": round(os.path.getsize(path) / 1024, 1),
"글자수": len(text),
"첫문장": text.split("\n")[1][:20],
})
HEADERS = ["파일명", "크기KB", "글자수", "첫문장"]
wb = Workbook()
ws = wb.active
ws.title = "서류 현황"
ws.append(HEADERS)
for r in rows:
ws.append([r[h] for h in HEADERS])
# 1. 제목 줄을 눈에 띄게 - ws[1] 은 1행 전체다
for cell in ws[1]:
cell.font = Font(bold=True, color="FFFFFF") # 굵은 흰 글씨
cell.fill = PatternFill("solid", fgColor="333333") # 짙은 회색 배경
cell.alignment = Alignment(horizontal="center") # 가운데 정렬
# 2. 열 너비 - 안 하면 글자가 잘려 보인다
for col, width in zip("ABCD", (16, 10, 10, 26)):
ws.column_dimensions[col].width = width
# 3. 필터 버튼 - ws.dimensions 는 "A1:D6" 처럼 표 전체 범위다
ws.auto_filter.ref = ws.dimensions
# 4. 첫 줄 고정 - A2 위쪽이 얼어붙는다. 스크롤해도 제목이 보인다
ws.freeze_panes = "A2"
wb.save("서류현황.xlsx")
print(ws.dimensions)
print(ws.title)
- A1:D6
- 서류 현황
A1:D6 은 제목 줄 1행 + 문서 5행이다. 이 문자열을 그대로 auto_filter.ref 에 넣었으니 범위를 손으로 셀 필요가 없다. 엑셀에서 열어보면 앞 강의와 확실히 다르다.
| 코드 | 엑셀에서 보이는 것 |
|---|---|
Font(bold=True, color="FFFFFF") | 제목 줄이 굵은 흰 글씨 |
PatternFill("solid", fgColor="333333") | 제목 줄 배경이 짙은 회색 |
column_dimensions["A"].width = 16 | A열이 넓어져 파일명이 안 잘린다 |
auto_filter.ref = ws.dimensions | 제목 줄에 필터 버튼이 생긴다 |
freeze_panes = "A2" | 스크롤해도 제목 줄이 안 사라진다 |
2. 재사용할 수 있게 함수로
한 번 쓰고 버릴 코드가 아니다. 폴더 이름만 바꿔 다음 달에 다시 돌릴 수 있게 묶는다.
import os
import glob
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
def 폴더현황_엑셀(folder, out_path="현황.xlsx"):
"""폴더 안 txt 를 훑어 서식 갖춘 엑셀 파일로 만든다."""
headers = ["파일명", "크기KB", "글자수", "첫문장"]
rows = []
for path in sorted(glob.glob(f"{folder}/*.txt")):
try:
with open(path, encoding="utf-8") as f:
text = f.read()
except UnicodeDecodeError:
print(f"[건너뜀] {path} - utf-8 이 아니다")
continue
lines = text.split("\n")
rows.append({
"파일명": os.path.basename(path),
"크기KB": round(os.path.getsize(path) / 1024, 1),
"글자수": len(text),
# 줄이 하나뿐인 파일에 lines[1] 을 쓰면 IndexError 가 난다
"첫문장": (lines[1] if len(lines) > 1 else lines[0])[:20],
})
if not rows:
print(f"[경고] '{folder}' 에서 txt 를 못 찾았다.")
return None
wb = Workbook()
ws = wb.active
ws.title = "현황"
ws.append(headers)
for r in rows:
ws.append([r[h] for h in headers])
for cell in ws[1]:
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", fgColor="333333")
cell.alignment = Alignment(horizontal="center")
for col, width in zip("ABCD", (16, 10, 10, 26)):
ws.column_dimensions[col].width = width
ws.auto_filter.ref = ws.dimensions
ws.freeze_panes = "A2"
wb.save(out_path)
print(f"{len(rows)}건을 {out_path} 에 저장했다")
return out_path
폴더현황_엑셀("서류")
폴더현황_엑셀("없는폴더")
- 5건을 현황.xlsx 에 저장했다
- [경고] '없는폴더' 에서 txt 를 못 찾았다.
이번 강의에 나온 것
| 쓴 것 | 하는 일 |
|---|---|
ws[1] | 1행 전체 (제목 줄) |
Font(bold=True, color="FFFFFF") | 글자 서식 |
PatternFill("solid", fgColor="333333") | 배경색 |
Alignment(horizontal="center") | 가운데 정렬 |
column_dimensions["A"].width | 열 너비 |
ws.dimensions | 표 전체 범위 — "A1:D6" |
ws.auto_filter.ref = ... | 필터 버튼 |
ws.freeze_panes = "A2" | 첫 줄 고정 |
ws.cell(row=2, column=3) | 칸 하나를 집는다. 1부터 센다 |
스스로 해보기
- 01
글자수가 많은 줄에 색을 넣는다
ai-course/63_연습1.py. 500자를 넘는 행의 글자수 칸만 배경을 노랗게 칠해본다. 힌트는ws.cell(row=번호, column=3)으로 칸 하나를 집는 것이다. - 02
4강의 docs 폴더에 그대로 돌려본다
ai-course/64_연습2.py.폴더현황_엑셀("docs", "docs현황.xlsx")를 불러본다. 함수 안을 한 글자도 안 고치고 다른 폴더가 처리되나.
1번 — 조건에 맞는 칸에 색 넣기충분히 고민해본 뒤 꼭 필요한 경우에만 열어보세요
ws.cell(row=..., column=...) 로 칸 하나를 집는다. 행과 열 모두 1부터 센다 — 파이썬의 0과 다르다.
제목 줄이 1행이므로 데이터는 2행부터다. enumerate(rows, start=2) 가 그걸 맞춰준다.
from openpyxl.styles import PatternFill
노랑 = PatternFill("solid", fgColor="FFF3B0")
for i, r in enumerate(rows, start=2): # 2행부터가 데이터
if r["글자수"] > 500:
ws.cell(row=i, column=3).fill = 노랑 # 3열 = 글자수
print(i, r["파일명"], r["글자수"])
wb.save("서류현황.xlsx")5 보고서.txt 817 과 6 회의록.txt 1087 두 줄이 찍히고, 엑셀에서 그 두 칸만 노랗다.
행 번호가 5·6인 것에 주의한다. 파일명 순서(견적서·계약서·메모·보고서·회의록)에서 4·5번째이고, 제목 줄 때문에 하나씩 밀렸다.
변수 이름에 한글을 써도 된다. 실무에서는 거의 안 쓰지만 문법상 문제는 없다.
2번 — 다른 폴더에 그대로 돌리기충분히 고민해본 뒤 꼭 필요한 경우에만 열어보세요
함수 안은 한 글자도 안 고친다. 인자만 바꿔 부른다. 이게 폴더 이름을 인자로 뺀 값어치다.
폴더현황_엑셀("docs", "docs현황.xlsx")3건을 docs현황.xlsx 에 저장했다 가 찍힌다. 4강에서 만든 backend.txt · data.txt · frontend.txt 셋이다.
대상이 바뀌었는데 코드는 안 바뀌었다. 이 시리즈가 계속 반복할 이야기다 — 4강의 DOCS_DIR, 뒤에서 만들 검색기까지 전부 같은 규칙이다.
docs 파일들은 첫 줄이 「수업용 예제」라 첫문장 열에 두 번째 줄이 들어온다. 서류 폴더와 같은 모양이라 함수가 그대로 먹힌다.
정리하면
서식 네 덩어리 — 글자 · 배경 · 너비 · 고정 — 을 더하면 밋밋한 표가 사람이 열어볼 문서가 된다. 코드는 길어 보여도 wb.save 앞에 붙는 네 조각뿐이다.
그리고 폴더 이름을 인자로 빼서 함수로 묶었다. 다음 달에는 다시 실행만 하면 된다.
다음 강의는 같은 틀로 사진을 다룬다. 폴더를 훑고, 하나씩 열고, 바꿔서, 저장한다. 틀이 똑같다.