Puppeteer에서 무한스크롤 구현하기 – 끝까지 내려가는 법

JEONGYUJIN·2025년 5월 19일

경험정리

목록 보기
3/7

📌 들어가며

SPA 구조로 된 웹사이트에서 데이터를 크롤링할 때 흔히 마주치는 문제:
바로 무한스크롤(infinite scroll)!!

예를 들어 쿠팡 상품 리뷰 페이지를 보면, 처음에 10개 정도의 리뷰만 보이고, 스크롤을 아래로 내릴 때마다 추가로 로딩된다.
이 구조에서는 단순히 page.content()로는 전체 리뷰를 긁을 수 없다.
Puppeteer로 직접 "스크롤"을 해줘야 한다.


🛠️ 무한스크롤을 위한 Puppeteer 기본 설정

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();

await page.goto(url, {
  waitUntil: 'networkidle2' // 네트워크 요청이 거의 끝났을 때까지 기다림
});
  • waitUntil: 'networkidle2'는 페이지 내 JS 비동기 요청들이 마무리되었음을 기다리는 전략이다.
  • 리뷰 탭 같은 CSR 영역에서도 초기 렌더링은 보장할 수 있다.

🧭 렌더링 감지 전략

await page.waitForSelector('.sdp-review__article__list'); // 리뷰 리스트 DOM이 뜰 때까지 기다림
await page.waitForTimeout(1000); // 또는 UI가 안정화되도록 약간 대기
  • DOM이 렌더링되기 전에 접근하면 에러가 난다.
  • CSR 구조에서는 뷰가 완전히 그려진 후 크롤링해야 한다.

⬇️ 무한스크롤 로직 구현 (기본 구조)

let previousHeight = await page.evaluate('document.body.scrollHeight');

while (true) {
  await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
  await page.waitForTimeout(1000);

  const newHeight = await page.evaluate('document.body.scrollHeight');
  if (newHeight === previousHeight) break;
  previousHeight = newHeight;
}
  • 핵심은 scrollHeight를 계속 비교하는 것.
  • 새 콘텐츠가 로딩되면 페이지 높이가 늘어난다.
  • 더 이상 높이가 증가하지 않으면, 스크롤 종료 조건으로 판단한다.

🧠 고급 전략: Skeleton UI & Lazy Load 감지

  • 어떤 사이트는 데이터를 늦게 렌더링하는데, 이걸 Skeleton UI로 표시한다.
  • 이때는 단순 scrollHeight 비교로는 부족할 수 있고, 리뷰 DOM이 일정 개수 이상 쌓였는지로 판단하기도 한다.
let maxRetries = 10;
let retryCount = 0;

while (retryCount < maxRetries) {
  const beforeCount = await page.$$eval('.sdp-review__article__list > li', els => els.length);

  await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
  await page.waitForTimeout(1500);

  const afterCount = await page.$$eval('.sdp-review__article__list > li', els => els.length);
  if (afterCount === beforeCount) {
    retryCount++;
  } else {
    retryCount = 0;
  }
}

✅ 이런 구조는 리뷰가 1500개에 못 미치는 경우에도 안정적으로 종료할 수 있게 도와준다.


🖼 디버깅 팁 – 페이지 스냅샷 찍기

await page.screenshot({ path: 'loaded.png', fullPage: true });
  • 실제로 리뷰가 로딩되었는지 눈으로 확인할 수 있다.
  • 네트워크가 느리거나 봇 감지가 있을 때, 어디서 멈췄는지도 파악 가능

💡 IntersectionObserver? 참고만 하자

  • JS 프론트엔드에서는 무한스크롤 구현에 IntersectionObserver를 쓰기도 한다.
  • 하지만 Puppeteer는 실제 브라우저 환경을 따라 하므로, scrollTo() 방식이 훨씬 확실하고 실전적이다.

✍️ 마무리

무한스크롤을 처리하는 건 단순한 반복문 이상의 논리다.
로딩 타이밍, 렌더링 완료, 종료 조건 등 다양한 요소를 고려해야 한다.

Puppeteer를 통해 리뷰 데이터를 수집하면서 느낀 점은
“실제 브라우저가 하는 일”을 하나하나 따라 하는 게 결국 크롤링의 본질이라는 것이다.


profile
일단 하고 보자 (펠리컨적 마인드 ㅠㅠ)

0개의 댓글