먼저 크롤링과 스크래핑의 개념에 대해 알고가자.
나는 이 두 방법이 똑같은 기법이라고 알고 있었는데
어떻게 데이터를 추출하냐에 따라 개념이 달라진다고 볼 수 있다.
Puppeteer는 Google Chrome에서 개발한 Node.js 크롤링 라이브러리다.
브라우저에서 하는 동적인 작업들을 Node.js에서 Puppeteer를 통해 할 수 있다.
(Cheerio만 가지고 웹 스크래핑을 한 적 있는데 뎁스를 들어가고 들어가서도 스크래핑이 안되었었다.
동적 방식으로 되어 있는 웹사이트의 경우 Puppeteer를 활용해 크롤링 해줘야한다.)
예를 들어 특정 페이지의 스크린샷 또는 pdf 파일을 생성할 수 있으며, 싱글 페이지 어플리케이션의 크롤링 및 화면 이동, 스크롤, 폼 양식 제출, 키보드 입력 자동화 등 과 같은 다양한 작업도 수행할 수 있기 때문에 특히 동적 페이지에서 강력한 기능을 발휘할 수 있다. 단점으로는 속도면에서는 느리다.
Cheerio도 Node.js의 크롤링 라이브러리.
HTML문서를 문자열로 불러들여 쿼리 셀렉터의 선택자 형식으로 데이터를 추출할 수 있는 기능을 제공한다.
Node.js와 크롬 환경에서만 사용할 수 있는 Puppeteer와 다르게 모든 브라우저에서 사용할 수 있으나 요소 선택, 텍스트 가져오기와 같은 기본적인 작업에 국한되어 있다.그러나 Puppeteer에 비해 빠른 속도를 자랑한다.
3. Puppeteer 사용하기
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('https://www.medisvc.com/hospital/fo/availablebedslist.sd');
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 });
}
이제 이 데이터를 디비에 저장하고 알맞게 뿌려주어야한다.