Loading...
Loading...
03-skills/crawler-code-generator/assets/dynamic-crawler-template.py
"""
동적 페이지 크롤러 템플릿
라이브러리: Selenium + webdriver-manager
용도: JavaScript 렌더링이 필요한 동적 페이지 (AJAX, SPA)
UV 설정:
uv init crawler-project
uv add selenium webdriver-manager pandas
uv run crawler.py
"""
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from webdriver_manager.chrome import ChromeDriverManager
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 DynamicCrawler:
"""동적 웹 페이지 크롤러 (Selenium)"""
def __init__(self, base_url: str, headless: bool = True):
self.base_url = base_url
self.headless = headless
self.driver: Optional[webdriver.Chrome] = None
self.data: List[Dict] = []
def _setup_driver(self) -> webdriver.Chrome:
"""Chrome WebDriver 설정"""
options = Options()
if self.headless:
options.add_argument('--headless=new')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-gpu')
options.add_argument('--window-size=1920,1080')
options.add_argument('--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')
# 자동 드라이버 설치
service = Service(ChromeDriverManager().install())
return webdriver.Chrome(service=service, options=options)
def start(self):
"""드라이버 시작"""
if self.driver is None:
self.driver = self._setup_driver()
logger.info("Chrome WebDriver 시작됨")
def stop(self):
"""드라이버 종료"""
if self.driver:
self.driver.quit()
self.driver = None
logger.info("Chrome WebDriver 종료됨")
def wait_for_element(self, selector: str, by: By = By.CSS_SELECTOR, timeout: int = 10):
"""요소가 로드될 때까지 대기"""
try:
element = WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((by, selector))
)
return element
except TimeoutException:
logger.warning(f"요소 대기 시간 초과: {selector}")
return None
def scroll_to_bottom(self, pause_time: float = 1.0, max_scrolls: int = 10):
"""무한 스크롤 페이지 처리"""
last_height = self.driver.execute_script("return document.body.scrollHeight")
scroll_count = 0
while scroll_count < max_scrolls:
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(pause_time)
new_height = self.driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
scroll_count += 1
logger.info(f"스크롤 {scroll_count}/{max_scrolls}")
def parse_item(self, element) -> Dict:
"""개별 항목 파싱 (사이트에 맞게 수정 필요)"""
# TODO: 실제 사이트 구조에 맞게 셀렉터 수정
try:
title = element.find_element(By.CSS_SELECTOR, '.title').text
except NoSuchElementException:
title = ''
try:
content = element.find_element(By.CSS_SELECTOR, '.content').text
except NoSuchElementException:
content = ''
try:
link = element.find_element(By.CSS_SELECTOR, 'a').get_attribute('href')
except NoSuchElementException:
link = ''
return {
'title': title,
'content': content,
'link': link,
}
def crawl(self, urls: List[str] = None) -> List[Dict]:
"""크롤링 실행"""
if urls is None:
urls = [self.base_url]
self.start()
try:
for url in urls:
logger.info(f"크롤링 중: {url}")
self.driver.get(url)
# 페이지 로드 대기
# TODO: 실제 사이트의 로드 완료 시그널 요소로 수정
self.wait_for_element('.item', timeout=10)
# 필요시 스크롤 (무한 스크롤 페이지용)
# self.scroll_to_bottom()
# TODO: 실제 사이트의 항목 컨테이너 셀렉터로 수정
items = self.driver.find_elements(By.CSS_SELECTOR, '.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) # 서버 부하 방지
finally:
self.stop()
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 = DynamicCrawler(BASE_URL, headless=True)
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()