이 가이드에서는 다음을 배우게 됩니다:
- 사이트의 복잡한 탐색 구조를 식별하는 방법
- 이러한 시나리오를 처리하기 위한 최적의 스크래핑 도구
- 일반적인 복잡한 네비게이션 패턴을 스크래핑하는 방법
자, 시작해 보겠습니다!
사이트의 네비게이션이 복잡한 경우는 언제인가요?
복잡한 네비게이션을 가진 사이트는 개발자로서 우리가 마주해야하는흔한웹 스크래핑 과제입니다. 하지만 정확히 “복잡한 네비게이션”이란 무엇을 의미할까요? 웹 스크래핑에서 복잡한 네비게이션이란 콘텐츠나 페이지에 쉽게 접근할 수 없는 웹사이트 구조를 말합니다.
복잡한 네비게이션 시나리오는 동적 요소, 비동기 데이터 로딩 또는 사용자 주도 상호작용을 수반하는 경우가 많습니다. 이러한 측면은 사용자 경험을 향상시킬 수 있지만, 데이터 추출 과정을 상당히 복잡하게 만듭니다.
복잡한 네비게이션을 이해하는 가장 좋은 방법은 몇 가지 사례를 살펴보는 것입니다:
- 자바스크립트 렌더링 네비게이션: React, Vue.js, Angular 같은 자바스크립트 프레임워크를 활용해 브라우저 내에서 직접 콘텐츠를 생성하는 웹사이트.
- 페이지분할된 콘텐츠: 데이터가 여러 페이지에 걸쳐 있는 사이트입니다. AJAX를 통해 페이지 번호를 기반으로 페이지가 로드될 경우 후속 페이지 접근이 어려워져 더욱 복잡해집니다.
- 무한 스크롤: 사용자가 아래로 스크롤할 때 동적으로 추가 콘텐츠를 불러오는 페이지로, 소셜 미디어 피드나 뉴스 웹사이트에서 흔히 볼 수 있습니다.
- 다단계 메뉴: 중첩된 메뉴 구조로, 여러 번의 클릭이나 마우스 오버 동작이 필요한 사이트(예: 대형 이커머스 플랫폼의 제품 카테고리 트리).
- 대화형 지도 인터페이스: 지도나 그래프에 정보를 표시하는 웹사이트로, 사용자가 이동하거나 확대/축소할 때 데이터 포인트가 동적으로 로드됩니다.
- 탭 또는 아코디언: 콘텐츠가 동적으로 렌더링되는 탭이나 접을 수 있는 아코디언 아래에 숨겨져 있으며, 해당 콘텐츠가 서버에서 반환된 HTML 페이지에 직접 포함되지 않은 페이지.
- 동적 필터 및 정렬 옵션: URL 구조를 변경하지 않고 여러 필터를 적용하면 항목 목록이 동적으로 다시 로드되는 복잡한 필터링 시스템을 갖춘 사이트.
복잡한 탐색 웹사이트 처리용 최고의 스크래핑 도구
복잡한 네비게이션을 가진 사이트를 효과적으로 스크래핑하려면 어떤 도구를 사용해야 하는지 이해해야 합니다. 작업 자체가 본질적으로 어렵고, 올바른 스크래핑 라이브러리를 사용하지 않으면 더욱 어려워질 뿐입니다.
위에서 언급한 복잡한 상호작용의 공통점은 다음과 같습니다:
- 사용자 상호작용이 필요하거나
- 브라우저 내 클라이언트 측에서 실행됩니다.
다시 말해, 이러한 작업들은 자바스크립트 실행이 필요하며, 이는 오직 브라우저만이 수행할 수 있습니다. 따라서 이러한 페이지에는 단순한HTML 파서만으로는의존할 수 없습니다. 대신 Selenium, Playwright, Puppeteer와 같은 브라우저 자동화 도구를 사용해야 합니다.
이러한 솔루션은 브라우저에 프로그래밍 방식으로 특정 웹 페이지 작업을 수행하도록 지시하여 사용자 행동을 모방할 수 있게 합니다. 그래픽 인터페이스 없이 브라우저를 렌더링하여 시스템 자원을 절약할 수 있기 때문에 흔히헤드리스 브라우저라고불립니다.
웹 스크래핑에 가장 적합한 헤드리스 브라우저 도구를 알아보세요.
일반적인 복잡한 네비게이션 패턴 스크래핑 방법
이 튜토리얼 섹션에서는 Python의 Selenium을 사용할 것입니다. 그러나 해당 로직은 Playwright, Puppeteer 또는 기타 브라우저 자동화 도구로도 쉽게 적용할 수 있습니다. 또한Selenium을 사용한 웹 스크래핑의 기본 개념에 이미 익숙하다고 가정하겠습니다.
구체적으로 다음과 같은 일반적인 복잡한 탐색 패턴을 스크래핑하는 방법을 다룹니다:
- 동적 페이지네이션: AJAX를 통해 동적으로 로드되는 페이지네이션 데이터가 있는 사이트.
- ‘더 보기’ 버튼: 일반적인 자바스크립트 기반 탐색 예시.
- 무한 스크롤: 사용자가 아래로 스크롤할 때마다 지속적으로 데이터를 불러오는 페이지.
코딩을 시작해 보세요!
동적 페이지네이션
이 예제의 대상 페이지는 “오스카 수상작 영화: AJAX와 자바스크립트” 스크래핑 샌드박스입니다:

이 사이트는 연도별로 페이지네이션된 오스카 수상 영화 데이터를 동적으로 로드합니다.
이러한 복잡한 탐색을 처리하기 위한 접근 방식은 다음과 같습니다:
- 새 연도를 클릭하면 데이터 로딩이 트리거됩니다(로더 요소가 나타남).
- 로더 요소가 사라질 때까지 기다립니다(데이터가 완전히 로드된 상태).
- 데이터가 포함된 테이블이 페이지에 제대로 렌더링되었는지 확인합니다.
- 데이터가 사용 가능해지면 스크래핑합니다.
자세한 구현 방법은 아래 Selenium을 활용한 Python 코드입니다:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
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.webdriver.chrome.options import Options
# 헤드리스 모드용 Chrome 옵션 설정
options = Options()
options.add_argument("--headless")
# Chrome 웹 드라이버 인스턴스 생성
driver = webdriver.Chrome(service=Service(), options=options)
# 대상 페이지 연결
driver.get("https://www.scrapethissite.com/pages/ajax-javascript/")
# "2012" 페이지네이션 버튼 클릭
element = driver.find_element(By.ID, "2012")
element.click()
# 로더가 더 이상 보이지 않을 때까지 대기
WebDriverWait(driver, 10).until(
lambda d: d.find_element(By.CSS_SELECTOR, "#loading").get_attribute("style") == "display: none;")
# 이제 데이터가 로드되었을 것...
# 페이지에 테이블이 존재할 때까지 대기
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".table")))
# 스크랩한 데이터를 저장할 위치
films = []
# 테이블에서 데이터 스크랩
table_body = driver.find_element(By.CSS_SELECTOR, "#table-body")
rows = table_body.find_elements(By.CSS_SELECTOR, ".film")
for row in rows:
title = row.find_element(By.CSS_SELECTOR, ".film-title").text
nominations = row.find_element(By.CSS_SELECTOR, ".film-nominations").text
awards = row.find_element(By.CSS_SELECTOR, ".film-awards").text
best_picture_icon = row.find_element(By.CSS_SELECTOR, ".film-best-picture").find_elements(By.TAG_NAME, "i")
best_picture = True if best_picture_icon else False
# 스크랩한 데이터 저장
films.append({
"title": title,
"nominations": nominations,
"awards": awards,
"best_picture": best_picture
})
# 데이터 내보내기 로직...
# 브라우저 드라이버 종료
driver.quit()
위 코드의 세부 설명:
- 이 코드는 헤드리스 Chrome 인스턴스를 설정합니다.
- 스크립트는 대상 페이지를 열고 “2012” 페이지네이션 버튼을 클릭하여 데이터 로딩을 트리거합니다.
- Selenium은
WebDriverWait()를사용하여 로더가 사라질 때까지 대기합니다. - 로더가 사라진 후, 스크립트는 테이블이 나타날 때까지 대기합니다.
- 데이터가 완전히 로드된 후, 스크립트는 영화 제목, 후보작, 수상 내역, 그리고 해당 영화가 최우수 작품상을 수상했는지 여부를 스크래핑합니다. 이 데이터를 사전 목록에 저장합니다.
결과는 다음과 같습니다:
[
{
"title": "Argo",
"nominations": "7",
"awards": "3",
"best_picture": true
},
// ...
{
"title": "Curfew",
"nominations": "1",
"awards": "1",
"best_picture": false
}
]
이러한 탐색 패턴을 처리하는 데 항상 단 하나의 최적의 방법이 있는 것은 아닙니다. 페이지의 동작에 따라 다른 옵션이 필요할 수 있습니다. 예시는 다음과 같습니다:
- 특정 HTML 요소가 나타나거나 사라질 때까지 기다리기 위해
WebDriverWait()를 예상 조건과 함께 사용합니다. - AJAX 요청 트래픽을 모니터링하여 새 콘텐츠가 가져오는 시점을 감지합니다. 여기에는 브라우저 로깅 사용이 포함될 수 있습니다.
- 페이지네이션에 의해 트리거되는 API 요청을 식별하고 프로그래밍 방식으로 데이터를 가져오기 위해 직접 요청을 수행합니다(예:
requests라이브러리 사용).
‘더 보기’ 버튼
사용자 상호작용이 포함된 자바스크립트 기반의 복잡한 탐색 시나리오를 표현하기 위해 ‘더 보기’ 버튼 예시를 선택했습니다. 개념은 간단합니다: 항목 목록이 표시되고, 버튼을 클릭하면 추가 항목이 로드됩니다.
이번 대상 사이트는 스크래핑 과정의‘더 보기’ 예제페이지입니다:

이 복잡한 탐색 스크래핑 패턴을 처리하려면 다음 단계를 따르세요:
- ‘더 보기’ 버튼을 찾아 클릭합니다.
- 새 요소가 페이지에 로드될 때까지 기다립니다.
Selenium으로 이를 구현하는 방법은 다음과 같습니다:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
# 헤드리스 모드용 Chrome 옵션 설정
options = Options()
options.add_argument("--headless")
# Chrome 웹 드라이버 인스턴스 생성
driver = webdriver.Chrome(options=options)
# 대상 페이지에 연결
driver.get("https://www.scrapingcourse.com/button-click")
# 초기 상품 수 수집
initial_product_count = len(driver.find_elements(By.CSS_SELECTOR, ".product-item"))
# "더 보기" 버튼 위치 파악 및 클릭
load_more_button = driver.find_element(By.CSS_SELECTOR, "#load-more-btn")
load_more_button.click()
# 페이지의 상품 항목 수가 증가할 때까지 대기
WebDriverWait(driver, 10).until(lambda driver: len(driver.find_elements(By.CSS_SELECTOR, ".product-item")) > initial_product_count)
# 스크랩된 데이터 저장 위치
products = []
# 상품 상세 정보 스크래핑
product_elements = driver.find_elements(By.CSS_SELECTOR, ".product-item")
for product_element in product_elements:
# 상품 상세 정보 추출
name = product_element.find_element(By.CSS_SELECTOR, ".product-name").text
image = product_element.find_element(By.CSS_SELECTOR, ".product-image").get_attribute("src")
price = product_element.find_element(By.CSS_SELECTOR, ".product-price").text
url = product_element.find_element(By.CSS_SELECTOR, "a").get_attribute("href")
# 추출된 데이터 저장
products.append({
"name": name,
"image": image,
"price": price,
"url": url
})
# 데이터 내보내기 로직...
# 브라우저 드라이버 종료
driver.quit()
이 탐색 로직을 처리하기 위해 스크립트는:
- 페이지의 초기 상품 수 기록
- “더 보기” 버튼을 클릭합니다
- 제품 수가 증가할 때까지 대기하여 새 항목이 추가되었음을 확인합니다
이 접근 방식은 로드할 정확한 요소 수를 알 필요가 없기 때문에 스마트하면서도 범용적입니다. 다만 유사한 결과를 달성할 수 있는 다른 방법들도 존재한다는 점을 명심하세요.
무한 스크롤
무한 스크롤은 사용자 참여도를 높이기 위해 많은 사이트에서 사용하는 일반적인 상호작용 방식입니다. 특히 소셜 미디어 및 전자상거래 플랫폼에서 흔히 볼 수 있습니다. 이 경우 대상은 위와 동일한 페이지이지만‘더 보기’ 버튼 대신 무한 스크롤이 적용됩니다:

대부분의 브라우저 자동화 도구(Selenium 포함)는 페이지 상하 스크롤을 직접 수행하는 방법을 제공하지 않습니다. 대신 페이지에서 스크롤 작업을 수행하는 자바스크립트 스크립트를 실행해야 합니다.
핵심은 아래로 스크롤하는 사용자 정의 자바스크립트 스크립트를 작성하는 것입니다:
- 지정된 횟수만큼 스크롤하거나,
- 더 이상 로드할 데이터가 없을 때까지
참고: 각 스크롤마다 새 데이터가 로드되어 페이지의 요소 수가 증가합니다.
이후 새로 로드된 콘텐츠를 스크래핑할 수 있습니다.
Selenium에서 무한 스크롤을 처리하는 방법은 다음과 같습니다:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
# 헤드리스 모드용 Chrome 옵션 설정
options = Options()
# options.add_argument("--headless")
# Chrome 웹 드라이버 인스턴스 생성
driver = webdriver.Chrome(options=options)
# 무한 스크롤이 적용된 대상 페이지에 연결
driver.get("https://www.scrapingcourse.com/infinite-scrolling")
# 현재 페이지 높이
scroll_height = driver.execute_script("return document.body.scrollHeight")
# 페이지의 상품 수
product_count = len(driver.find_elements(By.CSS_SELECTOR, ".product-item"))
# 최대 스크롤 횟수
max_scrolls = 10
scroll_count = 1
# 스크롤 횟수를 10회로 제한
while scroll_count < max_scrolls:
# 아래로 스크롤
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 페이지의 상품 항목 수가 증가할 때까지 대기
WebDriverWait(driver, 10).until(lambda driver: len(driver.find_elements(By.CSS_SELECTOR, ".product-item")) > product_count)
# 상품 수 업데이트
product_count = len(driver.find_elements(By.CSS_SELECTOR, ".product-item"))
# 새 페이지 높이 가져오기
new_scroll_height = driver.execute_script("return document.body.scrollHeight")
# 새 콘텐츠가 로드되지 않은 경우
if new_scroll_height == scroll_height:
break
# 스크롤 높이 업데이트 및 스크롤 횟수 증가
scroll_height = new_scroll_height
scroll_count += 1
# 무한 스크롤 후 제품 상세 정보 수집
products = []
product_elements = driver.find_elements(By.CSS_SELECTOR, ".product-item")
for product_element in product_elements:
# 제품 상세 정보 추출
name = product_element.find_element(By.CSS_SELECTOR, ".product-name").text
image = product_element.find_element(By.CSS_SELECTOR, ".product-image").get_attribute("src")
price = product_element.find_element(By.CSS_SELECTOR, ".product-price").text
url = product_element.find_element(By.CSS_SELECTOR, "a").get_attribute("href")
# 추출된 데이터 저장
products.append({
"name": name,
"image": image,
"price": price,
"url": url
})
# CSV/JSON으로 내보내기...
# 브라우저 드라이버 종료
driver.quit()
이 스크립트는 먼저 현재 페이지 높이 및 제품 수를 확인하여 무한 스크롤을 관리합니다. 그런 다음 스크롤 작업을 최대 10회 반복으로 제한합니다. 각 반복에서 다음을 수행합니다:
- 맨 아래까지 스크롤합니다
- 제품 수가 증가할 때까지 대기(새 콘텐츠가 로드되었음을 나타냄)
- 페이지 높이를 비교하여 추가 콘텐츠가 있는지 감지
스크롤 후 페이지 높이가 변하지 않으면 루프가 종료되며, 이는 더 이상 로드할 데이터가 없음을 의미합니다. 이렇게 복잡한 무한 스크롤 패턴을 처리할 수 있습니다.
훌륭합니다! 이제 복잡한 탐색 구조를 가진 웹사이트 스크래핑의 달인이 되셨습니다.
결론
이 글에서는 복잡한 탐색 패턴을 사용하는 사이트와 Python과 Selenium을 사용하여 이를 처리하는 방법에 대해 배웠습니다. 이는 웹 스크래핑이 어려울 수 있지만,스크래핑 방지 조치로 인해 더욱 어려워질 수 있음을 보여줍니다.
기업들은 데이터의 가치를 이해하고 이를 보호하기 위해 모든 수단을 동원합니다. 그래서 많은 사이트가 자동화된 스크립트를 차단하는 조치를 시행합니다. 이러한 솔루션은 너무 많은 요청 후 IP를 차단하거나, CAPTCHA를 제시하거나, 더 심한 경우를 만들 수 있습니다.
Selenium과 같은 기존 브라우저 자동화 도구는 이러한 제한을 우회할 수 없습니다…
해결책은Scraping Browser와 같은 클라우드 기반 스크래핑 전용 브라우저를 사용하는 것입니다. 이 브라우저는 Playwright, Puppeteer, Selenium 등 다양한 도구와 연동되며, 요청마다 자동으로 IP를 회전시킵니다. 브라우저 지문 인식, 재시도,CAPTCHA 해결 등을 처리할 수 있습니다. 복잡한 사이트를 다루는 동안 차단당하는 일은 이제 잊으세요!