AI/Python

크롤링 안정화: 대기 처리, 예외 처리, 매너

jumemory 2025. 6. 2. 17:47

“한 번 성공하는 코드”가 아니라 “여러 페이지를 안정적으로 수집하는 코드”를 만드는 핵심 단계

크롤링을 처음 배울 때는 보통 이런 흐름으로 시작합니다.

  • 페이지 열기
  • 요소 찾기
  • 텍스트 추출하기
  • 리스트에 저장하기

이 정도만 되면 일단 “크롤링이 된다”는 느낌을 받습니다.
하지만 실제 프로젝트나 실무 환경에서는 여기서부터가 진짜 시작입니다.

왜냐하면 실제 웹페이지는 항상 깔끔하게 동작하지 않기 때문입니다.

예를 들어 이런 일이 아주 흔합니다.

  • 페이지가 생각보다 늦게 뜬다
  • 버튼을 눌렀는데 다음 요소가 바로 안 생긴다
  • 어떤 매물은 가격이 비어 있다
  • 어떤 카드만 구조가 조금 다르다
  • 몇 페이지는 정상인데 중간 페이지에서 갑자기 에러가 난다
  • 사이트가 요청을 너무 많이 보내는 것으로 판단해 차단한다
  • 크롤링 도중 인터넷이 잠깐 끊기거나 응답이 늦어진다

즉, 크롤링에서 중요한 것은
단순히 데이터를 뽑는 문법만이 아니라,
예상치 못한 상황에서도 전체 수집이 끝까지 돌아가게 만드는 안정성입니다.

이 파트에서는 바로 그 부분을 다룹니다.

  • time.sleep()은 왜 필요한가
  • Selenium의 implicit wait / explicit wait는 무엇이 다른가
  • 왜 “무조건 기다리기”보다 “조건을 기다리기”가 좋은가
  • try-except는 크롤링에서 왜 거의 필수인가
  • 어떤 예외는 건너뛰고, 어떤 예외는 멈춰야 하는가
  • 로그를 남기는 습관은 왜 중요한가
  • 크롤링 매너는 왜 단순 예절이 아니라 실전 전략인가
  • 서버 부하와 차단을 줄이려면 무엇을 고려해야 하는가

즉, 이 파트의 핵심은
크롤링을 성공시키는 것이 아니라, 크롤링을 안정적으로 지속시키는 방법을 배우는 것입니다.


1. 왜 “안정화”가 따로 필요한가?

많은 초보자는 이렇게 생각하기 쉽습니다.

  • 내가 원하는 요소를 찾았으니 끝
  • 코드가 한 번 돌아갔으니 성공
  • 일단 결과가 나오니까 문제 없음

하지만 실제 크롤링은 “한 번 되는 것”과 “계속 잘 되는 것”이 다릅니다.

예를 들어 이런 코드는 종종 처음엔 잘 됩니다.

 
price = driver.find_element(By.CLASS_NAME, "price").text
 

그런데 실제로는:

  • 페이지 로딩이 조금만 늦어도 실패할 수 있고
  • 구조가 조금만 바뀌어도 실패할 수 있고
  • 특정 카드만 예외 구조면 실패할 수 있습니다

즉, 크롤링은 본질적으로 불안정한 환경에서 돌아가는 코드라고 생각하는 것이 맞습니다.

왜냐하면 크롤링 대상 사이트는:

  • 내가 통제할 수 없는 외부 시스템이고
  • 언제든 구조가 바뀔 수 있고
  • 속도도 일정하지 않고
  • 요청 제한 정책도 있을 수 있기 때문입니다

그래서 안정화는 선택이 아니라 필수입니다.


2. 크롤링에서 자주 발생하는 문제 유형

안정화 전략을 이해하려면 먼저 어떤 문제가 자주 발생하는지 알아야 합니다.


2-1. 로딩 타이밍 문제

예:

  • 페이지는 열렸지만 원하는 요소는 아직 안 나타남
  • 버튼 클릭 후 DOM이 늦게 갱신됨
  • 스크롤 후 추가 데이터가 늦게 붙음

이건 Selenium에서 가장 흔한 문제 중 하나입니다.


2-2. 구조 불일치 문제

예:

  • 대부분 카드에는 가격이 있는데 일부 카드만 없음
  • 어떤 페이지는 배너가 끼어 있어서 구조가 다름
  • 일부 항목은 옵션 정보가 빠져 있음

즉, 모든 데이터가 동일한 형태라고 가정하면 위험합니다.


2-3. 네트워크 / 응답 문제

예:

  • 요청이 느림
  • 타임아웃
  • 일시적인 500 에러
  • 연결 불안정

이건 requests 기반 크롤링에서 특히 자주 만납니다.


2-4. 차단 / 접근 제한 문제

예:

  • 너무 빠른 요청
  • 비정상적인 반복 접근
  • 헤더가 너무 비어 있음
  • 짧은 시간에 과도한 클릭/스크롤

이런 경우 사이트가 봇처럼 판단할 수 있습니다.


2-5. 코드 자체 문제

예:

  • 잘못된 selector
  • 리스트 길이 불일치
  • None 처리 누락
  • 반복 중간에 예외 발생

즉, 안정화는 웹사이트 문제뿐 아니라
내 코드가 예외 상황을 제대로 처리하느냐와도 깊게 연결됩니다.


3. time.sleep()은 무엇인가?

3-1. 정의

time.sleep()은
파이썬 코드 실행을 일정 시간 멈추는 함수입니다.

예:

import time

time.sleep(2)
 

이 코드는 2초 동안 프로그램을 멈춥니다.


3-2. 왜 크롤링에서 자주 쓰일까?

크롤링에서는 너무 빠르게 다음 단계로 넘어가면 문제가 생길 수 있습니다.

예:

  • 페이지 이동 직후 아직 요소가 안 뜸
  • 버튼 클릭 후 결과 목록이 안 바뀜
  • 스크롤 직후 추가 로딩이 덜 끝남

이때 잠깐 기다리는 용도로 자주 씁니다.


3-3. 가장 쉬운 대기 방법

time.sleep()의 장점은 단순하다는 것입니다.

예:

driver.get(url)
time.sleep(3)
 

이렇게 하면 일단 3초 기다리므로
페이지가 어느 정도 뜨는 시간을 벌 수 있습니다.

즉, 초반 실습에서는 가장 이해하기 쉬운 대기 방식입니다.


4. time.sleep()의 한계

4-1. 너무 많이 기다릴 수 있다

페이지가 0.5초 만에 로딩돼도 무조건 3초 기다립니다.

즉, 전체 크롤링 속도가 느려집니다.


4-2. 너무 적게 기다릴 수 있다

반대로 어떤 페이지는 5초 걸리는데 2초만 기다리면 실패합니다.

즉, 고정된 시간 대기는 환경 변화에 약합니다.


4-3. 왜 실무에서는 불완전한가?

실제 웹페이지 속도는 항상 일정하지 않습니다.
그래서 sleep(2)가 오늘은 되고 내일은 안 될 수 있습니다.

즉, time.sleep()은 단순하고 편하지만
정확히 무엇을 기다리는지 모른 채 그냥 시간을 버는 방식입니다.

그래서 Selenium에서는 더 나은 대기 방식이 등장합니다.


5. Selenium의 대기(wait)란 무엇인가?

Selenium에서는 단순 시간 지연보다
“특정 조건이 만족될 때까지 기다리는 방식” 이 중요합니다.

이걸 보통 wait라고 부릅니다.

크게 보면 다음 두 개가 자주 언급됩니다.

  • Implicit Wait
  • Explicit Wait

6. Implicit Wait란 무엇인가?

6-1. 정의

Implicit Wait는
Selenium이 요소를 찾을 때, 바로 없다고 실패하지 말고
일정 시간 동안 기다려 보라는 설정입니다.

예:

driver.implicitly_wait(5)
 

이렇게 하면 Selenium이 요소를 찾을 때 최대 5초 정도 기다릴 수 있습니다.


6-2. 어떻게 이해하면 좋을까?

이건 브라우저에게 이렇게 말하는 것과 비슷합니다.

“요소를 바로 못 찾더라도, 조금 기다렸다가 다시 찾아봐.”

즉, 기본적인 완충 장치입니다.


6-3. 장점

  • 설정이 간단하다
  • 코드가 짧다
  • 초보자에게 이해가 쉽다

6-4. 한계

  • “무슨 조건을 기다리는지” 세밀하게 지정할 수 없다
  • 모든 탐색에 공통 적용되므로 비효율적일 수 있다
  • 복잡한 동적 페이지에서는 부족할 수 있다

즉, Implicit Wait는 기본 안전장치 정도로 이해하면 좋고,
실전에서는 Explicit Wait가 더 강력합니다.


7. Explicit Wait란 무엇인가?

7-1. 정의

Explicit Wait는
특정 조건이 만족될 때까지 기다리는 방식입니다.

예:

  • 요소가 나타날 때까지 기다리기
  • 버튼이 클릭 가능할 때까지 기다리기
  • 특정 텍스트가 보일 때까지 기다리기

즉, 단순히 “몇 초 쉬어라”가 아니라
“원하는 상태가 될 때까지 기다려라” 입니다.


7-2. 왜 더 좋은가?

이 방식은:

  • 너무 일찍 넘어가는 문제를 줄이고
  • 필요 이상 오래 기다리지 않고
  • 실제 동작 조건과 연결되기 때문에

더 안정적이고 효율적입니다.

즉, 실전 Selenium에서는 Explicit Wait가 매우 중요합니다.


8. Explicit Wait 기본 형태

예:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "price"))
)
 

이 코드는 다음 의미를 가집니다.

  • 최대 10초 동안 기다리되
  • class="price" 요소가 나타나면
  • 그 즉시 반환하라

즉, “시간”이 아니라 “조건”을 기다리는 방식입니다.


9. 자주 쓰는 Explicit Wait 조건들

9-1. presence_of_element_located

요소가 DOM에 존재할 때까지 기다립니다.

예:

EC.presence_of_element_located((By.CSS_SELECTOR, ".car-item"))
 

주의할 점은 “존재”만 보는 것이지,
반드시 화면에 보여야 한다는 뜻은 아닐 수 있습니다.


9-2. visibility_of_element_located

요소가 실제로 보일 때까지 기다립니다.

예:

EC.visibility_of_element_located((By.CSS_SELECTOR, ".price"))
 

이건 사용자 화면에서 볼 수 있는 상태를 기다릴 때 유용합니다.


9-3. element_to_be_clickable

요소가 클릭 가능한 상태가 될 때까지 기다립니다.

예:

EC.element_to_be_clickable((By.CSS_SELECTOR, ".next-page"))
 

버튼 클릭 전에 아주 자주 씁니다.


9-4. presence_of_all_elements_located

여러 요소가 나타날 때까지 기다립니다.

예:

EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".car-item"))
 

목록형 구조에서 자주 유용합니다.


10. 언제 sleep을 쓰고, 언제 wait를 쓸까?

이건 실전에서 매우 중요합니다.

time.sleep()

  • 간단한 테스트용
  • 아주 짧은 완충용
  • 구조 파악 초반 임시 확인용

Explicit Wait

  • 요소 등장을 기다릴 때
  • 클릭 가능 상태를 기다릴 때
  • 동적 로딩 대응할 때
  • 반복 수집 코드 안정화할 때

즉, 처음 실습에서는 sleep이 쉬울 수 있지만,
실전 안정화 코드로 갈수록 Explicit Wait 비중이 높아지는 것이 자연스럽습니다.


11. try-except는 왜 크롤링에서 거의 필수인가?

크롤링은 외부 환경을 다루기 때문에
예외가 정말 자주 발생합니다.

예:

  • 요소를 못 찾음
  • 텍스트 변환 실패
  • 링크 누락
  • 특정 카드 구조 이상
  • 페이지 전환 실패

이때 try-except가 없으면
중간에 한 번 에러가 나서 전체 수집이 멈출 수 있습니다.

즉, try-except는 단순 문법이 아니라
전체 크롤링을 끝까지 살아남게 하는 안전장치입니다.


12. 어떤 부분에 try-except를 둘까?

12-1. 카드 하나 처리 단위

가장 흔한 패턴은
반복문 안에서 각 아이템 하나를 처리할 때 예외를 잡는 것입니다.

예:

for item in items:
try:
brand = item.find_element(By.CSS_SELECTOR, ".brand").text
price = item.find_element(By.CSS_SELECTOR, ".price").text
cars.append({"brand": brand, "price": price})
except Exception as e:
print("매물 하나 처리 실패:", e)
continue
 

이렇게 하면 한 카드가 실패해도
다음 카드로 넘어갈 수 있습니다.


12-2. 페이지 단위

페이지 번호를 넘기며 수집할 때는
페이지 단위로 예외를 잡을 수도 있습니다.

예:

  • 1페이지는 성공
  • 2페이지는 실패
  • 3페이지는 계속 진행

이런 흐름이 가능해집니다.


12-3. 네트워크 요청 단위

requests 기반이라면
요청 자체를 try-except로 감쌀 수 있습니다.

즉, 예외 범위를 어디까지 둘지는
“어디까지 실패해도 전체를 계속 돌릴 것인가”와 연결됩니다.


13. 모든 예외를 무조건 무시하면 안 되는 이유

초보자는 종종 이렇게 쓰기도 합니다.

try:
...
except:
pass
 

이건 매우 위험할 수 있습니다.

왜냐하면:

  • 무슨 오류가 났는지 전혀 모르고
  • 데이터가 빠져도 눈치채기 어렵고
  • 구조가 깨졌는데 계속 진행해버릴 수 있기 때문입니다

즉, 예외를 잡더라도
최소한 무엇이 실패했는지는 알 수 있게 해야 합니다.

예:

try:
...
except Exception as e:
print("오류 발생:", e)
 

이렇게라도 남겨야 디버깅이 가능합니다.


14. continue와 break는 어떻게 다르게 쓸까?

예외 처리와 함께 자주 나오는 개념입니다.

continue

이번 반복만 건너뛰고 다음으로 넘어갑니다.

예:

for item in items:
try:
...
except Exception as e:
print("건너뜀:", e)
continue
 

즉, 하나 실패해도 전체는 계속 갑니다.


break

반복 자체를 종료합니다.

예:

for page in range(1, 100):
try:
...
except Exception as e:
print("치명적 오류:", e)
break
 

즉, 더 진행하면 의미 없다고 판단할 때 멈춥니다.


어떻게 판단하면 좋을까?

  • 카드 하나 누락 → continue가 자연스러울 수 있음
  • 페이지 전체 구조가 무너짐 → break가 더 적절할 수 있음
  • 로그인 만료 / 차단 감지 → 멈추는 것이 나을 수 있음

즉, 예외를 만났을 때
무조건 계속 갈지, 멈출지의 판단도 중요합니다.


15. 로깅(logging) 감각은 왜 중요한가?

크롤링이 길어질수록
단순 print()만으로는 흐름을 파악하기 어려울 수 있습니다.

예를 들어 이런 정보는 남겨두는 것이 좋습니다.

  • 몇 페이지째 처리 중인지
  • 몇 개 매물을 수집했는지
  • 어떤 URL에서 실패했는지
  • 어떤 항목이 누락됐는지
  • 언제 시작했고 언제 끝났는지

예:

print(f"{page}페이지 처리 중")
print(f"현재까지 수집 개수: {len(cars)}")
print(f"실패 URL: {url}")
 

이런 로그는 디버깅뿐 아니라
실행 상태를 추적하는 데도 매우 중요합니다.

즉, 안정적인 크롤링은
단순히 결과만 저장하는 것이 아니라
과정도 추적 가능해야 합니다.


16. 재시도(retry) 개념은 왜 중요할까?

실패가 항상 영구적인 것은 아닙니다.
어떤 실패는 잠깐 네트워크가 느렸거나, 로딩이 늦어서 생길 수 있습니다.

즉:

  • 한 번 실패했지만
  • 다시 시도하면 될 수도 있습니다

이런 상황에서 재시도 개념이 중요합니다.

예를 들어:

  • 요청 타임아웃
  • 일시적 응답 실패
  • 버튼 클릭 직전 로딩 지연

은 다시 시도해서 해결될 수 있습니다.

처음 단계에서는 복잡한 retry 라이브러리까지 몰라도 괜찮지만,
“모든 실패가 영구 실패는 아니다” 라는 감각은 꼭 필요합니다.


17. 크롤링 매너란 무엇인가?

이건 단순 예절 문제가 아닙니다.
실전 전략과도 깊게 연결됩니다.

17-1. 의미

크롤링 매너는
대상 사이트에 과도한 부담을 주지 않고,
불필요한 차단이나 문제를 일으키지 않도록 조심하는 태도와 방법을 뜻합니다.


17-2. 왜 중요한가?

웹사이트는 사람이 보는 서비스를 위해 운영됩니다.
내 크롤러가 너무 공격적으로 요청을 보내면:

  • 서버에 부담을 주고
  • 서비스에 악영향을 줄 수 있고
  • 차단되거나 법적/운영적 문제가 생길 수 있습니다

즉, 크롤링 매너는 단순한 예절이 아니라
지속 가능한 수집을 위한 기본 조건입니다.


18. 서버 부하를 줄이는 기본 방법

18-1. 요청 사이 간격 두기

너무 빠르게 연속 요청하지 않도록 간격을 둡니다.

예:

import time
time.sleep(1)
 

18-2. 불필요한 요청 줄이기

같은 페이지를 계속 새로 열지 않기
이미 수집한 데이터는 다시 요청하지 않기
필요한 페이지만 요청하기


18-3. 전체 규모를 조절하기

처음부터 수만 건을 긁지 말고:

  • 작은 범위로 테스트
  • 구조 확인 후 점진적 확장

이 더 안전합니다.


19. 너무 규칙적인 요청도 문제일 수 있다

사이트는 봇 패턴을 감지할 수 있습니다.

예:

  • 정확히 0.1초마다 요청
  • 동일한 패턴 반복
  • 너무 일정한 클릭 흐름
  • 너무 빠른 페이지 넘김

즉, 사람처럼 보이지 않는 패턴은 차단 위험이 커질 수 있습니다.

그래서 실제로는
너무 기계적인 속도를 피하고,
적절한 간격과 완충을 두는 것이 중요합니다.


20. 헤드리스(headless) 실행은 무엇인가?

Selenium에서는 브라우저 창을 실제로 띄우지 않고
백그라운드처럼 실행하는 방식을 headless 라고 합니다.

예:

  • 화면 없이 브라우저 자동 실행
  • 서버 환경에서 사용
  • 배포 환경에서 자주 사용

이건 매우 유용하지만,
일부 사이트에서는 일반 브라우저보다 더 쉽게 탐지하거나 다르게 반응할 수도 있습니다.

즉, headless는 성능과 편의성에 장점이 있지만
항상 만능은 아니라는 정도는 알아두면 좋습니다.


21. 크롤링 안정화에서 “요소가 없을 수 있음”을 항상 생각해야 한다

실전 크롤링에서 가장 중요한 태도 중 하나입니다.

예:

  • 가격 없음
  • 주행거리 없음
  • 링크 없음
  • 이미지 없음
  • 상세 스펙 없음

이건 이상한 일이 아니라 흔한 일입니다.

그래서 코드는 보통 이렇게 생각해야 합니다.

  • “이 값이 반드시 있다”가 아니라
  • “없을 수도 있다”

즉, 안정적인 크롤링은
완벽한 데이터만 상정하지 않는 코드에서 시작합니다.


22. requests 기반 크롤링에서도 안정화는 중요하다

안정화는 Selenium 전용 개념이 아닙니다.

requests를 쓸 때도 마찬가지입니다.

예:

  • 상태 코드 확인
  • timeout 설정
  • 응답 없을 때 재시도
  • 인코딩 문제 확인
  • 너무 빠른 요청 방지

즉, 안정화는
웹 수집 전반의 공통 주제입니다.


23. 범용적으로 꼭 익혀야 하는 안정화 사고방식

23-1. 외부 시스템은 항상 불안정할 수 있다

대상 사이트는 내 코드처럼 내가 통제하는 시스템이 아닙니다.
따라서:

  • 느릴 수도 있고
  • 구조가 바뀔 수도 있고
  • 일시적으로 실패할 수도 있습니다

이걸 기본 전제로 깔아야 합니다.


23-2. 예외는 특별한 일이 아니라 기본 시나리오다

크롤링에서는 예외가 생기는 것이 자연스럽습니다.
중요한 건 “예외가 없게 만들기”보다
예외가 생겨도 전체 수집이 망가지지 않게 하는 것입니다.


23-3. 기다림은 시간보다 조건 중심이 좋다

무조건 sleep(5)보다
“이 요소가 나올 때까지” 기다리는 방식이 더 안정적이고 효율적입니다.


23-4. 과정이 보여야 나중에 고칠 수 있다

로그를 남기지 않으면:

  • 어디서 실패했는지
  • 얼마나 수집됐는지
  • 어느 페이지부터 꼬였는지

알 수 없습니다.

즉, 안정화는 디버깅 가능성과도 연결됩니다.


23-5. 크롤링 매너는 결국 내 코드도 보호한다

너무 공격적으로 수집하면 사이트뿐 아니라
내 수집 코드도 차단당하거나 실패하게 됩니다.

즉, 매너는 윤리 문제이기도 하지만
동시에 성공률을 높이는 전략이기도 합니다.