node.js에서 크롤링

Marsboy·2022년 12월 1일
1

개발

목록 보기
1/1
post-thumbnail

서론

개인 프로젝트로 하기 가장 적절한 활동을 손꼽으라고 하면 역시 크롤링인 것 같다. 어떤 활동을 할 것이냐에 따라 무궁무진하게 크롤링을 활용할 수 있기 때문이다. 나는 파이썬과 자바스크립트 환경에서 크롤링하기 위해 다양한 삽질을 했던 기억이 있다.

파이썬을 통해서 크롤링할 때는 셀레니움을 사용했다. 최근에 자바스크립트의 puppeteer와 cheerio를 사용해서 크롤링하는 API를 만들었는데, 사실 기존에는 자바스크립트의 axios & cheerio를 사용해서 카드의 데이터를 크롤링해오는 API를 만들 계획이었다. 레퍼런스가 가장 많았기 때문인데, axios & cheerio를 사용하게 되면서 발생했던 삽질들을 기록하려고 한다.

결국 headless chrome 이슈 때문에 puppeteer & cheerio를 사용해서 크롤링을 구현했다.

axios란?

Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and nodejs with the same codebase). On the server-side it uses the native node.js http module, while on the client (browser) it uses XMLHttpRequests.

Axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트라고 한다. 서버 쪽에서는 node.js에서 http의 모듈을 이용하고, 클라이언트에서는 XMLHttpRequests를 사용한다고 한다.

따라서, axios라는 라이브러리는 node.js와 클라이언트 모두 사용하는 라이브러리라고 할 수 있다.

특징

  • 브라우저를 위해 XMLHttpRequests 생성
  • node.js를 위해 http 요청 생성
  • Promise API를 지원
  • 요청 및 응답 인터셉트
  • 요청 및 응답 데이터 변환
  • 요청 취소
  • JSON 데이터 자동 변환
  • XSRF를 막기위한 클라이언트 사이드 지원

위와 같은 다양한 기능이 있는데, node.js에서 axios를 사용하는 이유는 HTTP 요청을 하기 위해서 주로 사용한다. 클라이언트의 코드를 보면 서버와 데이터를 주고받기 위해서 axios를 애용하는 것을 알 수 있다. 나는 클라이언트에서만 사용하는 라이브러리인 줄 알았으나, node.js 환경에서도 꽤 사용하는 것을 알 수 있었다.

puppeteer란?

Puppeteer is a Node.js library which provides a high-level API to control Chrome/Chromium over the DevTools Protocol. Puppeteer runs in headless mode by default, but can be configured to run in full (non-headless) Chrome/Chromium.

puppeteer는 node.js의 크롬 및 크로미움을 다루게 해주는 API를 제공하는 라이브러리다. 기본적으로 headless 환경에서 작동하지만 non-headless 환경에서도 작동하게 할 수 있다.

cheerio란?

❤ Familiar syntax: Cheerio implements a subset of core jQuery. Cheerio removes all the DOM inconsistencies and browser cruft from the jQuery library, revealing its truly gorgeous API.
ϟ Blazingly fast: Cheerio works with a very simple, consistent DOM model. As a result parsing, manipulating, and rendering are incredibly efficient.
❁ Incredibly flexible: Cheerio wraps around parse5 parser and can optionally use @FB55's forgiving htmlparser2. Cheerio can parse nearly any HTML or XML document.

공식 설명에 따르면 DOM과 JQuery에 관한 설명이 나온다. 웹 페이지의 콘텐츠는 DOM에 저장되어 있는데, 이 콘텐츠를 자바스크립트를 통해 접근하거나 조작할 수 있다.

JQuery는 Element에 접근하기 쉽게 만들어주는 JS 라이브러리이다.

즉 cheerio는 DOM과 JQuery 및 parse5 등을 이용해서 웹 사이트의 뼈대를 이루는 HTML + CSS + JS를 받아오기 쉽게 해주는 라이브러리인 것이다.

본론

axios & cheerio

위에 다양한 라이브러리에 관해서 설명을 했다. 가장 먼저 떠오른 방법이자, 인터넷에서 레퍼런스를 찾기 쉬웠던 방법인데, axios를 통해서 어떤 웹 URI에 접근한 다음, 해당 웹에서 정보를 cheerio를 통해 끌어오는 방법을 이용하면 되는 것이 아닐까? 라는 생각했다.

export class CardService {
  cheerio = require('cheerio');
  axios = require('axios');

  async getHTML(name: string) {
    try {
      params.keyword = name;
      return await this.axios.get(url, { params });
    } catch (error) {
      console.log(error);
    }
  }

  getCardData(name: string) {
    this.getHTML(name).then((html) => {
      const $ = this.cheerio.load(html.data);
     
      const card_img = $('#card_list > div > div');
      const card_effect = $(
        '#card_list > div > dl > dd.box_card_text.c_text.flex_1',
      ).text();
    });
	
    const data = { card_img, card_effect };
    return data;
  }
}

유희왕 카드 데이터베이스라는 사이트에서 검색한 카드의 정보를 빼 오는 크롤링을 구현한 코드이다. axios와 cheerio를 이용했는데 해당 코드에서 도저히 크롤링할 수 없는 상황이 나왔다.

이유를 찾아보기 위해서 여러 번 코드를 바꾸어봤는데, 결론은 headless 크롬을 통한 크롤링을 할 수 없는 환경이었다.

axios에서 크롤링이 안되는 이유
Headless 크롬으로 크롤링하기

첫번째 하이퍼 링크의 내용을 참고하면

axios로 해당 데이터를 가져올때 문제점이 해당 태그들이 생성되지 않은 상태에서 접근하다보니 발생하는 문제라고 한다. 요즘 같이 CSR 페이지들이 늘어나는데 렌더링이 늦게 되는 사이트에서 Tag를 접근하려고 하다보니 생기는 문제였다.

이와 같은 이유로 axios를 이용한 크롤링에 실패하여 puppeteer를 사용하기로 했다. 해당 과정을 좀 더 알아보았는데 이유는 headless 크롬이냐 아니냐 때문에 갈렸다. 두 번째 하이퍼링크에 headless 크롤링에 대한 정보가 쓰여있다.

해당 두 포스트를 참고하면서 axios를 통한 headless 환경 크롤링에서 puppeteer를 사용하는 방법으로 변경하였다.

puppeteer & cheerio

axios를 통한 headless 크롤링 환경에서 이슈가 있었기 때문에 퍼펫티어를 이용한 방법으로 크롤링을 수행했다.

export class CardService {
  async getDataViaPuppeteer(name: string) {
    const url = URI;

    const browser = await puppeteer.launch({
      headless: false,
    });
    const page = await browser.newPage();
    await page.goto(url, {
      waitUntil: 'networkidle2',
    });

    const content = await page.content();

    const $ = cheerio.load(content);

    const card_name = $(
      '#card_list > div:nth-child(1) > dl > dd.box_card_name.flex_1.top_set > span.card_name',
    ).text();
    
    const data = {
      card_name
    };

    browser.close();
    return trimAllEscape(data);
  }
}

해당 코드를 통해서 card_name 뿐만 아니라 card_effect 등의 다양한 정보를 가져오지만, 가독성을 위해서 다른 부분은 제거했다. 데이터를 return할 때 사용하는 trimAllEscape는 데이터를 cheerio를 통해 크롤링할 때 생기는 이스케이프 시컨스들을 제거해주는 함수이다.

해당 방법을 통해서 "하루 우라라"라는 카드 명을 통해 유희왕 카드 정보가 담긴 사이트를 크롤링하면 해당 JSON 데이터를 얻을 수 있었다.

{
  "card_name": "하루 우라라",
  "card_level": "레벨 3",
  "card_species": "[언데드족/튜너/효과]",
  "card_atk": "공격력 0",
  "card_def": "수비력 1800",
  "card_attribute": "화염",
  "card_effect": "이 카드명의 효과는 1턴에 1번밖에 사용할 수 없다..." // 중략
}

결론

node.js 환경에서 크롤링할 때에 axios를 사용할 것인지 puppeteer를 사용할 것인지 결정할 때, 이 글을 참고하여 포스팅하면 좋을 것 같아서 작성하게 됐다.

정적인 페이지를 크롤링할 때에는 axios & puppeteer가 압도적으로 빠르고 편리하므로 가능하면 axios를 사용하는 것이 좋을 것이다. 반면 동적인 페이지나 알 수 없는 이유로 axios가 작동하지 않는다면 puppeteer를 사용하는 것도 권고하는 바이다.

puppeteer에서도 headless 값을 true로 주면 axios와 마찬가지로 정보를 퍼오지 못한다.

const browser = await puppeteer.launch({
      headless: false, // 이 부분을 true로 바꾸면 정상 작동하지 않음
    });
    const page = await browser.newPage();
    await page.goto(url, {
      waitUntil: 'networkidle2',
    });

    const content = await page.content();

즉, headless 환경에서 크롤링이 불가능한 경우에는 puppeteer를 이용하여 headless를 false 값으로 주는 방법을 사용하는 것이 정상적으로 크롤링이 되는 것 같다. 이는 selenium을 사용하는 방식과 비슷했다. 가상의 크롬 환경을 열어 해당 크롬 환경을 조작한 후, 조작된 웹에서 cheerio로 데이터를 퍼오는 방식이다.

단점으로는 아무래도 가상의 크롬을 열어서 크롤링하는 방식이기 때문에 속도가 느리다는 단점이 있다.

추가 사항

해당 프로젝트를 완성하게 되면서 nestjs 프레임워크로 짜인 api를 AWS의 EC2에 업로드하게 됐는데, headless 환경으로 인하여 에러가 났다.

puppeteer 관련해서 구글링 도중 Xvfb를 통해서 해결할 수 있다는 정보를 찾아 이를 통해서 성공적으로 구현했다.

Xvfb(X Virtual Frame Buffer)는 로컬 혹은 리모트 서버에 X로 접속하거나 띄우지 않고, 콘솔로만 접속하여 윈도우 콘솔 프로그램을 돌리고자 할 경우에 사용하는 Display Server이다. Xvfb는 다른 Display Server와 다르게 보여지는 output이 없이 메모리 수준에서 graphic operation을 수행한다. 따라서 Xvfb가 수행되는 서버(컴퓨터)에서는 보여주기위한 출력장치나 입력장치가 존재하지 않아도 된다. 네트워크 계층만 필요로 한다.

이러한 Xvfb의 설치를 통해서 성공적으로 크롤링해주는 api를 EC2에 업로드할 수 있었다.

profile
Backend Developer

0개의 댓글