본문 바로가기
개발

🌈 초보자를 위한 PyCharm으로 웹 스크래핑 시작하기 (3/3)

by D-Project 2025. 3. 17.

안녕하세요, 여러분! 드디어 웹 스크래핑 시리즈의 마지막 시간이 되었어요! 🎉 지난 시간까지 환경 설정과 기본적인 웹 스크래핑 방법을 배웠는데요, 오늘은 수집한 데이터를 저장하고 활용하는 방법과 더 심화된 기술을 알아볼 거예요! 어제는 스크래핑 코드로 날씨 정보를 가져왔더니 우산을 챙겨갔는데 정말 비가 왔어요! 웹 스크래핑의 실용성을 몸소 체험했답니다! 😄 이제 마지막 단계로 함께 나아가볼까요?

웹 스크래핑 데이터 저장하기 💾

수집한 데이터는 다양한 형식으로 저장할 수 있어요. 가장 많이 사용되는 형식들을 살펴볼게요!

1. CSV 파일로 저장하기 📋

CSV(Comma-Separated Values)는 데이터를 쉼표로 구분하여 저장하는 간단한 형식이에요. 엑셀에서도 쉽게 열 수 있어 널리 사용되죠!

import requests
from bs4 import BeautifulSoup
import csv
import datetime

def scrape_news_headlines():
    """뉴스 헤드라인을 스크래핑하는 함수"""
    url = "https://news.ycombinator.com/"
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 헤드라인과 링크 가져오기
    headlines = []
    for item in soup.select('span.titleline > a'):
        headlines.append({
            'title': item.text,
            'link': item.get('href')
        })
    
    return headlines

def save_to_csv(data, filename):
    """데이터를 CSV 파일로 저장하는 함수"""
    # 현재 날짜를 파일명에 추가
    today = datetime.datetime.now().strftime("%Y-%m-%d")
    full_filename = f"{filename}_{today}.csv"
    
    # CSV 파일 생성 및 데이터 쓰기
    with open(full_filename, 'w', newline='', encoding='utf-8') as csvfile:
        # 필드 이름 정의
        fieldnames = data[0].keys() if data else []
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        # 헤더 쓰기
        writer.writeheader()
        
        # 데이터 쓰기
        for row in data:
            writer.writerow(row)
    
    print(f"데이터가 {full_filename}에 저장되었습니다!")
    return full_filename

# 실행 예제
if __name__ == "__main__":
    print("뉴스 헤드라인 스크래핑 중...")
    headlines = scrape_news_headlines()
    
    if headlines:
        print(f"{len(headlines)}개의 헤드라인을 찾았습니다.")
        filename = save_to_csv(headlines, "news_headlines")
        
        # 저장된 데이터 미리보기
        print("\n저장된 데이터 미리보기:")
        for i, headline in enumerate(headlines[:5], 1):
            print(f"{i}. {headline['title']}")
    else:
        print("헤드라인을 찾을 수 없습니다.")

2. JSON 파일로 저장하기 📊

JSON(JavaScript Object Notation)은 구조화된 데이터를 저장하기에 좋은 형식이에요. 웹 애플리케이션에서 많이 사용되죠!

import requests
from bs4 import BeautifulSoup
import json
import datetime

def scrape_product_info():
    """상품 정보를 스크래핑하는 예제 함수"""
    # 이 예제에서는 실제 데이터 대신 샘플 데이터를 사용합니다
    # 실제 스크래핑에서는 아래 코드를 사용하세요:
    # url = "https://example-shop.com/products"
    # response = requests.get(url)
    # soup = BeautifulSoup(response.text, 'html.parser')
    # products = []
    # for item in soup.select('.product-item'):
    #     name = item.select_one('.product-name').text.strip()
    #     price = item.select_one('.product-price').text.strip()
    #     products.append({'name': name, 'price': price})
    
    # 샘플 데이터
    products = [
        {'name': '스마트폰 케이스', 'price': '15,000원', 'rating': 4.5, 'reviews': 120},
        {'name': '블루투스 이어폰', 'price': '89,000원', 'rating': 4.8, 'reviews': 342},
        {'name': '노트북 파우치', 'price': '28,500원', 'rating': 4.2, 'reviews': 56},
        {'name': '무선 충전기', 'price': '35,000원', 'rating': 3.9, 'reviews': 78}
    ]
    
    return products

def save_to_json(data, filename):
    """데이터를 JSON 파일로 저장하는 함수"""
    # 현재 날짜와 시간을 파일명에 추가
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    full_filename = f"{filename}_{timestamp}.json"
    
    # 메타데이터 추가
    json_data = {
        'timestamp': timestamp,
        'source': 'Web Scraping Example',
        'total_items': len(data),
        'items': data
    }
    
    # JSON 파일에 데이터 쓰기
    with open(full_filename, 'w', encoding='utf-8') as jsonfile:
        json.dump(json_data, jsonfile, ensure_ascii=False, indent=4)
    
    print(f"데이터가 {full_filename}에 저장되었습니다!")
    return full_filename

# 실행 예제
if __name__ == "__main__":
    print("상품 정보 스크래핑 중...")
    products = scrape_product_info()
    
    if products:
        print(f"{len(products)}개의 상품 정보를 찾았습니다.")
        filename = save_to_json(products, "product_data")
        
        # 저장된 데이터 미리보기
        print("\n저장된 데이터 미리보기:")
        for i, product in enumerate(products, 1):
            print(f"{i}. {product['name']} - {product['price']} (평점: {product['rating']})")
    else:
        print("상품 정보를 찾을 수 없습니다.")

3. 데이터베이스에 저장하기 🗄️

대용량 데이터나 구조화된 데이터는 SQLite 같은 데이터베이스에 저장하면 더 효율적으로 관리할 수 있어요!

import requests
from bs4 import BeautifulSoup
import sqlite3
import datetime

def scrape_book_info():
    """책 정보를 스크래핑하는 예제 함수"""
    # 실제 사이트 스크래핑 대신 샘플 데이터 사용
    books = [
        {'title': '파이썬으로 배우는 웹 스크래핑', 'author': '김개발', 'price': '25,000원', 'publisher': '코딩출판사', 'year': 2023},
        {'title': '처음 시작하는 데이터 분석', 'author': '이분석', 'price': '30,000원', 'publisher': '데이터북스', 'year': 2022},
        {'title': 'PyCharm 마스터하기', 'author': '박코딩', 'price': '28,000원', 'publisher': 'IDE출판', 'year': 2023},
        {'title': '웹 개발자를 위한 HTML/CSS', 'author': '최웹개발', 'price': '22,000원', 'publisher': '프론트엔드사', 'year': 2021}
    ]
    return books

def save_to_database(books):
    """책 정보를 SQLite 데이터베이스에 저장하는 함수"""
    # 현재 날짜로 DB 파일 이름 생성
    today = datetime.datetime.now().strftime("%Y-%m-%d")
    db_name = f"books_database_{today}.db"
    
    # 데이터베이스 연결
    conn = sqlite3.connect(db_name)
    cursor = conn.cursor()
    
    # 테이블 생성 (이미 존재하는 경우 무시)
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS books (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        author TEXT NOT NULL,
        price TEXT,
        publisher TEXT,
        year INTEGER,
        date_added TEXT
    )
    ''')
    
    # 현재 시간 가져오기
    current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    # 데이터 추가
    for book in books:
        cursor.execute('''
        INSERT INTO books (title, author, price, publisher, year, date_added)
        VALUES (?, ?, ?, ?, ?, ?)
        ''', (
            book['title'],
            book['author'],
            book['price'],
            book['publisher'],
            book['year'],
            current_time
        ))
    
    # 변경사항 저장
    conn.commit()
    
    # 데이터베이스에서 데이터 조회하여 확인
    cursor.execute("SELECT * FROM books")
    saved_books = cursor.fetchall()
    
    # 연결 종료
    conn.close()
    
    print(f"데이터가 {db_name} 데이터베이스에 저장되었습니다!")
    return db_name, saved_books

# 실행 예제
if __name__ == "__main__":
    print("책 정보 스크래핑 중...")
    books = scrape_book_info()
    
    if books:
        print(f"{len(books)}개의 책 정보를 찾았습니다.")
        db_name, saved_books = save_to_database(books)
        
        # 저장된 데이터 미리보기
        print("\n데이터베이스에 저장된 책 정보:")
        for i, book in enumerate(saved_books, 1):
            # book 튜플 구조: (id, title, author, price, publisher, year, date_added)
            print(f"{i}. {book[1]} - {book[2]} ({book[4]}, {book[5]})")
    else:
        print("책 정보를 찾을 수 없습니다.")

고급 스크래핑 기법 🔍

이제 더 복잡한 웹사이트를 스크래핑하는 고급 기법을 알아볼게요!

1. 동적 페이지 스크래핑 (Selenium 활용) 🌐

JavaScript로 동적으로 콘텐츠를 불러오는 웹사이트는 Selenium을 사용하여 스크래핑할 수 있어요. Selenium은 실제 브라우저를 제어하는 도구예요!

"""
Selenium을 이용한 동적 웹페이지 스크래핑 예제

사용 전 준비사항:
1. Selenium 설치: pip install selenium
2. 웹 드라이버 설치: ChromeDriver (https://sites.google.com/chromium.org/driver/)
"""

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import csv

def setup_driver():
    """Selenium 웹 드라이버 설정"""
    # 크롬 옵션 설정
    chrome_options = Options()
    chrome_options.add_argument("--headless")  # 헤드리스 모드 (화면 표시 없음)
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("--window-size=1920,1080")
    
    # 크롬 드라이버 경로 설정 (자신의 ChromeDriver 경로로 변경하세요)
    driver_path = "./chromedriver"  # Windows: "chromedriver.exe"
    service = Service(driver_path)
    
    # 드라이버 생성
    driver = webdriver.Chrome(service=service, options=chrome_options)
    return driver

def scrape_infinite_scroll_page():
    """무한 스크롤 페이지 스크래핑 예제"""
    driver = setup_driver()
    
    try:
        # 웹 페이지 로드 (예: 트위터 검색 결과)
        driver.get("https://twitter.com/search?q=python&src=typed_query")
        
        # 페이지가 로드될 때까지 대기
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "[data-testid='tweet']"))
        )
        
        # 스크롤 데이터를 담을 리스트
        tweets = []
        target_count = 20  # 스크래핑할 트윗 수
        
        # 이미 처리한 트윗의 ID를 저장하는 집합
        processed_tweets = set()
        
        print(f"{target_count}개의 트윗을 수집합니다...")
        
        # 원하는 개수의 트윗을 모을 때까지 스크롤 반복
        while len(tweets) < target_count:
            # 현재 보이는 모든 트윗 요소 가져오기
            tweet_elements = driver.find_elements(By.CSS_SELECTOR, "[data-testid='tweet']")
            
            # 각 트윗 정보 추출
            for tweet in tweet_elements:
                # 트윗 ID 또는 고유 식별자 추출 (여기서는 내부 텍스트로 대체)
                tweet_text = tweet.text
                tweet_id = hash(tweet_text)  # 간단한 해시 기반 ID
                
                # 이미 처리한 트윗인지 확인
                if tweet_id in processed_tweets:
                    continue
                
                try:
                    # 트윗 작성자
                    author = tweet.find_element(By.CSS_SELECTOR, "[data-testid='User-Name']").text
                    
                    # 트윗 내용
                    content = tweet.find_element(By.CSS_SELECTOR, "[data-testid='tweetText']").text
                    
                    # 수집한 정보 저장
                    tweets.append({
                        'author': author,
                        'content': content
                    })
                    
                    # 처리한 트윗 ID 기록
                    processed_tweets.add(tweet_id)
                    
                    print(f"트윗 수집: {len(tweets)}/{target_count}")
                    
                    # 목표 달성 시 종료
                    if len(tweets) >= target_count:
                        break
                
                except Exception as e:
                    print(f"트윗 정보 추출 중 오류: {e}")
            
            # 페이지 아래로 스크롤
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            
            # 새 콘텐츠 로드 대기
            time.sleep(2)
        
        # CSV 파일로 저장
        with open('twitter_python_tweets.csv', 'w', newline='', encoding='utf-8') as csvfile:
            fieldnames = ['author', 'content']
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            for tweet in tweets:
                writer.writerow(tweet)
        
        print(f"{len(tweets)}개의 트윗을 성공적으로 수집했습니다!")
        print("데이터가 twitter_python_tweets.csv 파일에 저장되었습니다.")
    
    except Exception as e:
        print(f"스크래핑 중 오류 발생: {e}")
    
    finally:
        # 브라우저 종료
        driver.quit()

# 실행 시 주의: Twitter의 구조는 자주 변경될 수 있으며, 
# 실제 실행 시 로그인이 필요하거나 구조가 달라져 작동하지 않을 수 있습니다.
# 이 코드는 교육 목적으로만 제공됩니다.
if __name__ == "__main__":
    print("⚠️ 주의: 이 예제는 실제 Twitter 사이트의 구조에 따라 작동하지 않을 수 있습니다.")
    print("실행하기 전에 ChromeDriver가 설치되어 있는지 확인하세요.")
    # scrape_infinite_scroll_page()  # 실행 시 주석 해제

참고: Selenium을 사용하려면 먼저 pip install selenium으로 라이브러리를 설치하고, 브라우저 드라이버(예: ChromeDriver)를 다운로드해야 해요. 위 예제는 교육 목적으로만 제공되며, Twitter의 이용약관을 준수해야 함을 명심하세요!

2. 웹 스크래핑 자동화하기 ⏱️

일정 시간마다 자동으로 데이터를 수집하고 저장하는 스크립트를 만들 수 있어요. schedule 라이브러리를 사용하면 특정 시간에 자동으로 스크래핑을 수행할 수 있어요.

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

class WeatherScraper:
    """날씨 정보를 주기적으로 스크래핑하는 클래스"""
    
    def __init__(self, cities, output_folder="weather_data"):
        self.cities = cities
        self.output_folder = output_folder
        
        # 출력 폴더가 없으면 생성
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)
            print(f"출력 폴더 '{output_folder}'를 생성했습니다.")
        
        # 헤더 설정
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
        
        # 수집한 데이터를 저장할 데이터프레임 초기화
        self.all_data = pd.DataFrame()
    
    def get_weather(self, city):
        """지정된 도시의 날씨 정보를 가져오는 함수"""
        try:
            # 실제 프로덕션에서는 적절한 날씨 사이트를 사용하세요
            url = f"https://www.timeanddate.com/weather/{city}/ext"
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 날씨 데이터 추출 (사이트 구조에 따라 선택자 조정 필요)
            temp_element = soup.select_one('.h2')
            temp = temp_element.text if temp_element else "N/A"
            
            weather_element = soup.select_one('p.summary')
            weather = weather_element.text if weather_element else "N/A"
            
            # 현재 시간
            current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            
            return {
                'city': city,
                'temperature': temp,
                'condition': weather,
                'timestamp': current_time
            }
            
        except Exception as e:
            print(f"도시 '{city}' 날씨 스크래핑 중 오류: {e}")
            return {
                'city': city,
                'temperature': "오류",
                'condition': "오류",
                'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            }
    
    def scrape_all_cities(self):
        """모든 도시의 날씨 정보를 스크래핑하는 함수"""
        print(f"\n====== 날씨 스크래핑 시작: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ======")
        
        # 각 도시의 날씨 정보 수집
        data = []
        for city in self.cities:
            print(f"{city} 날씨 정보 스크래핑 중...")
            weather_info = self.get_weather(city)
            data.append(weather_info)
            
            # 너무 빠른 요청으로 차단되지 않도록 대기
            time.sleep(1)
        
        # 데이터프레임으로 변환
        df = pd.DataFrame(data)
        print(f"{len(df)} 개의 도시 날씨 정보를 수집했습니다.")
        
        # 모든 데이터에 추가
        self.all_data = pd.concat([self.all_data, df])
        
        # CSV 파일로 저장
        today = datetime.now().strftime("%Y-%m-%d")
        now = datetime.now().strftime("%H-%M-%S")
        filename = f"{self.output_folder}/weather_{today}_{now}.csv"
        df.to_csv(filename, index=False)
        print(f"데이터가 {filename}에 저장되었습니다.")
        
        # 전체 데이터 저장
        all_data_filename = f"{self.output_folder}/all_weather_data.csv"
        self.all_data.to_csv(all_data_filename, index=False)
        print(f"누적 데이터가 {all_data_filename}에 저장되었습니다.")
    
    def start_scheduled_scraping(self, interval_minutes=60):
        """스케줄링된 스크래핑 시작"""
        print(f"날씨 스크래핑 자동화 시작: {interval_minutes}분마다 실행됩니다.")
        
        # 먼저 한 번 실행
        self.scrape_all_cities()
        
        # 주기적으로 실행하도록 스케줄링
        schedule.every(interval_minutes).minutes.do(self.scrape_all_cities)
        
        try:
            # 스케줄러 계속 실행
            while True:
                schedule.run_pending()
                time.sleep(1)
        except KeyboardInterrupt:
            print("\n스크래핑 자동화가 중지되었습니다.")

# 사용 예제
if __name__ == "__main__":
    # 스크래핑할 도시 리스트
    cities_to_scrape = ["seoul", "tokyo", "london", "new-york"]
    
    # 스크래퍼 초기화
    scraper = WeatherScraper(cities_to_scrape)
    
    # 15분마다 스크래핑 시작
    scraper.start_scheduled_scraping(interval_minutes=15)

참고: 스크래핑 자동화 시 해당 웹사이트의 이용약관을 준수하고, 서버에 부담을 주지 않도록 적절한 시간 간격을 설정하는 것이 중요해요!

3. 스크래핑 데이터로 분석하기 📊

수집한 데이터를 분석하고 시각화하면 유용한 인사이트를 얻을 수 있어요. pandas와 matplotlib 같은 라이브러리를 활용하면 데이터를 더 쉽게 분석할 수 있어요!

import pandas as pd
import matplotlib.pyplot as plt
import os
import re
from datetime import datetime

def analyze_weather_data(data_folder="weather_data"):
    """날씨 데이터를 분석하고 시각화하는 함수"""
    # 누적 데이터 파일 확인
    all_data_file = f"{data_folder}/all_weather_data.csv"
    
    if not os.path.exists(all_data_file):
        print(f"누적 데이터 파일을 찾을 수 없습니다: {all_data_file}")
        return
    
    # 데이터 로드
    df = pd.read_csv(all_data_file)
    print(f"데이터 로드 완료: {len(df)}개의 레코드")
    
    # 데이터 전처리
    # 온도에서 숫자만 추출 (예: "25°C" -> 25)
    df['temp_value'] = df['temperature'].apply(lambda x: 
        float(re.search(r'(-?\d+(\.\d+)?)', str(x)).group(1)) if isinstance(x, str) and re.search(r'(-?\d+(\.\d+)?)', str(x)) else None)
    
    # 타임스탬프를 datetime 객체로 변환
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    df['date'] = df['timestamp'].dt.date
    
    # 기본 통계 분석
    print("\n=== 도시별 평균 온도 ===")
    city_avg_temp = df.groupby('city')['temp_value'].mean().reset_index()
    print(city_avg_temp)
    
    # 도시별 날씨 상태 빈도
    print("\n=== 도시별 가장 흔한 날씨 상태 ===")
    weather_counts = df.groupby(['city', 'condition']).size().reset_index(name='count')
    for city in df['city'].unique():
        city_weather = weather_counts[weather_counts['city'] == city].sort_values('count', ascending=False)
        if not city_weather.empty:
            print(f"{city}: {city_weather.iloc[0]['condition']} ({city_weather.iloc[0]['count']}회)")
    
    # 그래프 생성을 위한 폴더 생성
    graphs_folder = f"{data_folder}/graphs"
    if not os.path.exists(graphs_folder):
        os.makedirs(graphs_folder)
    
    # 1. 도시별 평균 온도 바 차트
    plt.figure(figsize=(10, 6))
    plt.bar(city_avg_temp['city'], city_avg_temp['temp_value'], color='skyblue')
    plt.title('도시별 평균 온도')
    plt.xlabel('도시')
    plt.ylabel('평균 온도 (°C)')
    plt.grid(axis='y', linestyle='--', alpha=0.7)
    plt.savefig(f"{graphs_folder}/city_avg_temp.png")
    plt.close()
    
    # 2. 시간별 온도 변화 (라인 차트)
    # 각 도시별로 별도의 선 그래프
    plt.figure(figsize=(12, 7))
    for city in df['city'].unique():
        city_data = df[df['city'] == city].sort_values('timestamp')
        plt.plot(city_data['timestamp'], city_data['temp_value'], label=city, marker='o')
    
    plt.title('시간별 도시 온도 변화')
    plt.xlabel('시간')
    plt.ylabel('온도 (°C)')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.savefig(f"{graphs_folder}/temp_over_time.png")
    plt.close()
    
    # 3. 도시별 온도 분포 (박스플롯)
    plt.figure(figsize=(10, 6))
    df.boxplot(column='temp_value', by='city', grid=False)
    plt.title('도시별 온도 분포')
    plt.suptitle('')  # 상단 제목 제거
    plt.xlabel('도시')
    plt.ylabel('온도 (°C)')
    plt.savefig(f"{graphs_folder}/temp_distribution.png")
    plt.close()
    
    print(f"\n분석 완료! 그래프가 {graphs_folder}에 저장되었습니다.")
    
    return df

def create_weather_report(df, output_folder="weather_data"):
    """날씨 데이터를 기반으로 간단한 보고서 생성"""
    if df is None or df.empty:
        print("보고서를 생성할 데이터가 없습니다.")
        return
    
    # 보고서 파일명
    report_file = f"{output_folder}/weather_report_{datetime.now().strftime('%Y-%m-%d')}.html"
    
    # HTML 보고서 작성
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>날씨 데이터 분석 보고서</title>
        <style>
            body {{ font-family: Arial, sans-serif; margin: 20px; }}
            h1 {{ color: #2c3e50; }}
            h2 {{ color: #3498db; }}
            table {{ border-collapse: collapse; width: 100%; margin-bottom: 20px; }}
            th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
            th {{ background-color: #f2f2f2; }}
            tr:nth-child(even) {{ background-color: #f9f9f9; }}
            .graph {{ margin: 20px 0; max-width: 100%; }}
            .summary {{ background-color: #e8f4f8; padding: 15px; border-radius: 5px; }}
        </style>
    </head>
    <body>
        <h1>날씨 데이터 분석 보고서</h1>
        <p>생성 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
        
        <div class="summary">
            <h2>요약</h2>
            <p>분석된 데이터 수: {len(df)}개 레코드</p>
            <p>분석 기간: {df['timestamp'].min().strftime('%Y-%m-%d')} ~ {df['timestamp'].max().strftime('%Y-%m-%d')}</p>
            <p>분석 도시: {', '.join(df['city'].unique())}</p>
        </div>
        
        <h2>도시별 평균 온도</h2>
        <table>
            <tr>
                <th>도시</th>
                <th>평균 온도 (°C)</th>
                <th>최저 온도 (°C)</th>
                <th>최고 온도 (°C)</th>
            </tr>
    """
    
    # 도시별 통계 추가
    for city in df['city'].unique():
        city_data = df[df['city'] == city]
        avg_temp = city_data['temp_value'].mean()
        min_temp = city_data['temp_value'].min()
        max_temp = city_data['temp_value'].max()
        
        html += f"""
            <tr>
                <td>{city}</td>
                <td>{avg_temp:.1f}</td>
                <td>{min_temp:.1f}</td>
                <td>{max_temp:.1f}</td>
            </tr>
        """
    
    html += """
        </table>
        
        <h2>그래프</h2>
        <div class="graph">
            <h3>도시별 평균 온도</h3>
            <img src="graphs/city_avg_temp.png" alt="도시별 평균 온도" width="800">
        </div>
        
        <div class="graph">
            <h3>시간별 온도 변화</h3>
            <img src="graphs/temp_over_time.png" alt="시간별 온도 변화" width="800">
        </div>
        
        <div class="graph">
            <h3>도시별 온도 분포</h3>
            <img src="graphs/temp_distribution.png" alt="도시별 온도 분포" width="800">
        </div>
        
        <h2>결론</h2>
        <p>이 보고서는 수집된 날씨 데이터를 기반으로 자동 생성되었습니다. 더 자세한 분석이 필요한 경우 원본 데이터를 참조하세요.</p>
    </body>
    </html>
    """
    
    # HTML 파일로 저장
    with open(report_file, 'w', encoding='utf-8') as f:
        f.write(html)
    
    print(f"보고서가 생성되었습니다: {report_file}")

# 실행 예제
if __name__ == "__main__":
    print("날씨 데이터 분석 시작...")
    df = analyze_weather_data()
    
    if df is not None and not df.empty:
        create_weather_report(df)

웹 스크래핑 프로젝트 아이디어 💡

이제 배운 내용을 응용할 수 있는 몇 가지 프로젝트 아이디어를 소개할게요:

  1. 가격 비교 도구: 여러 쇼핑몰에서 특정 제품의 가격을 수집하여 비교하는 도구
  2. 뉴스 요약기: 여러 뉴스 사이트에서 헤드라인을 수집하고 주요 키워드를 분석하는 도구
  3. 도서 추천 시스템: 도서 리뷰 사이트에서 데이터를 수집하여 사용자 취향에 맞는 책을 추천하는 시스템
  4. 채용 정보 모니터링: 구직 사이트에서 특정 키워드가 포함된 채용 공고를 수집하는 도구
  5. 소셜 미디어 트렌드 분석: 소셜 미디어 플랫폼에서 인기 주제나 해시태그를 분석하는 도구

웹 스크래핑의 윤리적 고려사항 🤔

웹 스크래핑은 강력한 도구이지만, 책임감 있게 사용해야 해요:

  1. 이용 약관 준수: 웹사이트의 이용 약관과 robots.txt 파일을 반드시 확인하세요
  2. 개인정보 보호: 개인정보가 포함된 데이터는 수집하지 마세요
  3. 서버 부하 고려: 짧은 시간에 너무 많은 요청을 보내지 마세요
  4. 저작권 존중: 수집한 데이터의 사용 시 저작권을 존중하세요
  5. 상업적 이용 제한: 일부 웹사이트는 상업적 목적의 스크래핑을 금지하고 있어요

웹 스크래핑 기술 향상을 위한 다음 단계 🚀

웹 스크래핑에 더 능숙해지고 싶다면, 다음 주제들을 더 깊이 공부해보세요:

  1. 정규표현식(Regex): 복잡한 패턴의 문자열을 추출하는데 유용해요
  2. API 활용: 많은 웹사이트는 공식 API를 제공하며, 이를 활용하면 더 안정적으로 데이터를 수집할 수 있어요
  3. 비동기 프로그래밍: 여러 페이지를 동시에 스크래핑하여 성능을 향상시킬 수 있어요
  4. 데이터베이스 연동: 대량의 데이터를 효율적으로 저장하고 관리하는 방법을 배우세요
  5. 머신러닝 적용: 수집한 데이터에 머신러닝을 적용하여 예측 모델을 만들어보세요

시리즈를 마치며 🌟

이렇게 3회에 걸친 웹 스크래핑 시리즈가 마무리되었어요! 여러분은 이제:

  1. PyCharm 환경 설정 방법
  2. 기본적인 웹 스크래핑 방법
  3. 다양한 데이터 저장 방식
  4. 고급 스크래핑 기법
  5. 데이터 분석 및 시각화 방법

을 배웠어요! 이 지식을 바탕으로 여러분만의 웹 스크래핑 프로젝트를 시작해보세요!

어제 웹 스크래핑으로 날씨 정보를 가져와서 비 소식을 미리 알게 된 것처럼, 웹 스크래핑은 우리 일상을 더 편리하게 만들어줄 수 있어요. 이제 여러분도 코딩의 마법으로 인터넷의 바다에서 원하는 정보를 쏙쏙 건져올릴 수 있게 되었네요! 😊

질문이나 피드백이 있으시면 언제든지 댓글로 남겨주세요! 여러분의 첫 웹 스크래핑 프로젝트가 무엇인지도 알려주시면 좋겠어요! 함께 성장하는 즐거움을 나눠요~ 💖