YES24 국내도서 베스트셀러 웹사이트 크롤링
리브로 몬도에서는 YES24의 국내도서 베스트셀러를 크롤링해서 메인페이지에서 보여주고 있습니다.
크롤링(Crawling)은 웹 페이지에서 데이터를 자동으로 수집하는 기술을 말합니다.
보통 웹 크롤러 또는 스파이더라고 불리는 프로그램이 웹사이트를 방문해 페이지의 내용을 읽고, 필요한 정보를 수집하는 방식으로 작동합니다.
검색 엔진에서 사용되는 크롤러가 가장 대표적인 예로, 구글이나 네이버 같은 검색 엔진은 크롤러를 통해 인터넷 상의 다양한 웹 페이지를 탐색하고 인덱싱하여 검색 결과에 표시합니다.
크롤링 과정은 다음과 같이 이루어집니다:
크롤러는 탐색할 첫 번째 URL을 정해두고 해당 페이지로 접근합니다. 이를 시드(seed) URL이라고도 부릅니다.
크롤러는 해당 URL에서 HTML 페이지를 다운로드하고, 페이지 내부의 텍스트, 이미지, 링크 같은 데이터를 수집합니다.
크롤러는 페이지 내에 포함된 다른 링크를 추적하여 연관된 페이지로 이동하며 데이터를 계속해서 수집합니다. 이를 통해 웹 상의 수많은 페이지를 자동으로 탐색할 수 있습니다.
수집한 데이터를 저장한 후, 이를 분석하거나 가공하여 원하는 형태로 변환합니다. 예를 들어, 검색 엔진은 이 데이터를 분석해 웹 페이지의 내용을 인덱싱하거나 특정 키워드와 연관된 정보를 제공할 수 있습니다.
이제 코드를 보며 웹 크롤링을 하는 방법을 알아봅시다.
axios: HTTP 요청을 보내고 응답을 처리하는 데 사용됩니다.
여기서는 웹 페이지의 HTML을 가져오기 위해 GET 요청을 보냅니다.
cheerio: jQuery와 비슷한 문법을 사용하여 HTML을 파싱하고 DOM 탐색을 쉽게 할 수 있게 도와줍니다.
가져온 HTML 데이터를 분석하고, 원하는 정보를 추출하는 데 사용됩니다.
우선 전체 코드입니다.
// route.ts
export const dynamic = "force-dynamic";
export const revalidate = 0;
import axios from "axios";
import cheerio from "cheerio";
export const GET = async () => {
const { data } = await axios.get([타겟으로 할 URL]);
const $ = cheerio.load(data);
const list: string[] = [];
$("li[data-goods-no]").each((idx, el) => {
const title = $(el).find(".gd_name").text().trim();
list.push(title);
});
return Response.json(list);
};
export const dynamic = "force-dynamic";
export const revalidate = 0;
이 부분은 Next.js에서 동적 데이터 처리를 위한 설정입니다. 여기서 dynamic = "force-dynamic"
은 데이터를 강제로 동적으로 처리하겠다는 의미이고, revalidate = 0
은 데이터가 매번 요청될 때마다 갱신되도록 설정합니다.
따라서, 이 설정 덕분에 크롤링 결과가 항상 최신 정보를 반영하게 됩니다.
const $ = cheerio.load(data);
가져온 HTML 데이터를 cheerio.load()
를 통해 $
라는 변수로 저장합니다. $
는 jQuery와 비슷한 방식으로 DOM 요소를 탐색하고 조작할 수 있게 해줍니다. 이제 HTML 문서 내에서 원하는 데이터를 쉽게 추출할 수 있습니다.
$("li[data-goods-no]").each((idx, el) => {
const title = $(el).find(".gd_name").text().trim();
list.push(title);
});
$("li[data-goods-no]")
는 YES24 웹페이지에서 각 상품을 나타내는 <li>
태그를 선택합니다.
data-goods-no
속성을 가진 <li>
태그는 각각 베스트셀러 책을 나타냅니다.
each()
메서드를 사용해 각 <li>
태그에서 책 제목을 추출합니다.
$(el).find(".gd_name")
: 각 <li>
태그 내부에서 책 제목을 담고 있는 .gd_name
클래스를 가진 요소를 찾습니다.
.text().trim()
: 해당 요소의 텍스트를 가져오고, trim()
을 사용해 불필요한 공백을 제거합니다.
추출한 제목은 list 배열에 추가됩니다.
위의 코드를 통해 YES24의 베스트셀러 목록을 자동으로 크롤링하고, 원하는 정보를 추출해 API 응답으로 제공하는 방식을 구현할 수 있습니다.
getBestBookTitles
는 크롤링한 책 제목 리스트를 얻는 함수입니다.
const getBestBookTitles = async (): Promise<string[]> => {
const res = await fetch([크롤링한 데이터를 fetch할 route URL]);
const data = await res.json();
return data;
};
fetchBookData
는 카카오 검색 API를 사용하여 원하는 도서 정보를 받아오는 함수입니다.
카카오 검색 API 공식 문서
export const fetchBookData = async (
title: string,
size?: number,
page?: string
): Promise<KakaoBookResponse> => {
const res = await fetch(
`https://dapi.kakao.com/v3/search/book?query=${title}&size=${size}&page=${page}`,
{
headers: {
Authorization: `KakaoAK ${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}`,
},
}
);
const data = await res.json();
return data;
};
getBestBooks
는 앞서 설명한 두 가지 함수를 조합하여 베스트셀러 도서 정보를 수집하는 함수입니다.
export const getBestBooks = async (): Promise<BookResponseType[]> => {
const bestBookTitles = await getBestBookTitles();
const data = await Promise.all(
bestBookTitles.map((title) => fetchBookData(title, 1))
);
const bookData = data
.filter((d) => d.meta.total_count > 0)
.map((d) => d.documents[0]);
return bookData;
};
이 함수의 동작 방식은 다음과 같습니다
베스트셀러 제목 가져오기: 먼저, getBestBookTitles
함수를 호출해 크롤링된 베스트셀러 도서 제목들을 가져옵니다. 이 제목들은 bestBookTitles
배열에 저장됩니다.
카카오 API로 도서 정보 검색: bestBookTitles.map((title) => fetchBookData(title, 1))
구문을 통해 각 도서 제목에 대해 카카오 API를 호출합니다. Promise.all
을 사용하여 여러 개의 비동기 요청을 병렬적으로 처리합니다. 이때, size
를 1로 설정하여 각 책 제목에 대해 하나의 도서 정보만 받습니다.
필터링 및 변환: 카카오 API의 응답 중, 도서 검색 결과가 있는 경우(meta.total_count > 0)
에만 데이터를 필터링한 후, 첫 번째 도서 정보(documents[0])
를 추출합니다.
최종 도서 목록 반환: 최종적으로, 추출된 도서 정보를 bookData
배열에 저장하고 이를 반환합니다. 이 배열에는 각 베스트셀러 도서의 세부 정보가 담겨 있습니다.
이 구조를 통해 효율적으로 국내 도서 베스트셀러의 정보를 수집하고, 화면에 출력할 수 있었습니다.
오늘의 국내도서 종합 베스트 확인하기 !!!