웹사이트에서 데이터를 수집하거나 API로 받은 데이터를 엑셀로 저장하고 싶으신가요? 파이썬을 사용하면 JSON, CSV, HTML, API 응답 등 어떤 형태의 데이터든 쉽게 파싱하고 엑셀 파일로 변환할 수 있습니다. 오늘은 실무에서 바로 쓸 수 있는 다양한 파싱 방법과 엑셀 저장 기법을 알려드립니다.
필요한 라이브러리 설치하기
엑셀 작업에 가장 많이 사용되는 라이브러리는 다음과 같습니다.
# 엑셀 작업을 위한 필수 라이브러리
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)
마치며
파이썬으로 데이터를 파싱하고 엑셀로 저장하는 것은 생각보다 간단합니다. 핵심 포인트를 정리하면:
기본 단계:
- 데이터 수집 (API, 웹 크롤링, CSV 등)
- pandas DataFrame으로 변환
to_excel()
메서드로 저장
실무 팁:
- 가상환경 사용: 프로젝트마다 독립된 환경 유지
- 에러 처리: try-except로 안정성 확보
- 로깅: 문제 추적을 위한 로그 기록
- 데이터 검증: 저장 전 데이터 정제 및 검증
- 스케줄링: 정기적 수집은 schedule 라이브러리 활용
이제 여러분도 데이터 파싱 전문가입니다! 궁금한 점이 있다면 댓글로 질문해주세요.