웹 크롤링 시스템 개선: 안티봇 탐지 우회와 안정성 향상
최근 우리 팀은 웹 크롤링 시스템을 대폭 개선하는 작업을 진행했습니다. 이 글에서는 크롤링 시스템의 안정성을 높이고 안티봇 탐지를 우회하기 위해 적용한 다양한 전략들을 공유하고자 합니다.
1. 인프라 구성 개선
1.1 패키지 의존성 추가
크롤링 시스템의 안정성을 높이기 위해 필요한 시스템 패키지들을 추가했습니다. 특히 헤드리스 Chrome 브라우저가 정상적으로 동작하기 위해 필요한 다양한 의존성들을 식별하고 추가했습니다.
packages:
cups-libs: []
cups: []
cups-client: []
cups-devel: []
libXScrnSaver: []
nss: []
이러한 패키지들은 다음과 같은 목적으로 추가되었습니다:
- CUPS 관련 패키지: PDF 생성 및 프린팅 기능 지원
- libXScrnSaver: 화면 보호기 관련 기능 지원
- NSS: 네트워크 보안 서비스 지원
1.2 ChromeDriver 설치 프로세스 개선
기존의 단순한 ChromeDriver 설치 과정을 더욱 견고하게 개선했습니다. 주요 변경사항은 다음과 같습니다:
# EPEL 저장소 활성화
amazon-linux-extras install epel -y
# Chrome 브라우저 설치 개선
if ! command -v google-chrome > /dev/null 2>&1; then
echo "Google Chrome not found. Installing..."
yum install -y cups-libs libX11 libXcomposite libXcursor libXdamage libXext libXi libXrandr libXScrnSaver libXss libXtst at-spi2-atk gtk3 alsa-lib mesa-libgbm xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc
cd /tmp
wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
sudo yum install -y ./google-chrome-stable_current_x86_64.rpm
fi
특히 주목할 만한 개선사항은 다음과 같습니다:
- 이미 설치된 경우 중복 설치 방지
- 필요한 폰트 및 그래픽 라이브러리 자동 설치
- 설치 과정의 로깅 개선
2. 크롤링 아키텍처 개선
2.1 WebDriverFactory 도입
크롬 드라이버 생성 로직을 중앙화하고 재사용성을 높이기 위해 WebDriverFactory 클래스를 도입했습니다:
class WebDriverFactory:
@staticmethod
def create_chrome_options() -> Options:
options = Options()
# 안티봇 탐지 방지 설정
selected_ua = random.choice(USER_AGENTS)
options.add_argument(f'user-agent={selected_ua}')
# 헤드리스 모드 설정
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
# 브라우저 동작 최적화
options.add_argument('--disable-gpu')
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option('excludeSwitches', ['enable-automation'])
return options
@classmethod
def create_driver(cls) -> webdriver.Chrome:
return webdriver.Chrome(
service=Service(executable_path='/usr/local/bin/chromedriver'),
options=cls.create_chrome_options()
)
2.2 BaseScraper 추상 클래스 구현
반복되는 크롤링 로직을 추상화하고 일관된 인터페이스를 제공하기 위해 BaseScraper 클래스를 구현했습니다:
class BaseScraper:
def __init__(self, wait_timeout: int = 20):
self.driver = WebDriverFactory.create_driver()
self.wait = WebDriverWait(self.driver, wait_timeout)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.driver.quit()
def scroll_page(self, scroll_pause: float = 1.5):
"""인간다운 스크롤 동작 구현"""
last_height = self.driver.execute_script(
"return document.body.scrollHeight"
)
while True:
# 랜덤한 스크롤 동작
scroll_amount = random.randint(300, 700)
self.driver.execute_script(
f"window.scrollBy(0, {scroll_amount});"
)
# 자연스러운 대기 시간
time.sleep(random.uniform(1.0, scroll_pause))
new_height = self.driver.execute_script(
"return document.body.scrollHeight"
)
if new_height == last_height:
break
last_height = new_height
3. 쿠팡 크롤러 구현
3.1 Product 데이터 클래스
수집할 상품 데이터를 명확하게 정의하기 위해 데이터 클래스를 활용했습니다:
@dataclass
class Product:
name: str
link: str
image: str
def to_dict(self) -> Dict:
return {
"name": self.name,
"link": self.link,
"image": self.image
}
3.2 CoupangScraper 구현
쿠팡 전용 크롤러는 BaseScraper를 상속받아 구현했습니다:
class CoupangScraper(BaseScraper):
def scrape_products(self, query: str) -> Dict:
try:
encoded_query = quote(query)
url = f"https://www.coupang.com/np/search?q={encoded_query}"
self.driver.get(url)
time.sleep(random.uniform(2.0, 4.0)) # 자연스러운 대기
self._scrape_product_cards()
return {
'url': self.driver.current_url,
'data': [product.to_dict() for product in self.products]
}
except Exception as e:
logger.error(f"Error during scraping: {str(e)}")
return {'error': str(e)}
4. 안티봇 탐지 우회 전략
4.1 User-Agent Rotation
다양한 User-Agent를 사용하여 봇 탐지를 우회합니다:
USER_AGENTS = [
# 모바일 에이전트
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_2 like Mac OS X) AppleWebKit/605.1.15',
'Mozilla/5.0 (Linux; Android 10; SM-G970F) AppleWebKit/537.36',
# 데스크톱 에이전트
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
]
4.2 자연스러운 브라우저 동작 구현
봇 탐지를 피하기 위해 다음과 같은 전략들을 구현했습니다:
- 랜덤한 스크롤 동작
- 자연스러운 대기 시간
- 실제 브라우저와 유사한 헤더 설정
- 자동화 흔적 제거
5. 에러 처리 및 로깅
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
try:
product_cards = self.wait.until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, "li.search-product")
)
)
except TimeoutException:
logger.warning("Timeout while waiting for product cards")
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
결론
이번 개선을 통해 다음과 같은 성과를 얻을 수 있었습니다:
- 크롤링 성공률 향상
- 안티봇 탐지 회피율 증가
- 안정적인 데이터 수집
- 코드 품질 개선
- 재사용 가능한 컴포넌트 설계
- 테스트 용이성 향상
- 유지보수성 향상
- 명확한 에러 처리
- 체계적인 로깅
향후에는 다음과 같은 개선을 계획하고 있습니다:
- 추가 크롤러 개발 용이성 확보
- 성능 모니터링 시스템 구축
- 분산 크롤링 시스템 도입 검토
이러한 개선사항들은 GitHub에서 확인하실 수 있습니다. 여러분의 프로젝트에도 도움이 되었기를 바랍니다.
반응형
'Develop' 카테고리의 다른 글
크롬 확장프로그램 개발기: 이미지 다운로더 만들며 배운 것들 (1) | 2024.11.30 |
---|---|
Flutter로 만든 안드로이드 앱 플레이스토어 출시하기 (초보자도 가능한 A to Z) (2) | 2024.11.26 |
크롬 확장프로그램 개발 실전 가이드 (feat. 삽질 방지 팁) (0) | 2024.11.24 |
Flutter Android App Bundle 배포 시 발생한 이슈들과 해결 방법 (1) | 2024.11.21 |
Flutter로 '각산마을' 지도 앱 개발기 (2) | 2024.11.20 |