Loading...
Loading...
03-skills/crawler-code-generator/assets/static-crawler-template.py
"""
정적 페이지 크롤러 템플릿
라이브러리: Requests + BeautifulSoup4
용도: JavaScript 렌더링이 필요 없는 정적 HTML 페이지
UV 설정:
uv init crawler-project
uv add requests beautifulsoup4 pandas
uv run crawler.py
"""
import requests
from bs4 import BeautifulSoup
import pandas as pd
from typing import List, Dict, Optional
import time
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class StaticCrawler:
"""정적 웹 페이지 크롤러"""
def __init__(self, base_url: str):
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
})
self.data: List[Dict] = []
def fetch_page(self, url: str) -> Optional[BeautifulSoup]:
"""페이지 HTML 가져오기"""
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
response.encoding = response.apparent_encoding
return BeautifulSoup(response.text, 'html.parser')
except requests.RequestException as e:
logger.error(f"페이지 요청 실패: {url} - {e}")
return None
def parse_item(self, element) -> Dict:
"""개별 항목 파싱 (사이트에 맞게 수정 필요)"""
# TODO: 실제 사이트 구조에 맞게 셀렉터 수정
return {
'title': element.select_one('.title').get_text(strip=True) if element.select_one('.title') else '',
'content': element.select_one('.content').get_text(strip=True) if element.select_one('.content') else '',
'link': element.select_one('a')['href'] if element.select_one('a') else '',
}
def crawl(self, urls: List[str] = None) -> List[Dict]:
"""크롤링 실행"""
if urls is None:
urls = [self.base_url]
for url in urls:
logger.info(f"크롤링 중: {url}")
soup = self.fetch_page(url)
if soup is None:
continue
# TODO: 실제 사이트의 항목 컨테이너 셀렉터로 수정
items = soup.select('.item')
for item in items:
try:
parsed = self.parse_item(item)
self.data.append(parsed)
except Exception as e:
logger.error(f"파싱 실패: {e}")
continue
time.sleep(1) # 서버 부하 방지
logger.info(f"총 {len(self.data)}개 항목 수집 완료")
return self.data
def save_to_csv(self, filename: str = 'output.csv'):
"""CSV 파일로 저장"""
if not self.data:
logger.warning("저장할 데이터가 없습니다")
return
df = pd.DataFrame(self.data)
df.to_csv(filename, index=False, encoding='utf-8-sig')
logger.info(f"CSV 저장 완료: {filename}")
def main():
# TODO: 크롤링할 URL로 수정
BASE_URL = "https://example.com"
crawler = StaticCrawler(BASE_URL)
data = crawler.crawl()
crawler.save_to_csv('output.csv')
print(f"\n수집된 데이터: {len(data)}개")
if data:
print(pd.DataFrame(data).head())
if __name__ == "__main__":
main()