보고서 만들고, 메일 보내고, 데이터 긁어오고… 클릭 대신 파이썬이 야근을 대신합니다.
안녕하세요, ICT리더 리치입니다. 실무에서 가장 시간을 많이 잡아먹는 일이 무엇일까요? 저는 늘 엑셀 정리 → 이메일 발송 → 웹 데이터 수집을 반복하다가 “이걸 한 번에 이어붙이면 하루가 바뀌겠다”는 확신이 들었어요. 오늘 글에서는 파이썬으로 엑셀 처리, 이메일 발송/수신 체크, 웹 크롤링을 하나의 워크플로로 묶는 자동화 프로젝트를 실제 코드와 함께 설명합니다. 초보자도 바로 따라 할 수 있도록 환경 세팅부터 스케줄링까지 전 과정을 담았어요.
📌 바로가기 목차
1. 프로젝트 개요와 환경 세팅
이번 자동화의 목표는 (1) 엑셀 데이터 정리→리포트 저장, (2) 이메일 일괄 발송·수신 확인, (3) 웹에서 신규 데이터 수집을 스크립트 한 번으로 끝내는 것입니다. 권장 환경은 Python 3.10+, 가상환경(venv), 그리고 주요 라이브러리 pandas
, openpyxl
, requests
, beautifulsoup4
, selenium
, smtplib
, imaplib
입니다. 크롬 자동화를 쓸 경우 WebDriver(또는 Chrome for Testing) 경로 설정이 필요합니다. 기업망에서는 프록시/방화벽 정책을 반드시 확인하세요.
# 1) 가상환경 생성 & 활성화
python -m venv .venv
# Windows
. .venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate
# 2) 필수 패키지 설치
python -m pip install --upgrade pip
pip install -r requirements.txt
# 3) 환경변수(.env) 준비 후 테스트 실행
python -m pip show pandas
python src/main.py --dry-run
automation_project/
├─ data/ # 원시/중간 데이터 (입출력)
├─ reports/ # 최종 엑셀 결과물(.xlsx)
├─ logs/ # 실행 로그(.log)
├─ src/
│ ├─ config.py # 설정/환경변수 로딩
│ ├─ excel_builder.py # 집계·서식·보고서 생성
│ ├─ mailer.py # SMTP 발송·IMAP 확인
│ ├─ scraper.py # 웹 요청/파싱 파이프라인
│ └─ main.py # 엔트리포인트(크롤→리포트→메일)
├─ .env # 비밀키·계정 (커밋 금지)
├─ requirements.txt # 의존성 목록
└─ README.md # 사용법/유지보수 메모
TZ=Asia/Seoul
# SMTP
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@example.com
SMTP_PASS=app_password
# IMAP
IMAP_HOST=imap.gmail.com
IMAP_USER=you@example.com
IMAP_PASS=app_password
.env*
를 반드시 등록하세요.pandas>=2.2
openpyxl>=3.1
requests>=2.32
beautifulsoup4>=4.12
lxml>=5.0
selenium>=4.22
python-dotenv>=1.0
loguru>=0.7
.venv/
.env*
__pycache__/
logs/*.log
reports/*.xlsx
*.pyc
- 가상환경 경로가 작업 스케줄러/크론과 동일한지 확인
- 로그 회전(log rotation) 설정으로 logs/ 용량 관리
- 민감정보는 코드 하드코딩 금지 → .env/Keychain 사용
2. 엑셀 자동화: 데이터 정리·리포트 생성
핵심은 정형화된 입력 → 정제 → 피벗/집계 → 서식 포함 출력입니다. 원본을 그대로 믿지 말고, 결측치·중복·타입을 먼저 정리하세요. 아래는 라이브러리 선택 가이드입니다.
라이브러리 | 주요 역할 | 장점/적합 시나리오 |
---|---|---|
pandas | 데이터 처리/집계 | 대량 처리·피벗·그룹화가 많을 때 |
openpyxl | 엑셀 서식/차트/수식 | 서식 있는 보고서(.xlsx) 출력 |
import pandas as pd
df = pd.read_excel("raw.xlsx")
df["매출"] = pd.to_numeric(df["매출"], errors="coerce").fillna(0)
df = df.drop_duplicates().fillna({"지역":"미기입"})
pivot = (df.groupby(["지역","상품"])
.agg(수량=("수량","sum"), 매출=("매출","sum"))
.reset_index()
.sort_values(["지역","매출"], ascending=[True,False]))
with pd.ExcelWriter("리포트.xlsx", engine="openpyxl") as writer:
pivot.to_excel(writer, index=False, sheet_name="요약")
3. 이메일 자동화: 대량 발송·수신 확인
SMTP로 보고서를 일괄 발송하고, IMAP으로 수신함을 점검하여 반송/문의 메일을 분류합니다. 회사 보안정책에 따라 앱 비밀번호 또는 사내 SMTP를 사용하세요.
- 제목/본문 템플릿에
{이름}, {월}
같은 플레이스홀더 사용 - 첨부파일(리포트.xlsx) 자동 첨부 및 MIME 타입 지정
import smtplib, ssl, imaplib, email
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
SMTP_HOST, SMTP_PORT = "smtp.gmail.com", 587
USER, PASS = "you@example.com", "app_password" # 보안: 환경변수로 관리 권장
def send_report(to_addr):
msg = MIMEMultipart()
msg["From"], msg["To"] = USER, to_addr
msg["Subject"] = "월간 매출 리포트"
msg.attach(MIMEText("안녕하세요,\n월간 리포트를 첨부드립니다.", "plain", "utf-8"))
with open("리포트.xlsx","rb") as f:
part = MIMEBase("application","octet-stream")
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header("Content-Disposition", "attachment; filename*=UTF-8''리포트.xlsx")
msg.attach(part)
context = ssl.create_default_context()
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as s:
s.starttls(context=context)
s.login(USER, PASS)
s.send_message(msg)
# 수신함 확인(예: 제목에 '반송' 포함 메일 카운트)
def count_bounce():
imap = imaplib.IMAP4_SSL("imap.gmail.com")
imap.login(USER, PASS)
imap.select("INBOX")
status, data = imap.search(None, '(SUBJECT "반송")')
ids = data[0].split()
imap.logout()
return len(ids)
4. 웹 크롤링: 데이터 수집 파이프라인
정적 페이지는 requests + BeautifulSoup, 동적 로딩은 Selenium으로 처리합니다. robots.txt, 서비스 약관, 요청 간 지연(sleep) 등 윤리적 크롤링 원칙을 지키세요. 크롤링 결과는 CSV로 저장하고 앞선 엑셀 처리 단계에 합류시킵니다.
import time, csv, requests
from bs4 import BeautifulSoup
rows = []
for page in range(1, 4):
url = f"https://example.com/list?page={page}"
r = requests.get(url, timeout=10)
soup = BeautifulSoup(r.text, "lxml")
for item in soup.select(".card"):
title = item.select_one(".title").get_text(strip=True)
price = item.select_one(".price").get_text(strip=True)
rows.append([title, price])
time.sleep(1) # 예의 있는 지연
with open("crawl.csv","w",newline="",encoding="utf-8") as f:
csv.writer(f).writerows([["title","price"], *rows])
5. 워크플로 통합: 하루 10분 루틴
하나의 실행 스크립트에서 크롤링 → 엑셀 정리 → 이메일 발송 순서로 연결합니다. 에러는 로그 파일로 남기고, 실패 시 재시도 횟수를 제한하세요.
단계 | 작업 | 산출물 |
---|---|---|
1 | 크롤링 | crawl.csv |
2 | 정제·집계 | 리포트.xlsx |
3 | 메일 발송/로그 | send.log |
# main.py (의사코드)
# 1) 웹에서 데이터 수집 -> 2) 집계 리포트 생성 -> 3) 메일 발송
# 예외 발생 시 로그 기록 후 종료 코드로 신호
# main.py에서 크롤링 → 엑셀 생성 → 메일 발송을 순차 실행하고, 실패 시 로그 남기기/재시도 전략을 둡니다.
# src/config.py
from dotenv import load_dotenv; load_dotenv()
TARGETS = [
{"url": "https://example.com/board", "selector": "table.list"},
# 필요 시 추가
]
MAIL_SUBJECT = "[자동보고] 일일 지표 보고서"
MAIL_TEMPLATE = """
# 일일 자동 보고서
첨부된 엑셀 파일에서 상세 데이터를 확인하세요.
본 메일은 자동 발송되었습니다.
"""
# src/main.py
import logging, os
from src.scraper import fetch_table, polite_sleep
from src.excel_builder import save_report
from src.mailer import send_mail
from src.config import TARGETS, MAIL_SUBJECT, MAIL_TEMPLATE
os.makedirs("logs", exist_ok=True); os.makedirs("reports", exist_ok=True)
logging.basicConfig(
filename="logs/app.log",
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s"
)
logger = logging.getLogger(__name__)
def run_pipeline():
try:
tables = []
for t in TARGETS:
df = fetch_table(t["url"], t["selector"])
tables.append(df)
polite_sleep()
report_path = save_report(tables)
send_mail(MAIL_SUBJECT, MAIL_TEMPLATE, attachments=[report_path])
logger.info("SUCCESS: %s", report_path)
return 0
except Exception as e:
logger.exception("FAILED: %s", e)
return 1
if __name__ == "__main__":
raise SystemExit(run_pipeline())
6. 배포·스케줄링: Windows/Mac/Linux
가상환경과 의존성 파일(requirements.txt
)을 함께 배포하세요. OS별 스케줄링 포인트는 아래와 같습니다.
- Windows: 작업 스케줄러에서
python main.py
매일 09:00 실행 - macOS/Linux: 크론 예)
0 9 * * 1-5 /usr/bin/python /path/main.py >> /path/send.log 2>&1
- 로그/알림: 실패 시 종료코드 감지→슬랙/메일 알림 훅
Windows (작업 스케줄러)
:: schedule.bat
schtasks /Create /TN "Daily_Py_Auto" /TR "C:\Path\to\venv\python.exe C:\repo\run.py" /SC DAILY /ST 08:30 /F
Linux/macOS (cron)
# crontab -e
30 8 * * 1-5 /path/to/venv/bin/python /repo/run.py >> /repo/logs/cron.log 2>&1
추가예시1 - 크롤링 설계: 안정성, 차단 회피, 스케줄링
추가예시1 - 크롤링 설계: 안정성, 차단 회피, 스케줄링
- 반복/재시도: 지수형 백오프(1s→2s→4s), 실패 로그 저장, 마지막 시도 후 알림 메일
- 차단 회피: 합법적 범위의
wait_for_selector
·랜덤 지연·헤드리스/UA 순환. 서비스 약관과 robots.txt 준수 - 데이터 무결성: 스키마(컬럼명/타입) 고정, 수집 버전태깅(
YYYYMMDD_HHMM
), 해시로 중복 방지 - 스케줄링: 매일 08:30 실행, 실패 시 10분 간격 3회 재시도
# /src/scrape_daily.py
import asyncio, random, time, hashlib, csv
from pathlib import Path
from playwright.async_api import async_playwright
from datetime import datetime
RAW_DIR = Path("data/raw")
LOG_DIR = Path("logs")
RAW_DIR.mkdir(parents=True, exist_ok=True); LOG_DIR.mkdir(exist_ok=True)
def _stamp(prefix="collect"):
return f"{prefix}_{datetime.now().strftime('%Y%m%d_%H%M')}"
async def fetch_table(page):
await page.goto("https://example.com/login", timeout=60_000)
# ✅ 실제 타겟 페이지 규칙/약관 및 로봇 배제 표준 준수 필요
await page.fill("#id", "YOUR_ID")
await page.fill("#pw", "YOUR_PW")
await page.click("button[type=submit]")
await page.wait_for_url("**/dashboard", timeout=60_000)
await page.goto("https://example.com/report")
await page.wait_for_selector("table.report-table")
rows = await page.locator("table.report-table tbody tr").all()
data = []
for r in rows:
tds = await r.locator("td").all_inner_texts()
data.append([t.strip() for t in tds])
return data
async def run_scraper():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(user_agent="Mozilla/5.0 auto-report")
page = await context.new_page()
data = await fetch_table(page)
await browser.close()
return data
def save_csv(rows):
stamp = _stamp("raw")
fp = RAW_DIR / f"{stamp}.csv"
with fp.open("w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["날짜", "카테고리", "수량", "금액"])
w.writerows(rows)
return fp
def scrape_with_retry(max_retry=3):
for i in range(max_retry):
try:
rows = asyncio.run(run_scraper())
fp = save_csv(rows)
return fp
except Exception as e:
(LOG_DIR / "scrape_error.log").write_text(f"{datetime.now()} :: {e}\n", encoding="utf-8")
if i == max_retry - 1:
raise
time.sleep(2 ** i + random.random()) # 1→2→4 sec
7. 자주 묻는 질문 (FAQ)
robots.txt, 서비스 약관, 개인정보 처리 방침을 확인하고 요청 빈도 조절·적절한 헤더 설정·로그인 필요 영역 접근 금지 등 기본 원칙을 지키면 위험을 크게 줄일 수 있습니다.
사내 SMTP 릴레이 주소·포트·인증 방식을 IT팀에 확인하세요. 외부 메일 사용 시 앱 비밀번호 또는 IP 허용 목록 등록이 필요할 수 있습니다.
CSV로 중간 저장하고, 필요한 컬럼만 로딩(usecols
)·명시적 dtype
설정·청크 처리(chunksize
)로 메모리를 절약하세요.
네. 브라우저 개발자도구의 네트워크 탭에서 호출되는 JSON API가 있으면 requests
로 직접 호출하는 편이 더 가볍고 안정적입니다.
지수 백오프(예: 1초→2초→4초), 최대 시도 횟수 제한, 실패 로그를 적용하고 임계 실패 시 관리자 알림을 발송하세요.
8. 마무리 요약
✅ 엑셀·이메일·크롤링을 하나로 묶으면 ‘업무의 흐름’이 자동화됩니다
정제→집계→보고서→발송의 풀라인을 스크립트로 연결하면 반복 업무가 재현 가능하고 예측 가능한 시스템으로 변합니다. 작은 자동화부터 시작해 크론/작업 스케줄러로 일상에 심어보세요. 야근이 줄어드는 순간, 자동화의 진가를 체감하게 됩니다.
'SW프로그래밍 개발 > Python' 카테고리의 다른 글
파이썬으로 보안 솔루션 엑셀 리포팅 툴 만들기(보고서 자동화) (3) | 2025.07.28 |
---|---|
[파이썬]FastAPI로 초간단 REST API 서버 만들기 – 실습 중심 (3) | 2025.06.14 |
실시간 OpenAPI 데이터를 머신러닝으로 분석하는 자동화 워크플로우 (1) | 2025.04.11 |