Education/신한투자증권 프로 디지털 아카데미

JavaScript cheerio 사용해서 크롤링 해보기

마이캣호두 2025. 6. 6. 17:37
반응형

신한투자증권 프로 디지털 아카데미 과정

 

 

3. 브라우저를 위한 JavaScript

1. 브라우저를 위한 자바스크립트

 

1) document 객체와 주요 함수

document 객체: 현재 웹 페이지에 대한 진입점 역할을 하며, 문서의 내용, 구조, 스타일 정보에 접근할 수 있다

 

2) 이벤트(Event)

: 이벤트는 웹 페이지 상에서 사용자의 동작이나 브라우저의 특정 활동에 반응하여 발생하는 신호 또는 알림

: 즉, 사용자가 웹 상에서 하는 모든 행동을 말함

 

3) 이벤트 리스너와 이벤트 객체

: 이벤트 리스너는 특정 이벤트가 발생했을 때 호출되는 함수

: 이벤트 객체는 이벤트 type, target요소, 이벤트와 관련된 데이터 포함

 

element.addEventListener

 

element.onClick = () => {}

: 한 번만 등록이 가능하고 복수의 이벤트 핸들러를 등록하는 것이 불가하기 때문에 addElementListener 를 사용하는 것을 표준으로 한다

 

<button onclick="sayHi()">Click me</button>

: 간단하지만 코드가 길어지는 경우 유지보수가 어렵다는 단점이 있다

 

연습문제)

input tag를 두 개 만들어서 두 input이 입력될때마다 input의 합이 출력되는 함수를 작성하여라.

const input1 = document.getElementById('input1');
const input2 = document.getElementById('input2');
const result = document.getElementById('result');

function onChangeInput(e){
    const input1Value = isNaN(parseFloat(input1.value)) ? 0 : parseFloat(input1.value);
    const input2Value = isNaN(parseFloat(input2.value)) ? 0 : parseFloat(input2.value);

    result.innerHTML = input1Value + input2Value;
}

input1.addEventListener('input', onChangeInput);
input2.addEventListener('input', onChangeInput);
// html
  <h2>숫자 합 계산기</h2>

  <input type="number" id="input1" placeholder="숫자 1">
  <input type="number" id="input2" placeholder="숫자 2">
  <p>합: <span id="result">0</span></p>

  <script src="js1.js"></script>

 

4) 이벤트 전파 - Event Propagation

: 이벤트가 발생했을 때 DOM 트리를 통해 이벤트가 전달되는 방식

  • 캡처링 단계: 이벤트가 최상위 노드에서부터 이벤트 타깃까지 전달 - 브라우저가 인지함
  • 타깃 단계: 이벤트가 타깃 요소에서 처리 - 타깃에서부터 리스너 함수가 실행
  • 버블링 단계: 이벤트가 타깃 요소에서부터 다시 최상위 노드까지 올라감 - 부모 노드의 이벤트 리스너 호출로 전파
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');

function onClickEvent(e) {
    e.stopPropagation();
    console.log('클릭 이벤트');
}

box1.addEventListener('click', onClickEvent);
box2.addEventListener('click', onClickEvent);
box3.addEventListener('click', onClickEvent);
  • box3(leaf node)을 클릭하게 되면 최상위 노드인 box1까지 이벤트가 버블링 되어 이벤트 핸들러가 3개 실행되는 것을 볼 수 있다→ e.stopPropagation() 을 활용해 이벤트 전파를 막을 수 있다 = 이벤트 중첩 방지

 

5) 이벤트 기본 처리 변경– preventDefault

 

: form의 submit button 같은 경우 기본적인 이벤트 리스너(default event method)가 등록되어 있는데, 이를 중단하기 위해 preventDefault( ) 사용 - 브라우저의 기본 동작 자체를 막는 것

 

6) 통신을 위한 fetch 함수

: 웹에서 데이터를 가져오거나 보내기 위한 자바스크립트의 내장 함수

  • 기본구조: fetch(url).then().catch()

  • request 의 4가지 method: GET, POST, PUT, DELETE

 

4. 웹 통신의 이해

1. 웹 통신이란

 

1) TCP vs UDP

 

TCP: 응답이 있는 통신 - 대부분의 웹 통신, 안전한 연결

UDP: 응답이 없는 통신 - 빠른 반응을 요하는 게임, 동영상 스트리밍(Youtube), VoIP, mVoIP

 

2) 웹 통신의 구조

Client는 서버에게 HTTP Request를 보내고 - Response를 받아서 - UI를 그린다

⭐️ 웹은 리퀘스트를 보내면 리스폰스가 온다 !!!

 

 

3) Request의 메소드 (HTTP Method)

  • GET (가져오기)
  • POST (등록하기)
  • PUT (수정하기)
  • DELETE (삭제하기)

 

4) Response Status Code (HTTP Status Code)

: 요청을 받으면 서버는 응답을 줄 의무가 있다

  • 2XX (성공)
  • 3XX (Page Redirection)
  • 4XX (Client Error 요청오류)
  • 5XX (Server Error 응답오류)

 

5) XML/JSON (A.K.A. 메시지 body)

구분 XML(eXtensible Markup Language) JSON(JavaScript Object Notation)
구조 형식 태그 기반 (<tag>value</tag>) 키-값 쌍 ({ "key": "value" })
가독성 상대적으로 낮음 상대적으로 높음
데이터 용량 무겁다 (태그 반복으로 많음) 가볍다 (간결한 문법)
사용 편의성 복잡한 문법, 파싱 어려움 간단한 문법, 파싱 쉬움
브라우저 연동 직접 파싱 어려움 (DOMParser 필요) JS에서 바로 사용 가능 (객체 형태)

 

 

5. Node.js를 이용한 데이터 수집

1. 데이터 수집

 

1) 데이터 수집

웹 통신이란 HTTP Request를 서버에 전달하면 HTTP Response가 서버로부터 오는 것

HTTP Response의 형태는 비교적 정형데이터인 XML<HTML>, JSON, CSV와 비정형 데이터로 나뉨

 

결국, 웹 크롤링이란 웹 요청 - 파싱 단계를 거친다

 

2) Browser 환경 vs Node.js 환경

크롤링은 Node.js 환경에서 주로 하게 된다 - 자동화에 적합, 브라우저가 필요 없음, 백엔드와의 통합에 용이(주기적 수집 + DB 저장)

 

 

2. HTML Parsing 하기

 

1) Parsing을 위한 라이브러리 cheerio

npm init	// Node.js 프로젝트 시작
npm install cheerio	// 패키지 설치 <패키지 이름>

 

package.json 에서 type 부분을 확인하자! - commonjs 로 할지 module 로 할지

항목 CommonJS ESM (ECMAScript Module)
불러오기 const cheerio = require("cheerio") import cheerio from "cheerio"
내보내기 module.exports = ... export default ... / export const ...
실행 시점 동기(require는 즉시 실행) 비동기(import는 lazy-loading 가능)
파일 확장자 .js만 사용해도 됨 .mjs 또는 package.json에 "type": "module" 필요
Node 지원 시기 오래전부터 (기본 방식) ES6 이후 지원, Node.js 12 이상 완전 지원
호환성 대부분의 Node.js 패키지가 이 방식 점점 증가 중이지만, 일부 패키지는 호환 이슈
크롤링 시 영향 require()로 쉽게 빠르게 적용 가능 최신 문법과 import 사용 시 설정 필요

 

CommonJS 방식

 

ESM 방식

 

cheerio 로 크롤링 해보기

 

 

연습문제1)

https://quotes.toscrape.com/

1. 해당 페이지에서 다음을 수집하여라. (quote:string, authorName:string, tags:string[])

2. 전체 페이지를 수집하여 JSON으로 저장하여라. (next 페이지 계속 넘어가서…!)

import * as cheerio from "cheerio";
import * as fs from "fs";

const url = "https://quotes.toscrape.com";

async function main() {
  const resp = await fetch(url);
  const body = await resp.text();
  const $ = cheerio.load(body);

  const quoteTags = $(".quote");
  const result = [];

  for (let i = 0; i < quoteTags.length; i++) {
    const quote1Tag = quoteTags.eq(i);
    const quoteTextTag = quote1Tag.find(".text");
    const quoteAuthorTag = quote1Tag.find(".author");
    const quoteTagTag = quote1Tag.find(".tag");

    // const tagArray = [];
    // for (let i = 0; i < quoteTagTag.length; i++) {
    //   const tag = quoteTagTag.eq(i);
    //   tagArray.push(tag.text());
    // }

    result.push({
      text: quoteTextTag.text(),
      author: quoteAuthorTag.text(),
      //   tag: tagArray,
      tag: quoteTagTag.map((_, el) => $(el).text()).get(),
    });
  }

  await fs.promises.writeFile("quotes.json", JSON.stringify(result, null, 2));
  console.log(JSON.stringify(result, null, 2));
}

main();

 

연습문제1 심화)

1. 저자에 대한 설명도 함께 저장

import * as cheerio from "cheerio";
import * as fs from "fs";

const url = "https://quotes.toscrape.com";

async function main() {
    let currentUrl = url;
    const result = [];

    while (currentUrl) {
        const resp = await fetch(currentUrl);
        const body = await resp.text();
        const $ = cheerio.load(body);

        const quoteTags = $(".quote");

        for (let i = 0; i < quoteTags.length; i++) {
            const quote1Tag = quoteTags.eq(i);
            const quoteTextTag = quote1Tag.find(".text");
            const quoteAuthorTag = quote1Tag.find(".author");
            const quoteTagTag = quote1Tag.find(".tag");

            result.push({
                text: quoteTextTag.text(),
                author: quoteAuthorTag.text(),
                tag: quoteTagTag.map((_, el) => $(el).text()).get(),
            });
        }

        const nextHref = $(".next a").attr("href");
        currentUrl = nextHref ? url + nextHref : null;
    }

    await fs.promises.writeFile("quotes.json", JSON.stringify(result, null, 2));
    console.log(JSON.stringify(result, null, 2));
}

main();

 

반응형