파이썬으로 데이터 파싱해서 엑셀로 저장하기 완벽 가이드

웹사이트에서 데이터를 수집하거나 API로 받은 데이터를 엑셀로 저장하고 싶으신가요? 파이썬을 사용하면 JSON, CSV, HTML, API 응답 등 어떤 형태의 데이터든 쉽게 파싱하고 엑셀 파일로 변환할 수 있습니다. 오늘은 실무에서 바로 쓸 수 있는 다양한 파싱 방법과 엑셀 저장 기법을 알려드립니다.

Table of Contents

필요한 라이브러리 설치하기

엑셀 작업에 가장 많이 사용되는 라이브러리는 다음과 같습니다.

# 엑셀 작업을 위한 필수 라이브러리
pip install pandas openpyxl

# 웹 크롤링/파싱용
pip install requests beautifulsoup4

# API 작업용 (JSON 파싱)
pip install requests

# HTML 테이블 파싱
pip install lxml

라이브러리 선택 가이드:

  • pandas + openpyxl: 가장 추천! 데이터 분석 + 엑셀 저장 모두 가능
  • xlsxwriter: 엑셀 서식 꾸미기에 특화
  • openpyxl: 기존 엑셀 파일 수정 시 유용

기본: 딕셔너리 데이터를 엑셀로 저장하기

가장 기본적인 방법부터 시작해봅시다.

import pandas as pd

# 간단한 데이터
data = {
    '이름': ['김철수', '이영희', '박민수'],
    '나이': [25, 30, 28],
    '직업': ['개발자', '디자이너', '기획자']
}

# 데이터프레임 생성
df = pd.DataFrame(data)

# 엑셀로 저장
df.to_excel('output.xlsx', index=False, engine='openpyxl')

print("엑셀 파일이 저장되었습니다!")

index=False는 행 번호를 제외하고 저장하는 옵션입니다.

JSON 데이터 파싱해서 엑셀로 저장하기

API에서 받은 JSON 데이터를 엑셀로 변환하는 방법입니다.

방법 1: 간단한 JSON

import pandas as pd
import json

# JSON 문자열
json_data = '''
[
    {"name": "김철수", "age": 25, "city": "서울"},
    {"name": "이영희", "age": 30, "city": "부산"},
    {"name": "박민수", "age": 28, "city": "대구"}
]
'''

# JSON 파싱
data = json.loads(json_data)

# 데이터프레임으로 변환
df = pd.DataFrame(data)

# 엑셀 저장
df.to_excel('users.xlsx', index=False)

방법 2: JSON 파일 읽기

import pandas as pd

# JSON 파일에서 바로 읽기
df = pd.read_json('data.json')

# 엑셀로 저장
df.to_excel('output.xlsx', index=False)

방법 3: 중첩된 JSON 데이터

import pandas as pd
import json

json_data = '''
{
    "users": [
        {
            "name": "김철수",
            "age": 25,
            "address": {
                "city": "서울",
                "district": "강남구"
            }
        },
        {
            "name": "이영희",
            "age": 30,
            "address": {
                "city": "부산",
                "district": "해운대구"
            }
        }
    ]
}
'''

data = json.loads(json_data)

# 중첩 데이터 평탄화
users_data = []
for user in data['users']:
    users_data.append({
        '이름': user['name'],
        '나이': user['age'],
        '도시': user['address']['city'],
        '구': user['address']['district']
    })

df = pd.DataFrame(users_data)
df.to_excel('users_flattened.xlsx', index=False)

API 응답 데이터를 엑셀로 저장하기

실제 API에서 데이터를 받아 엑셀로 저장하는 예제입니다.

공공 API 활용 예제

import requests
import pandas as pd

# 공공데이터 API 호출 (예시)
url = "<https://api.example.com/data>"
params = {
    'serviceKey': 'your_api_key',
    'numOfRows': 100,
    'pageNo': 1
}

response = requests.get(url, params=params)

# JSON 응답 파싱
if response.status_code == 200:
    data = response.json()

    # 데이터 추출 (API 구조에 따라 다름)
    items = data['response']['body']['items']['item']

    # 데이터프레임 변환
    df = pd.DataFrame(items)

    # 엑셀 저장
    df.to_excel('api_data.xlsx', index=False)
    print("API 데이터를 엑셀로 저장했습니다!")
else:
    print(f"API 호출 실패: {response.status_code}")

REST API 예제 (실무용)

import requests
import pandas as pd
from datetime import datetime

def fetch_and_save_to_excel(api_url, output_filename):
    """
    API에서 데이터를 가져와 엑셀로 저장
    """
    try:
        # API 호출
        response = requests.get(api_url, timeout=10)
        response.raise_for_status()

        # JSON 파싱
        data = response.json()

        # 데이터프레임 생성
        df = pd.DataFrame(data)

        # 현재 시간을 파일명에 추가
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f"{output_filename}_{timestamp}.xlsx"

        # 엑셀 저장
        df.to_excel(filename, index=False)

        print(f"✅ 성공: {len(df)}개 데이터를 {filename}에 저장했습니다.")
        return True

    except requests.exceptions.RequestException as e:
        print(f"❌ API 호출 오류: {e}")
        return False
    except Exception as e:
        print(f"❌ 저장 오류: {e}")
        return False

# 사용 예시
api_url = "<https://jsonplaceholder.typicode.com/posts>"
fetch_and_save_to_excel(api_url, "posts_data")

웹 크롤링해서 엑셀로 저장하기

웹사이트의 데이터를 크롤링하여 엑셀로 저장하는 방법입니다.

HTML 테이블 파싱

import pandas as pd
from bs4 import BeautifulSoup
import requests

# 웹페이지 가져오기
url = "<https://example.com/table-page>"
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')

# 테이블 찾기
table = soup.find('table')

# 헤더 추출
headers = []
for th in table.find_all('th'):
    headers.append(th.text.strip())

# 데이터 추출
rows = []
for tr in table.find_all('tr')[1:]:  # 첫 번째 행(헤더) 제외
    row = []
    for td in tr.find_all('td'):
        row.append(td.text.strip())
    rows.append(row)

# 데이터프레임 생성
df = pd.DataFrame(rows, columns=headers)

# 엑셀 저장
df.to_excel('crawled_data.xlsx', index=False)

pandas로 HTML 테이블 직접 읽기

pandas는 HTML 테이블을 자동으로 파싱할 수 있습니다.

import pandas as pd

# HTML 테이블을 직접 읽기
url = "<https://example.com/table-page>"
tables = pd.read_html(url)

# 첫 번째 테이블 선택
df = tables[0]

# 엑셀 저장
df.to_excel('table_data.xlsx', index=False)

여러 페이지 크롤링

import pandas as pd
import requests
from bs4 import BeautifulSoup
import time

all_data = []

for page in range(1, 6):  # 1~5페이지
    url = f"<https://example.com/list?page={page}>"
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')

    # 데이터 추출 (실제 HTML 구조에 맞게 수정 필요)
    items = soup.find_all('div', class_='item')

    for item in items:
        title = item.find('h2').text.strip()
        price = item.find('span', class_='price').text.strip()

        all_data.append({
            '제목': title,
            '가격': price
        })

    # 서버 부하 방지
    time.sleep(1)
    print(f"{page}페이지 크롤링 완료")

# 데이터프레임 생성 및 저장
df = pd.DataFrame(all_data)
df.to_excel('multi_page_data.xlsx', index=False)
print(f"총 {len(df)}개 항목을 저장했습니다!")

CSV 파일을 엑셀로 변환하기

CSV 파일을 읽어서 엑셀로 변환하는 가장 간단한 방법입니다.

기본 변환

import pandas as pd

# CSV 읽기
df = pd.read_csv('data.csv', encoding='utf-8')

# 엑셀로 저장
df.to_excel('data.xlsx', index=False)

여러 CSV 파일을 하나의 엑셀로 합치기

import pandas as pd
import glob

# 모든 CSV 파일 찾기
csv_files = glob.glob('*.csv')

# 엑셀 작성기 생성
with pd.ExcelWriter('combined.xlsx', engine='openpyxl') as writer:
    for csv_file in csv_files:
        # CSV 읽기
        df = pd.read_csv(csv_file)

        # 시트 이름은 파일명 (확장자 제거)
        sheet_name = csv_file.replace('.csv', '')

        # 엑셀 시트에 쓰기
        df.to_excel(writer, sheet_name=sheet_name, index=False)

print(f"{len(csv_files)}개 CSV 파일을 하나의 엑셀로 합쳤습니다!")

데이터베이스 쿼리 결과를 엑셀로 저장하기

데이터베이스에서 데이터를 가져와 엑셀로 저장하는 방법입니다.

SQLite 예제

import pandas as pd
import sqlite3

# 데이터베이스 연결
conn = sqlite3.connect('database.db')

# SQL 쿼리 실행
query = "SELECT * FROM users WHERE age >= 20"
df = pd.read_sql_query(query, conn)

# 엑셀 저장
df.to_excel('users_from_db.xlsx', index=False)

# 연결 종료
conn.close()

MySQL 예제

import pandas as pd
import pymysql

# MySQL 연결
connection = pymysql.connect(
    host='localhost',
    user='username',
    password='password',
    database='mydb'
)

# 쿼리 실행
query = "SELECT * FROM products ORDER BY price DESC"
df = pd.read_sql(query, connection)

# 엑셀 저장
df.to_excel('products.xlsx', index=False)

# 연결 종료
connection.close()

엑셀 서식 꾸미기

단순히 데이터만 저장하는 것이 아니라 보기 좋게 꾸며봅시다.

기본 서식 적용

import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font, Alignment, PatternFill

# 데이터 생성 및 저장
data = {
    '이름': ['김철수', '이영희', '박민수'],
    '점수': [85, 92, 78],
    '등급': ['B', 'A', 'C']
}
df = pd.DataFrame(data)
df.to_excel('styled.xlsx', index=False)

# 엑셀 파일 열기
wb = load_workbook('styled.xlsx')
ws = wb.active

# 헤더 스타일 지정
header_fill = PatternFill(start_color='366092', end_color='366092', fill_type='solid')
header_font = Font(bold=True, color='FFFFFF', size=12)

for cell in ws[1]:
    cell.fill = header_fill
    cell.font = header_font
    cell.alignment = Alignment(horizontal='center')

# 열 너비 자동 조정
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)))

    ws.column_dimensions[column_letter].width = max_length + 2

# 저장
wb.save('styled.xlsx')
print("서식이 적용된 엑셀 파일이 저장되었습니다!")

ExcelWriter로 고급 서식 적용

import pandas as pd

# 데이터 준비
data = {
    '제품명': ['노트북', '마우스', '키보드'],
    '가격': [1500000, 30000, 80000],
    '재고': [10, 50, 30]
}
df = pd.DataFrame(data)

# ExcelWriter로 저장
with pd.ExcelWriter('formatted.xlsx', engine='openpyxl') as writer:
    df.to_excel(writer, sheet_name='제품목록', index=False)

    # 워크시트 가져오기
    workbook = writer.book
    worksheet = writer.sheets['제품목록']

    # 헤더 스타일
    from openpyxl.styles import Font, PatternFill

    for cell in worksheet[1]:
        cell.font = Font(bold=True, size=12)
        cell.fill = PatternFill(start_color='DDEBF7', end_color='DDEBF7', fill_type='solid')

    # 숫자 포맷 (가격 컬럼)
    for row in range(2, len(df) + 2):
        cell = worksheet.cell(row=row, column=2)
        cell.number_format = '#,##0'

print("고급 서식이 적용되었습니다!")

여러 시트를 가진 엑셀 만들기

하나의 엑셀 파일에 여러 시트를 만드는 방법입니다.

import pandas as pd

# 여러 데이터프레임 준비
sales_data = pd.DataFrame({
    '날짜': ['2025-01-01', '2025-01-02', '2025-01-03'],
    '매출': [1000000, 1200000, 950000]
})

product_data = pd.DataFrame({
    '제품': ['A', 'B', 'C'],
    '재고': [100, 50, 75]
})

customer_data = pd.DataFrame({
    '고객명': ['김철수', '이영희', '박민수'],
    '구매액': [500000, 300000, 700000]
})

# ExcelWriter로 여러 시트 작성
with pd.ExcelWriter('multi_sheet.xlsx', engine='openpyxl') as writer:
    sales_data.to_excel(writer, sheet_name='매출', index=False)
    product_data.to_excel(writer, sheet_name='제품', index=False)
    customer_data.to_excel(writer, sheet_name='고객', index=False)

print("3개 시트가 포함된 엑셀이 생성되었습니다!")

실시간 데이터 수집 및 엑셀 저장 자동화

정기적으로 데이터를 수집하고 엑셀로 저장하는 스크립트입니다.

import pandas as pd
import requests
import schedule
import time
from datetime import datetime

def collect_and_save_data():
    """
    데이터를 수집하고 엑셀로 저장하는 함수
    """
    print(f"[{datetime.now()}] 데이터 수집 시작...")

    try:
        # API에서 데이터 가져오기
        response = requests.get('<https://api.example.com/data>')
        data = response.json()

        # 데이터프레임 생성
        df = pd.DataFrame(data)

        # 타임스탬프를 포함한 파일명
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f'data_{timestamp}.xlsx'

        # 엑셀 저장
        df.to_excel(filename, index=False)

        print(f"✅ 저장 완료: {filename} ({len(df)}개 레코드)")

    except Exception as e:
        print(f"❌ 오류 발생: {e}")

# 스케줄 설정
schedule.every(1).hours.do(collect_and_save_data)  # 1시간마다
# schedule.every().day.at("09:00").do(collect_and_save_data)  # 매일 오전 9시

print("데이터 수집 스케줄러가 시작되었습니다.")
print("프로그램을 종료하려면 Ctrl+C를 누르세요.")

# 스케줄 실행
while True:
    schedule.run_pending()
    time.sleep(60)  # 1분마다 체크

대용량 데이터 처리하기

수만 개 이상의 데이터를 효율적으로 처리하는 방법입니다.

청크 단위로 처리

import pandas as pd

# 대용량 CSV를 청크로 읽어 엑셀로 저장
chunk_size = 10000
chunks = []

for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    # 필요한 전처리
    chunk = chunk[chunk['가격'] > 10000]  # 필터링 예시
    chunks.append(chunk)

# 모든 청크 합치기
df = pd.concat(chunks, ignore_index=True)

# 엑셀 저장 (최대 100만 행 권장)
if len(df) <= 1000000:
    df.to_excel('filtered_data.xlsx', index=False)
else:
    print("데이터가 너무 많습니다. 여러 파일로 나누어 저장합니다.")
    # 여러 파일로 분할 저장
    num_files = (len(df) // 1000000) + 1
    for i in range(num_files):
        start = i * 1000000
        end = (i + 1) * 1000000
        df[start:end].to_excel(f'data_part_{i+1}.xlsx', index=False)

메모리 효율적인 처리

import pandas as pd

# 필요한 컬럼만 읽기
df = pd.read_csv('large_file.csv', usecols=['이름', '나이', '직업'])

# 데이터 타입 최적화
df['나이'] = df['나이'].astype('int8')  # int64 대신 int8

# 엑셀 저장
df.to_excel('optimized.xlsx', index=False)

에러 처리 및 로깅

안정적인 파싱 프로그램을 위한 에러 처리 방법입니다.

import pandas as pd
import requests
import logging
from datetime import datetime

# 로깅 설정
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('parsing.log'),
        logging.StreamHandler()
    ]
)

def safe_parse_and_save(url, output_file):
    """
    안전한 파싱 및 저장 함수
    """
    try:
        logging.info(f"데이터 수집 시작: {url}")

        # API 호출
        response = requests.get(url, timeout=10)
        response.raise_for_status()

        # JSON 파싱
        data = response.json()

        # 데이터 검증
        if not data or len(data) == 0:
            logging.warning("데이터가 비어있습니다.")
            return False

        # 데이터프레임 생성
        df = pd.DataFrame(data)

        # 엑셀 저장
        df.to_excel(output_file, index=False)

        logging.info(f"✅ 성공: {len(df)}개 레코드를 {output_file}에 저장")
        return True

    except requests.exceptions.Timeout:
        logging.error("❌ 타임아웃: 서버 응답이 없습니다.")
    except requests.exceptions.HTTPError as e:
        logging.error(f"❌ HTTP 오류: {e}")
    except ValueError as e:
        logging.error(f"❌ JSON 파싱 오류: {e}")
    except Exception as e:
        logging.error(f"❌ 알 수 없는 오류: {e}")

    return False

# 사용 예시
url = "<https://api.example.com/data>"
output_file = f"data_{datetime.now().strftime('%Y%m%d')}.xlsx"
safe_parse_and_save(url, output_file)

실전 프로젝트: 뉴스 기사 크롤러

완전한 예제 프로젝트를 만들어봅시다.

import requests
from bs4 import BeautifulSoup
import pandas as pd
from datetime import datetime
import time

class NewsCrawler:
    def __init__(self):
        self.articles = []

    def crawl_news(self, keyword, num_pages=3):
        """
        네이버 뉴스 검색 결과 크롤링 (예시)
        """
        print(f"'{keyword}' 키워드로 뉴스 검색 중...")

        for page in range(1, num_pages + 1):
            # 실제 사용 시 URL 수정 필요
            url = f"<https://search.naver.com/search.naver?where=news&query={keyword}&start={page}>"

            try:
                response = requests.get(url, headers={
                    'User-Agent': 'Mozilla/5.0'
                })
                soup = BeautifulSoup(response.content, 'html.parser')

                # 기사 목록 추출 (실제 HTML 구조에 맞게 수정 필요)
                news_items = soup.find_all('div', class_='news_area')

                for item in news_items:
                    try:
                        title = item.find('a', class_='news_tit').text.strip()
                        link = item.find('a', class_='news_tit')['href']
                        description = item.find('div', class_='news_dsc').text.strip()

                        self.articles.append({
                            '제목': title,
                            '링크': link,
                            '설명': description,
                            '수집시간': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                        })
                    except AttributeError:
                        continue

                print(f"{page}페이지 크롤링 완료")
                time.sleep(1)  # 서버 부하 방지

            except Exception as e:
                print(f"페이지 {page} 크롤링 실패: {e}")

    def save_to_excel(self, filename):
        """
        수집한 기사를 엑셀로 저장
        """
        if not self.articles:
            print("저장할 데이터가 없습니다.")
            return

        df = pd.DataFrame(self.articles)

        # 엑셀 저장
        with pd.ExcelWriter(filename, engine='openpyxl') as writer:
            df.to_excel(writer, sheet_name='뉴스목록', index=False)

            # 워크시트 스타일링
            workbook = writer.book
            worksheet = writer.sheets['뉴스목록']

            from openpyxl.styles import Font, Alignment, PatternFill

            # 헤더 스타일
            for cell in worksheet[1]:
                cell.font = Font(bold=True, size=11)
                cell.fill = PatternFill(start_color='4472C4',
                                       end_color='4472C4',
                                       fill_type='solid')
                cell.alignment = Alignment(horizontal='center')

            # 열 너비 조정
            worksheet.column_dimensions['A'].width = 60  # 제목
            worksheet.column_dimensions['B'].width = 80  # 링크
            worksheet.column_dimensions['C'].width = 100  # 설명
            worksheet.column_dimensions['D'].width = 20  # 수집시간

        print(f"✅ {len(self.articles)}개 기사를 {filename}에 저장했습니다!")

# 사용 예시
if __name__ == "__main__":
    crawler = NewsCrawler()
    crawler.crawl_news('파이썬', num_pages=3)
    crawler.save_to_excel(f'news_{datetime.now().strftime("%Y%m%d")}.xlsx')

자주 발생하는 문제와 해결법

문제 1: 한글 깨짐

# 해결 방법 1: 인코딩 명시
df = pd.read_csv('data.csv', encoding='cp949')  # 또는 'euc-kr'

# 해결 방법 2: 엑셀 저장 시 엔진 지정
df.to_excel('output.xlsx', index=False, engine='openpyxl')

문제 2: 엑셀 행 제한 초과

엑셀은 최대 1,048,576행까지만 지원합니다.

# 해결: 여러 파일로 나누기
chunk_size = 1000000

for i in range(0, len(df), chunk_size):
    chunk = df[i:i+chunk_size]
    chunk.to_excel(f'data_part_{i//chunk_size + 1}.xlsx', index=False)

문제 3: 메모리 부족

# 해결: 청크로 읽기
chunks = []
for chunk in pd.read_csv('large.csv', chunksize=10000):
    # 필터링 또는 처리
    chunks.append(chunk)

df = pd.concat(chunks, ignore_index=True)

문제 4: 날짜 형식 문제

# 해결: 날짜 파싱 지정
df = pd.read_csv('data.csv', parse_dates=['날짜컬럼'])

# 또는 변환
df['날짜'] = pd.to_datetime(df['날짜'], format='%Y-%m-%d')

문제 5: 특수문자로 인한 오류

# 해결: 특수문자 제거 또는 대체
df['제목'] = df['제목'].str.replace('[^\\w\\s]', '', regex=True)

# 또는 시트명에서 제거 (엑셀 시트명은 특수문자 제한)
sheet_name = '데이터/결과'.replace('/', '_')

성능 최적화 팁

대량의 데이터를 빠르게 처리하는 방법입니다.

1. 데이터 타입 최적화

import pandas as pd

# 기본 (메모리 많이 사용)
df = pd.read_csv('data.csv')

# 최적화 (메모리 절약)
df = pd.read_csv('data.csv', dtype={
    '이름': 'category',  # 중복 값이 많은 컬럼
    '나이': 'int8',      # 작은 범위의 정수
    '점수': 'float32'    # float64 대신
})

print(f"메모리 사용량: {df.memory_usage(deep=True).sum() / 1024 / 1024:.2f} MB")

2. 병렬 처리

import pandas as pd
from multiprocessing import Pool
import glob

def process_file(filename):
    """각 파일을 처리하는 함수"""
    df = pd.read_csv(filename)
    # 데이터 처리
    df = df[df['금액'] > 10000]
    return df

# 여러 파일을 병렬로 처리
if __name__ == '__main__':
    files = glob.glob('data_*.csv')

    with Pool(processes=4) as pool:
        results = pool.map(process_file, files)

    # 결과 합치기
    final_df = pd.concat(results, ignore_index=True)
    final_df.to_excel('combined_result.xlsx', index=False)

3. 캐싱 활용

import pandas as pd
import os
from datetime import datetime, timedelta

def get_cached_data(cache_file, fetch_function, max_age_hours=1):
    """
    캐시된 데이터를 사용하거나 새로 가져오기
    """
    # 캐시 파일이 존재하고 유효한지 확인
    if os.path.exists(cache_file):
        file_time = datetime.fromtimestamp(os.path.getmtime(cache_file))
        if datetime.now() - file_time < timedelta(hours=max_age_hours):
            print("캐시된 데이터 사용")
            return pd.read_excel(cache_file)

    # 새로운 데이터 가져오기
    print("새 데이터 가져오기")
    df = fetch_function()
    df.to_excel(cache_file, index=False)
    return df

# 사용 예시
def fetch_api_data():
    import requests
    response = requests.get('<https://api.example.com/data>')
    return pd.DataFrame(response.json())

df = get_cached_data('cache.xlsx', fetch_api_data, max_age_hours=1)

고급 기능: 차트가 포함된 엑셀 만들기

데이터와 함께 차트도 자동 생성하는 방법입니다.

import pandas as pd
from openpyxl import load_workbook
from openpyxl.chart import BarChart, LineChart, Reference

# 데이터 생성
data = {
    '월': ['1월', '2월', '3월', '4월', '5월', '6월'],
    '매출': [1000, 1200, 1100, 1400, 1600, 1500],
    '비용': [800, 850, 900, 950, 1000, 1050]
}
df = pd.DataFrame(data)

# 엑셀로 저장
df.to_excel('sales_report.xlsx', index=False, sheet_name='월별실적')

# 워크북 열기
wb = load_workbook('sales_report.xlsx')
ws = wb['월별실적']

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

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

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

# 차트를 시트에 추가
ws.add_chart(chart, "E2")

# 저장
wb.save('sales_report.xlsx')
print("차트가 포함된 엑셀이 생성되었습니다!")

여러 차트 추가하기

import pandas as pd
from openpyxl import load_workbook
from openpyxl.chart import BarChart, LineChart, PieChart, Reference

# 데이터 준비
data = {
    '분기': ['1Q', '2Q', '3Q', '4Q'],
    '매출': [2500, 3000, 3200, 3500],
    '영업이익': [500, 600, 700, 750]
}
df = pd.DataFrame(data)
df.to_excel('quarterly_report.xlsx', index=False)

wb = load_workbook('quarterly_report.xlsx')
ws = wb.active

# 1. 막대 차트 (매출)
bar_chart = BarChart()
bar_chart.title = "분기별 매출"
bar_chart.x_axis.title = "분기"
bar_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)
bar_chart.add_data(data_ref, titles_from_data=True)
bar_chart.set_categories(categories)
ws.add_chart(bar_chart, "E2")

# 2. 선 차트 (영업이익)
line_chart = LineChart()
line_chart.title = "영업이익 추이"
line_chart.x_axis.title = "분기"
line_chart.y_axis.title = "영업이익 (백만원)"

data_ref = Reference(ws, min_col=3, min_row=1, max_row=5)
line_chart.add_data(data_ref, titles_from_data=True)
line_chart.set_categories(categories)
ws.add_chart(line_chart, "M2")

wb.save('quarterly_report.xlsx')
print("여러 차트가 포함된 보고서가 생성되었습니다!")

실무 템플릿: API 데이터 모니터링 시스템

실시간으로 API를 모니터링하고 엑셀로 기록하는 시스템입니다.

import pandas as pd
import requests
from datetime import datetime
import time
import os

class APIMonitor:
    def __init__(self, api_url, output_dir='monitoring'):
        self.api_url = api_url
        self.output_dir = output_dir

        # 출력 디렉토리 생성
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)

    def fetch_data(self):
        """API에서 데이터 가져오기"""
        try:
            response = requests.get(self.api_url, timeout=10)
            response.raise_for_status()
            return response.json(), True
        except Exception as e:
            print(f"❌ API 호출 실패: {e}")
            return None, False

    def append_to_excel(self, data, date_str):
        """데이터를 엑셀에 추가"""
        filename = os.path.join(self.output_dir, f'monitoring_{date_str}.xlsx')

        # 현재 시간 추가
        data['수집시간'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

        # 새 데이터프레임
        new_df = pd.DataFrame([data])

        # 기존 파일이 있으면 추가, 없으면 생성
        if os.path.exists(filename):
            existing_df = pd.read_excel(filename)
            combined_df = pd.concat([existing_df, new_df], ignore_index=True)
        else:
            combined_df = new_df

        # 저장
        combined_df.to_excel(filename, index=False)
        print(f"✅ 데이터 저장: {filename}")

    def run_monitoring(self, interval_seconds=300):
        """
        주기적으로 모니터링 실행
        interval_seconds: 수집 간격 (초)
        """
        print(f"모니터링 시작: {self.api_url}")
        print(f"수집 간격: {interval_seconds}초")

        while True:
            try:
                data, success = self.fetch_data()

                if success and data:
                    date_str = datetime.now().strftime('%Y%m%d')
                    self.append_to_excel(data, date_str)
                    print(f"[{datetime.now()}] 데이터 수집 완료")

                time.sleep(interval_seconds)

            except KeyboardInterrupt:
                print("\\n모니터링을 종료합니다.")
                break
            except Exception as e:
                print(f"❌ 오류 발생: {e}")
                time.sleep(interval_seconds)

# 사용 예시
if __name__ == "__main__":
    api_url = "<https://api.example.com/status>"
    monitor = APIMonitor(api_url)

    # 5분마다 모니터링
    monitor.run_monitoring(interval_seconds=300)

데이터 검증 및 정제

파싱한 데이터를 정제하고 검증하는 방법입니다.

import pandas as pd
import re

def clean_data(df):
    """
    데이터 정제 함수
    """
    # 1. 중복 제거
    df = df.drop_duplicates()

    # 2. 결측치 처리
    df = df.fillna({
        '이름': '미상',
        '나이': 0,
        '가격': 0
    })

    # 3. 공백 제거
    for col in df.select_dtypes(include=['object']).columns:
        df[col] = df[col].str.strip()

    # 4. 이상치 제거 (나이가 150 이상인 데이터)
    if '나이' in df.columns:
        df = df[df['나이'] < 150]

    # 5. 전화번호 포맷 통일
    if '전화번호' in df.columns:
        df['전화번호'] = df['전화번호'].apply(format_phone)

    # 6. 날짜 형식 통일
    if '날짜' in df.columns:
        df['날짜'] = pd.to_datetime(df['날짜'], errors='coerce')

    return df

def format_phone(phone):
    """전화번호 포맷 정리"""
    if pd.isna(phone):
        return ''

    # 숫자만 추출
    phone = re.sub(r'[^0-9]', '', str(phone))

    # 형식 적용
    if len(phone) == 11:
        return f"{phone[:3]}-{phone[3:7]}-{phone[7:]}"
    elif len(phone) == 10:
        return f"{phone[:3]}-{phone[3:6]}-{phone[6:]}"
    else:
        return phone

# 사용 예시
raw_df = pd.read_csv('raw_data.csv')
cleaned_df = clean_data(raw_df)
cleaned_df.to_excel('cleaned_data.xlsx', index=False)

데이터 검증 리포트 생성

import pandas as pd
from datetime import datetime

def create_validation_report(df, output_file):
    """
    데이터 검증 리포트를 엑셀로 생성
    """
    report_data = {
        '항목': [],
        '결과': [],
        '상세': []
    }

    # 1. 전체 행 수
    report_data['항목'].append('총 레코드 수')
    report_data['결과'].append(len(df))
    report_data['상세'].append('-')

    # 2. 중복 행 수
    duplicates = df.duplicated().sum()
    report_data['항목'].append('중복 행 수')
    report_data['결과'].append(duplicates)
    report_data['상세'].append('중복 제거 권장' if duplicates > 0 else '정상')

    # 3. 결측치 확인
    null_counts = df.isnull().sum()
    for col, count in null_counts.items():
        if count > 0:
            report_data['항목'].append(f'{col} 결측치')
            report_data['결과'].append(count)
            report_data['상세'].append(f'전체의 {count/len(df)*100:.1f}%')

    # 4. 데이터 타입 확인
    for col in df.columns:
        report_data['항목'].append(f'{col} 타입')
        report_data['결과'].append(str(df[col].dtype))
        report_data['상세'].append('-')

    # 리포트 데이터프레임 생성
    report_df = pd.DataFrame(report_data)

    # 엑셀로 저장 (여러 시트)
    with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
        # 검증 리포트
        report_df.to_excel(writer, sheet_name='검증리포트', index=False)

        # 원본 데이터 샘플
        df.head(100).to_excel(writer, sheet_name='데이터샘플', index=False)

        # 통계 정보
        if len(df.select_dtypes(include=['number']).columns) > 0:
            df.describe().to_excel(writer, sheet_name='통계정보')

    print(f"검증 리포트가 생성되었습니다: {output_file}")

# 사용 예시
df = pd.read_csv('data.csv')
create_validation_report(df, 'validation_report.xlsx')

실전 예제: 쇼핑몰 상품 정보 수집기

완전한 실무 프로젝트 예제입니다.

import requests
from bs4 import BeautifulSoup
import pandas as pd
from datetime import datetime
import time
import logging

# 로깅 설정
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

class ProductScraper:
    def __init__(self):
        self.products = []
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }

    def scrape_page(self, url):
        """
        페이지에서 상품 정보 추출
        """
        try:
            response = requests.get(url, headers=self.headers, timeout=10)
            response.raise_for_status()

            soup = BeautifulSoup(response.content, 'html.parser')

            # 상품 목록 찾기 (실제 HTML 구조에 맞게 수정 필요)
            items = soup.find_all('div', class_='product-item')

            for item in items:
                try:
                    # 상품명
                    name_elem = item.find('h3', class_='product-name')
                    name = name_elem.text.strip() if name_elem else 'N/A'

                    # 가격
                    price_elem = item.find('span', class_='price')
                    price_text = price_elem.text.strip() if price_elem else '0'
                    price = int(price_text.replace(',', '').replace('원', ''))

                    # 할인율
                    discount_elem = item.find('span', class_='discount')
                    discount = discount_elem.text.strip() if discount_elem else '0%'

                    # 링크
                    link_elem = item.find('a')
                    link = link_elem['href'] if link_elem else ''

                    # 평점
                    rating_elem = item.find('span', class_='rating')
                    rating = float(rating_elem.text) if rating_elem else 0.0

                    self.products.append({
                        '상품명': name,
                        '가격': price,
                        '할인율': discount,
                        '평점': rating,
                        '링크': link,
                        '수집일시': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                    })

                except Exception as e:
                    logging.warning(f"상품 파싱 실패: {e}")
                    continue

            logging.info(f"페이지 크롤링 완료: {len(items)}개 상품")
            return True

        except Exception as e:
            logging.error(f"페이지 로드 실패: {e}")
            return False

    def scrape_multiple_pages(self, base_url, num_pages):
        """
        여러 페이지 크롤링
        """
        for page in range(1, num_pages + 1):
            url = f"{base_url}?page={page}"
            logging.info(f"페이지 {page}/{num_pages} 크롤링 중...")

            success = self.scrape_page(url)

            if not success:
                logging.warning(f"페이지 {page} 건너뛰기")

            # 서버 부하 방지
            time.sleep(2)

    def save_to_excel(self, filename):
        """
        엑셀로 저장 (다양한 시트 포함)
        """
        if not self.products:
            logging.warning("저장할 데이터가 없습니다.")
            return

        df = pd.DataFrame(self.products)

        with pd.ExcelWriter(filename, engine='openpyxl') as writer:
            # 1. 전체 상품 목록
            df.to_excel(writer, sheet_name='전체상품', index=False)

            # 2. 가격대별 분류
            df_sorted = df.sort_values('가격', ascending=False)
            df_sorted.to_excel(writer, sheet_name='가격순', index=False)

            # 3. 평점 높은 상품
            df_rating = df.sort_values('평점', ascending=False).head(50)
            df_rating.to_excel(writer, sheet_name='고평점상품', index=False)

            # 4. 통계 정보
            stats = pd.DataFrame({
                '항목': ['총 상품 수', '평균 가격', '최고가', '최저가', '평균 평점'],
                '값': [
                    len(df),
                    f"{df['가격'].mean():,.0f}원",
                    f"{df['가격'].max():,.0f}원",
                    f"{df['가격'].min():,.0f}원",
                    f"{df['평점'].mean():.2f}"
                ]
            })
            stats.to_excel(writer, sheet_name='통계', index=False)

            # 스타일 적용
            from openpyxl.styles import Font, PatternFill, Alignment

            for sheet_name in writer.sheets:
                worksheet = writer.sheets[sheet_name]

                # 헤더 스타일
                for cell in worksheet[1]:
                    cell.font = Font(bold=True, size=11, color='FFFFFF')
                    cell.fill = PatternFill(start_color='366092',
                                           end_color='366092',
                                           fill_type='solid')
                    cell.alignment = Alignment(horizontal='center')

                # 열 너비 자동 조정
                for column in worksheet.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)
                    worksheet.column_dimensions[column_letter].width = adjusted_width

        logging.info(f"✅ {len(df)}개 상품을 {filename}에 저장했습니다!")

    def export_summary(self):
        """
        수집 결과 요약
        """
        if not self.products:
            return

        df = pd.DataFrame(self.products)

        print("\\n" + "="*50)
        print("수집 결과 요약")
        print("="*50)
        print(f"총 상품 수: {len(df)}개")
        print(f"평균 가격: {df['가격'].mean():,.0f}원")
        print(f"가격 범위: {df['가격'].min():,}원 ~ {df['가격'].max():,}원")
        print(f"평균 평점: {df['평점'].mean():.2f}")
        print("="*50 + "\\n")

# 사용 예시
if __name__ == "__main__":
    scraper = ProductScraper()

    # 크롤링 실행
    base_url = "<https://example.com/products>"
    scraper.scrape_multiple_pages(base_url, num_pages=5)

    # 결과 요약
    scraper.export_summary()

    # 엑셀 저장
    filename = f'products_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx'
    scraper.save_to_excel(filename)

마치며

파이썬으로 데이터를 파싱하고 엑셀로 저장하는 것은 생각보다 간단합니다. 핵심 포인트를 정리하면:

기본 단계:

  1. 데이터 수집 (API, 웹 크롤링, CSV 등)
  2. pandas DataFrame으로 변환
  3. to_excel() 메서드로 저장

실무 팁:

  • 가상환경 사용: 프로젝트마다 독립된 환경 유지
  • 에러 처리: try-except로 안정성 확보
  • 로깅: 문제 추적을 위한 로그 기록
  • 데이터 검증: 저장 전 데이터 정제 및 검증
  • 스케줄링: 정기적 수집은 schedule 라이브러리 활용

이제 여러분도 데이터 파싱 전문가입니다! 궁금한 점이 있다면 댓글로 질문해주세요.

참고 자료

댓글 남기기