매일 업데이트
2023-08-06 19:35 10 min

Cheerio를 사용한 웹 스크래핑 소개

웹 스크래핑이란 무엇인가?

웹 스크래핑은 특정 웹사이트에서 정보를 추출하는 데 사용되는 기술입니다. 웹사이트는 HTML을 사용하여 콘텐츠를 구조화하고 표시하며, 잘 구성된 HTML은 데이터 추출을 용이하게 합니다.

웹 스크래퍼는 데이터를 수집 및 모니터링하고 웹사이트의 변경 사항을 추적하는 데 주로 활용됩니다.

Cheerio 사용 전 알아야 할 jQuery 기본 지식

jQuery는 가장 널리 사용되는 JavaScript 라이브러리 중 하나이며, DOM 조작, 이벤트 처리, 애니메이션과 같은 작업을 간소화합니다. Cheerio는 jQuery를 기반으로 구축된 웹 스크래핑 도구로서, jQuery와 유사한 구문과 API를 사용하여 HTML 및 XML 문서를 쉽게 파싱할 수 있도록 합니다.

Cheerio를 사용하기 전에 jQuery를 사용하여 HTML 요소를 선택하는 방법을 이해하는 것이 중요합니다. jQuery는 대부분의 CSS3 선택자를 지원하므로 DOM에서 요소를 쉽게 선택할 수 있습니다. 다음 코드를 살펴보겠습니다.

 $("#container");

위 코드는 ID가 "container"인 HTML 요소를 jQuery를 사용하여 선택합니다. 일반적인 JavaScript로 유사하게 구현하면 다음과 같습니다.

 document.querySelectorAll("#container");

두 코드를 비교하면 jQuery 코드가 더 읽기 쉽다는 것을 알 수 있습니다. 이것이 바로 jQuery의 장점입니다.

jQuery는 HTML 요소를 조작하는 데 유용한 text(), html() 메서드와 DOM을 탐색하는 parent(), sister(), prev(), next() 등의 메서드도 제공합니다.

jQuery의 each() 메서드는 Cheerio 프로젝트에서 자주 사용되며, 객체 및 배열을 반복 처리하는 데 유용합니다. each() 메서드의 구문은 다음과 같습니다.

 $(<element>).each(<array or object>, callback)

여기서 콜백 함수는 배열 또는 객체의 각 요소에 대해 실행됩니다.

Cheerio를 사용하여 HTML 로드하기

Cheerio를 사용하여 HTML 또는 XML 데이터를 파싱하려면 cheerio.load() 메서드를 사용합니다. 아래 예시를 살펴보세요.

 const $ = cheerio.load('<html><body><h1>Hello, world!</h1></body></html>');
console.log($('h1').text())

위 코드는 jQuery의 text() 메서드를 사용하여 <h1> 요소의 텍스트 내용을 추출합니다. load() 메서드의 전체 구문은 다음과 같습니다.

 load(content, options, mode)

content 매개변수는 파싱할 실제 HTML 또는 XML 데이터를 나타냅니다. options는 메서드의 동작을 변경할 수 있는 선택적 객체입니다. 기본적으로 load() 메서드는 html, head, body 요소가 없는 경우 자동으로 추가합니다. 이 동작을 비활성화하려면 mode를 false로 설정해야 합니다.

Cheerio로 Hacker News 스크래핑하기

이 프로젝트에 사용된 코드는 GitHub 저장소에서 MIT 라이선스로 자유롭게 사용할 수 있습니다.

지금까지 배운 내용을 바탕으로 간단한 웹 스크래퍼를 만들어 보겠습니다. Hacker News는 기업가와 혁신가들에게 인기 있는 웹사이트입니다. 또한 페이지 로딩 속도가 빠르고 인터페이스가 간결하며 광고가 없어 웹 스크래핑 기술을 연습하기에 좋은 환경입니다.

먼저 컴퓨터에 Node.js와 Node Package Manager가 설치되어 있는지 확인하십시오. 빈 폴더를 만들고 package.json 파일을 생성한 다음 다음 JSON 코드를 파일에 추가합니다.

 {
"name": "web-scraper",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"cheerio": "^1.0.0-rc.12",
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}

이제 터미널을 열고 다음 명령을 실행합니다.

 npm i

이 명령은 스크래퍼를 구축하는 데 필요한 종속성을 설치합니다. 여기에는 HTML 파싱을 위한 Cheerio, 서버 생성을 위한 ExpressJS, 그리고 파일 변경 사항을 감지하고 서버를 자동으로 재시작하는 Nodemon(개발 종속성)이 포함됩니다.

필요한 기능 설정 및 만들기

index.js 파일을 만들고, "PORT"라는 상수 변수를 선언합니다. PORT 값을 5500 (또는 원하는 다른 포트 번호)으로 설정하고, Cheerio와 Express 패키지를 가져옵니다.

 const PORT = 5500;
const cheerio = require("cheerio");
const express = require("express");
const app = express();

다음으로 url, html, finishedPage라는 세 가지 변수를 정의합니다. url 변수를 Hacker News URL로 설정합니다.

 const url="https://news.ycombinator.com";
let html;
let finishedPage;

이제 브라우저에 표시할 HTML을 반환하는 getHeader() 함수를 생성합니다.

 function getHeader(){
return `
<div style="display:flex; flex-direction:column; align-items:center;">
<h1 style="text-transform:capitalize">Scraper News</h1>
<div style="display:flex; gap:10px; align-items:center;">
<a href="https://www.makeuseof.com/" id="news" onClick='showLoading()'>Home</a>
<a href="https://wilku.top/best" id="best" onClick='showLoading()'>Best</a>
<a href="https://wilku.top/newest" id="newest" onClick='showLoading()'>Newest</a>
<a href="https://wilku.top/ask" id="ask" onClick='showLoading()'>Ask</a>
<a href="https://wilku.top/jobs" id="jobs" onClick='showLoading()'>Jobs</a>
</div>
<p class="loading" style="display:none;">Loading...</p>
</div>
`}

다음으로 브라우저에서 실행할 JavaScript 코드를 반환하는 getScript() 함수를 만듭니다. 이 함수는 인수로 변수 유형을 전달받아야 합니다.

 function getScript(type){
return `
<script>
document.title = "${type.substring(1)}"

window.addEventListener("DOMContentLoaded", (e) => {
let navLinks = [...document.querySelectorAll("a")];
let current = document.querySelector("#${type.substring(1)}");
document.body.style = "margin:0 auto; max-width:600px;";
navLinks.forEach(x => x.style = "color:black; text-decoration:none;");
current.style.textDecoration = "underline";
current.style.color = "black";
current.style.padding = "3px";
current.style.pointerEvents = "none";
})

function showLoading(e){
document.querySelector(".loading").style.display = "block";
document.querySelector(".loading").style.textAlign = "center";
}
</script>`
}

마지막으로, fetchAndRenderPage()라는 비동기 함수를 만듭니다. 이 함수는 Hacker News에서 페이지를 스크래핑하고, Cheerio로 파싱 및 형식화한 다음 클라이언트에게 HTML 코드를 반환합니다.

 async function fetchAndRenderPage(type, res) {
const response = await fetch(`${url}${type}`)
html = await response.text();
}

Hacker News는 다양한 게시물 유형을 제공합니다. 첫 페이지는 "뉴스" 게시물이며, 다른 사용자의 답변을 요청하는 게시물은 "질문", 최신 게시물은 "최신", 구인 게시물은 "작업"으로 분류됩니다.

fetchAndRenderPage() 함수는 인수로 전달된 유형에 따라 Hacker News 페이지에서 게시물 목록을 가져옵니다. 가져오기 작업이 성공하면 html 변수에 응답 텍스트를 할당합니다.

다음으로 함수에 다음 코드를 추가합니다.

 res.set('Content-Type', 'text/html');
res.write(getHeader());

const $ = cheerio.load(html);
const articles = [];
let i = 1;

위 코드에서 set() 메서드는 HTTP 헤더 필드를 설정하고, write() 메서드는 응답 본문 청크를 전송하며, load() 함수는 html을 인수로 받습니다.

다음으로 다음 코드를 추가하여 "titleline" 클래스를 가진 모든 요소의 각 자식을 선택합니다.

 $('.titleline').children('a').each(function(){
let title = $(this).text();
articles.push(`<h4>${i}. ${title}</h4>`);
i++;
})

이 코드에서 각 반복은 대상 HTML 요소의 텍스트 콘텐츠를 가져와 title 변수에 저장합니다.

다음으로, getScript() 함수의 결과를 기사 배열에 추가합니다. 그런 다음 완성된 HTML 코드를 저장할 finishedPage 변수를 생성합니다. 마지막으로, write() 메서드로 finishedPage를 청크로 전송하고, end() 메서드로 응답 프로세스를 종료합니다.

 articles.push(getScript(type))
finishedPage = articles.reduce((c, n) => c + n);
res.write(finishedPage);
res.end();

GET 요청 처리 경로 정의

fetchAndRenderPage 함수 바로 아래에 Express의 get() 메서드를 사용하여 다양한 유형의 게시물에 대한 각각의 경로를 정의합니다. 그런 다음 listen() 메서드를 사용하여 로컬 네트워크의 지정된 포트에 대한 연결을 수신합니다.

 app.get("https://www.makeuseof.com/", (req, res) => {
fetchAndRenderPage('/news', res);
})

app.get("https://wilku.top/best", (req, res) => {
fetchAndRenderPage("https://wilku.top/best", res);
})

app.get("https://wilku.top/newest", (req, res) => {
fetchAndRenderPage("https://wilku.top/newest", res);
})

app.get("https://wilku.top/ask", (req, res) => {
fetchAndRenderPage("https://wilku.top/ask", res);
})

app.get("https://wilku.top/jobs", (req, res) => {
fetchAndRenderPage("https://wilku.top/jobs", res);
})

app.listen(PORT)

위 코드에서 각 get 메서드는 콜백 함수를 포함하며, 이 콜백 함수는 각각의 유형과 res 객체를 인수로 사용하여 fetchAndRenderPage 함수를 호출합니다.

터미널을 열고 npm start를 실행합니다. 서버가 시작되면 브라우저에서 localhost:5500에 접속하여 결과를 확인할 수 있습니다.

축하합니다! 여러분은 Hacker News를 성공적으로 스크래핑하고 외부 API 없이 게시물 제목을 추출했습니다.

웹 스크래핑으로 더 나아가기

Hacker News에서 스크래핑한 데이터를 사용하여 차트, 그래프, 워드 클라우드와 같은 다양한 시각화를 생성하여 더욱 이해하기 쉬운 형태로 인사이트와 트렌드를 제시할 수 있습니다.

또한 사용자 프로필을 스크래핑하여 받은 좋아요, 댓글 등의 요소를 바탕으로 플랫폼에서 사용자 평판을 분석할 수도 있습니다.

저자
Korea

기술 트렌드와 실용적인 팁을 전하는 लेखक입니다.