서버/Node

NodeJS Crawling Puppeteer

realtrynna 2022. 4. 18. 20:11

 

NodeJS Crawling Puppeteer

concept: Headless 크롬 혹은 크로미엄을 제어하도록 도와주는 노드 라이브러리

- SPA(Single Page Application) 즉 SSR(Server Side Rendering) 된 사이트를 크롤링 할 수 있다.

- 페이지 화면 캡처와 양식 제출 UI 테스트 키보드 마우스 입출력 등 사람이 할 수 있는 동작 제어 가능

- Dev Tools 프로토콜로 키보드 마우스뿐만 아니라 쿠키/세션/스토리지 등 다양하게 제어할 수 있다.

- 크롤링하고자 하는 사이트의 보안이 걸려있을 경우 axios/cheerio를 사용하면 접근할 수 없다.

 

Headless

Headless란 CLI(Command Line Interface)에서 작동하는 브라우저이다.

가장 큰 특징으로 백그라운드에서 돌아가며 HTML/CSS로 DOM과 CSSOM을 만들고 자바스크립트를 구동한다.

크롤링은 보통 서버에서 일정 주기로 실행되기에 화면이 없는 상태로 실행되어야 하는데

개발과 배포 환경을 따로 옵션을 주어 GUI처럼 사용할 수도 있다.

 

구조

puppeteer는 하나의 Browser를 갖는다.

하나의 브라우저는 여러 BrowserContext를 가질 수 있다.

BrowserContext 하나는 여러 Page를 가질 수 있고 Servicewoker와 Session을 가질 수 있다.

Page 하나는 여러 Frame을 가질 수 있고 하나의 Frame은 여러 Context를 가질 수 있다.

 

puppeteer 구조

 

userAgent

브라우저의 정보를 담고 있다.

언젠가부터 크롤링으로 인한 법적 문제가 많아지다 보니 웹 사이트 자체적으로 봇을 검사하여 차단하기 시작했다.

그래서 봇을 사람처럼 설정하여 접근한다.

 

navigator.userAgent
크롤링 시 setUserAgent 메서드 인자로 넣어준다.
봇의 userAgent 확인

 

주의사항

  1. 특정 사이트를 무단으로 크롤링 할 경우 법적 문제가 발생할 수 있으니 사전에 알아보자.
  2. puppeterr를 배포할 경우 서버의 RAM을 최소 1GB가 확보돼있어야 한다.(구글 크롬 조언)
  3. 이미지 크롤링 시 해당 서버의 과부하를 줄 수 있으므로 조심하자.
  4. Promise 사용 시 속도는 빠르지만 순서가 보장되지 않는다.
  5. for of 사용 시 순서는 보장되지만 속도는 매우 느리다.

 

사용 라이브러리

 

import xlsx from "xlsx";
import puppeteer from "puppeteer";
import { add_to_sheet } from "./xlsx.js";

const workData = xlsx.readFile("./data/data.xlsx");
const workSheet = workData.Sheets.movieList;
const workResult = xlsx.utils.sheet_to_json(workSheet);
const botAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36";

const crawlerBot = async () => {
  add_to_sheet(workSheet, "B1", "s", "제목");
  try {
    const browser = await puppeteer.launch({ 
      headless: false,
      args: ["--window-size=1920,1080"],
    });
    await Promise.allSettled(workResult.map(async (e, i) => {
      try {
        const page = await browser.newPage();
        await page.setRequestInterception(true);
        page.on("request", async (request) => {
          if (request.resourceType === "image") {
            request.abort();
          } else {
            request.contin+ue();
          }
        });
        await page.setUserAgent(botAgent);
        await page.setViewport({
          width: 1920,
          height: 1080,
        });
        await page.goto(e.url);
        await page.waitFor(2000);
        const result = await page.evaluate(() => {
          const title = document.querySelector(".mv_info_area .mv_info .h_movie a").textContent;
          return title;
        });
        if (result) {
          console.log(result);
          const newCell = "B" + (i + 2);
          add_to_sheet(workSheet, newCell, "n", result.trim());
        } else {
          console.log("here");
        }
      } catch (err) {
        console.error(err);
      }
    }));
    await browser.close();
    xlsx.writeFile(workData, "./data/result.xlsx");
  } catch (err) {
    console.error(err);
  }
}

crawlerBot();

app.js

 

  • puppeteer.launch 메서드로 브라우저를 생성 후 실행시킨다.
  • headless 옵션은 개발 시에 GUI처럼 사용하고 args 옵션은 브라우저의 사이즈를 설정한다.
  • puppeteer의 메서드는 전부 비동기이므로 async/await 문법을 사용해야 한다.
  • 비동기 메서드 Promise.allSettled로 엑셀 데이터의 반복문을 처리한다.
  • 브라우저를 생성 후 page를 생성한다. 페이지는 탭이랑 동일하다. 수십 개를 띄울 수도 있다.
  • page.setRequestInterception 메서드를 통해 크롤링 사이트의 모든 이미지를 띄우지 않을 수 있다.
  • page.goto 메서드의 인자로 크롤링 할 사이트를 넣어준다.
  • page.waitFor 메서드의 인자로 페이지 이동 후 대기 시간을 설정한다.
  • page.evaluate 메서드로 이동 후 사이트의 영화 제목을 가져오는 콜백 함수를 넣어준다.
  • page.evaluate 메서드 안에서 DOM/BOM 메서드를 사용할 수 있다.
  • add_to_sheet / xlsx.writeFile 메서드로 데이터를 넣어준다.

 

import xlsx from "xlsx";

function range_add_cell(range, cell) {
  var rng = xlsx.utils.decode_range(range);
  var c = typeof cell === 'string' ? xlsx.utils.decode_cell(cell) : cell;
  if (rng.s.r > c.r) rng.s.r = c.r;
  if (rng.s.c > c.c) rng.s.c = c.c;

  if (rng.e.r < c.r) rng.e.r = c.r;
  if (rng.e.c < c.c) rng.e.c = c.c;
  return xlsx.utils.encode_range(rng);
}

export const add_to_sheet = (sheet, cell, type, raw) => {
  sheet['!ref'] = range_add_cell(sheet['!ref'], cell);
  sheet[cell] = { t: type, v: raw };
};

xlsx.js

'서버 > Node' 카테고리의 다른 글

NodeJS 작동 원리  (0) 2022.07.25
NodeJS  (0) 2022.07.23
NodeJS axios cheerio xlsx  (0) 2022.04.18
NodeJS dotenv  (0) 2022.01.20
NodeJS NPM semver package.json  (0) 2022.01.19