상세 컨텐츠

본문 제목

[TIL] 2024.03.29 puppeteer를 이용한 스크래핑

카테고리 없음

by 재호링 2024. 4. 1. 03:23

본문

1. 개념 파악

먼저 크롤링과 스크래핑의 개념에 대해 알고가자.

나는 이 두 방법이 똑같은 기법이라고 알고 있었는데

어떻게 데이터를 추출하냐에 따라 개념이 달라진다고 볼 수 있다.

  • 크롤링
     개인 혹은 단체에서 필요한 데이터가 있는 웹(Web) 페이지의 구조를 분석하고 파악하여 긁어온다. 여기서 긁어온다는 의미는 모두 그대로 가져오는 것. 이것은 데이터를 추출한다로 설명할 수 있고, 크롤링이라는 행위를 하는 소프트웨어(혹은 프로그램)를 크롤러(Crawler)라고 부른다.
  •  스크래핑
    웹 크롤링과 마찬가지로 특정 웹 사이트에서 원하는 데이터를 자동으로 추출하는 것이지만, 웹 크롤링은 웹 페이지의 링크를 통해 계속해서 정보를 찾아 나가지만, 웹 스크래핑은 특정 웹 사이트에서만 데이터를 추적한다.

2. Puppeteer 및 Cheerio 비교

Puppeteer는 Google Chrome에서 개발한 Node.js 크롤링 라이브러리다.

브라우저에서 하는 동적인 작업들을 Node.js에서 Puppeteer를 통해 할 수 있다.

(Cheerio만 가지고 웹 스크래핑을 한 적 있는데 뎁스를 들어가고 들어가서도 스크래핑이 안되었었다.

동적 방식으로 되어 있는 웹사이트의 경우 Puppeteer를 활용해 크롤링 해줘야한다.)

예를 들어 특정 페이지의 스크린샷 또는 pdf 파일을 생성할 수 있으며, 싱글 페이지 어플리케이션의 크롤링 및 화면 이동, 스크롤, 폼 양식 제출, 키보드 입력 자동화 등 과 같은 다양한 작업도 수행할 수 있기 때문에 특히 동적 페이지에서 강력한 기능을 발휘할 수 있다. 단점으로는 속도면에서는 느리다.

 

Cheerio도 Node.js의 크롤링 라이브러리.

HTML문서를 문자열로 불러들여 쿼리 셀렉터의 선택자 형식으로 데이터를 추출할 수 있는 기능을 제공한다.

Node.js와 크롬 환경에서만 사용할 수 있는 Puppeteer와 다르게 모든 브라우저에서 사용할 수 있으나 요소 선택, 텍스트 가져오기와 같은 기본적인 작업에 국한되어 있다.그러나 Puppeteer에 비해 빠른 속도를 자랑한다.

 

3. Puppeteer 사용하기

 

브라우저 실행
puppeteer 크롬 브라우저 실행
const browser = await puppeteer.launch({headless: false});
 
 
페이지 생성
브라우저에 대한 동작과 데이터를 담을 페이지를 생성
const page = await browser.newPage();
 
크롤링 대상 사이트로 이동
await page.goto('https://www.medisvc.com/hospital/fo/availablebedslist.sd');
 
 
페이지 스크롤 200만큼 이동
page.evaluate(() => { document.body.scrollTop = 200; })

 

위 정의를 통해 내가 가져오고 싶은 웹사이트에 연결해주었다.

그 후 가져오고 싶은 뉴스 제목, 이미지, 텍스트를 가져오고,

이제 여기서 무한스크롤링 함수만 작성해주면 자동화를 시킬 수 있다.

import { Injectable } from '@nestjs/common';
import { CreateNewsDto } from './dto/create-news.dto';
import axios from 'axios';
import cheerioModule from 'cheerio';
import puppeteer from 'puppeteer';

@Injectable()
export class NewsService {
  async getHTML() {
    try {
      const browser = await puppeteer.launch({ headless: false });
      const page = await browser.newPage();
      await page.goto('https://hypebeast.kr/footwear');

      let arr = [];
      while (arr.length < 1) {
        const infos = await page.$$eval('.post-box ', (elements) =>
          elements.map((element) => {
            const title = element.getAttribute('data-title');
            const image = element
              .querySelector('img')
              .getAttribute('data-srcset');
            const excerpt = element
              .querySelector('.post-box-content-excerpt')
              .textContent.trim();
            return { title, image, excerpt };
          }),
        );
        arr.push(infos);
      }
      await page.close();
      await browser.close();
      return arr;
    } catch (error) {
      console.log(error);
      return error;
    }
  }
}

 

원래 10개의 데이터만 가져오던 것을

전체 스크롤을 한 후 데이터 수집하는걸 볼 수 있다.

 // 스크롤링 함수 정의
      async function autoScroll(page) {
        await page.evaluate(async () => {
          await new Promise((resolve, reject) => {
            let totalHeight = 0;
            // 페이지 이동 픽셀 300으로 지정
            const distance = 300;
            // 일정한 간격으로 스크롤 하기 위한 setInterval
            const scrollInterval = setInterval(() => {
              // 현재 body에 있는 총 스크롤 가능한 높이 로드
              const scrollHeight = document.body.scrollHeight;
              window.scrollBy(0, distance);
              // 스크롤 된 높이 업데이트
              totalHeight += distance;
              // 스크롤 된 높이 전체 스크롤한 높이 비교 후
              // 크거나 같으면 모든 페이지 스크롤 한 것으로 판단
              // 스크롤 멈추기
              if (totalHeight >= scrollHeight) {
                clearInterval(scrollInterval);
                resolve(void 0);
              }
            }, 100);
          });
        });
      }

      // 페이지를 스크롤하여 추가 콘텐츠를 로드
      await autoScroll(page);

      // "더 알아보기" 버튼 클릭
      const loadMoreButton = await page.$('.button-load-more');
      if (loadMoreButton) {
        await loadMoreButton.click();

        // 추가 콘텐츠가 로드될 때까지 기다림
        await page.waitForSelector('.post-box', { timeout: 30000 });
      }​

 

이제 이 데이터를 디비에 저장하고 알맞게 뿌려주어야한다.