상현에 하루하루
All 개발자의 하루

메타크롤러 (with. puppeteer)

( 업데이트: )

📎도커 컨테이너로 배포할 것이라 NodeJS 사전준비 방법

🤔 왜? 무엇 때문에

SEO Preview link 만들기

해당 프로젝트에서 사용할 SEO관련 메타 정보를 가져오는 방법을 모색하다가 크롤링 후 정보 가공한다음 데이터를 보내주는 API 서버를 제작하는 것을 생각했다.

🔍 어떤 것들이 있을까?

크롤링하면 항상 머릿속에는 Python Selenium이 있었습니다. 하지만 제가 잘 활용하는 언어인 자바스크립트로는 크롤링 방법이 없을까? 살펴보았습니다.

Phantom JS

PhantomJS는 JavaScript로 스크립트 가능한 Headless Webkit입니다.
수백 명의 개발자와 수십 개의 조직에서 웹 관련 개발 워크 플로우를 위해 사용합니다.

Puppeteer

Puppeteer는 DevTools 프로토콜을 통해 헤드리스 Chrome을 제어하기 위한 고급 API를 제공하는 노드 라이브러리입니다.

Headless Chrome을 사용하도록 구성할 할 수도 있습니다.

Cheerio

Cheerio는 마크 업을 구문 분석하고 데이터 구조를 순회 / 조작하기위한 API를 제공합니다. 웹 브라우저처럼 결과를 해석하지 않습니다. 특히 시각적 렌더링을 생성하거나 CSS를 적용하거나 외부 리소스를 로드하거나 JavaScript를 실행하지 않습니다. 기능이 필요한 경우 PhantomJS 또는 JSDom 과 같은 프로젝트를 고려해야합니다.

osmosis

검색속도, 파싱속도, JQuery, Cheerio or JSDom과 같은 큰 종속성이 없다고 장점으로 내새움

이 중에서 제가 선택한 것은 Puppeteer였습니다.

  • SPA와 같은 사이트를 크롤링 할 수도있다.
  • 지인에게 추천받은 라이브러리
  • 노드에서 구성하기에 좋은 예제들이 많음
  • 매우 잘 정리되어있는 공식문서

🤡 Puppeteer 특징

puppeteer는 Headless Chrome 혹은 Chromium을 제어하도록 도와주는 라이브러리다.
해당 라이브러리는 아래와 같은 특징이있다.

  • SPA 화면의 렌더링 가능
  • 렌더링 후 키보드, 마우스 입력 제어
  • 웹 페이지의 자동 테스트 도구 제작 가능
  • 각각의 웹페이지 크롤링 가능
  • 접속한 페이지를 스크린샷 or PDF로 제작가능

Headless Browser

CLI에서 작동하는 브라우저이다.
백그라운드에서 동작하며, 일반적인 브라우저와 같이 웹페이지에 접속하여 HTML, CSS으로 DOM Tree와 CSSOM Tree를 만들고 JS 엔진을 구동한다.

👨‍💻 본격 NodeJS 구성

Puppeteer를 사용해서 데이터 구성

const puppeteer = require('puppeteer');
(async () => {
  // 브라우저 셋팅
  const browser = await puppeteer.launch({
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });
  // 새로운 페이지 new tab?
  const page = await browser.newPage();
  await page.goto(url);
})();

위와 같이 puppeteer로 Headless 브라우저를 실행시키고 페이지를 열어서 이동

여기서 launch({...args})를 넣어준 이유는 NodeJS를 도커 컨테이너에 띄워서 사용할 예정이라 실행되는 환경이 UNIX입니다. 그래서 해당 옵션들을 넣어줬습니다.

Puppeteer는 $eval('css selector',)똑같이 한가지 요소를 선택해서 요소에대해 작업할 수있습니다.
$ $$ $eval() $$eval() 이렇게 전체 선택 및 단일 선택 구분지어서 사용 가능

예:

// Meta data
const meta = await page.$$eval(`meta[property*='og:']`, data =>
  data.map(d => {
    let data = {};
    const keyRegExp = new RegExp('og:(.+)');
    const key = keyRegExp.exec(d.getAttribute('property'));
    const value = d.getAttribute('content');
    data[key[1]] = value;
    return data;
  }),
);
// 파비콘 찾고 추가
await page
  .$eval(`link[rel~='icon']`, el => {
    return el.href;
  })
  .then(favicon => {
    meta.push({ favicon });
  })
  .catch(err => err);

이런 방식으로 선택해서 객체로 셋팅

이미지 저장?

이미지 저장에있어서 어떻게 구현 할지 고민하였습니다. 어떤 폴더구조를 가지고 중첩되지 않게 구현할 수 있을까?
노션에서는 어떻게 구현하였을까?

살펴보니까 제가 크롤링하려는 대상 URL을 그대로 폴더이름에 적용시켜서 가져오는 것으로 구현되어있었습니다. 그래서 저도 Best Practice 를 따라 구현 시작!

파일 및 폴더 이름설정 오류 fix

  • 파일 및 폴더에는 : /와 같은 문제를 사용할 수 없습니다.
    해당 문제는 : /를 16진수의 문자열로 인코딩하는 방식을 사용
  • 한글 URL의 경우 인코딩한 문자열로 변환하여 저장

데이터 return

크롤링으로 <meta>를 객체로 받아와서 res.json(meta) 보내주면 끝!

🏷 배포 – Dockerfile

배포는 왜? Dockerfile?

현재 NodeJS에 관련된 코드들을 서버에 올려서 관리한다고 생각해 보았습니다. 그러면 코드를 양쪽에서 일치하게 관리해줘야 하는 부담이 있었습니다. 그래도 배운 것 중에 도커 컨테이너로 애플리케이션을 관리하는 것들을 많이 해보아서 도커 이미지로 만들어서 사용하면 따로 서버 쪽에서 코드 관리에 대한 부담이 줄어들 것 같아서 도커 이미지로 배포하기로 하였습니다.

Puppeteer NodeJS Chrome 종속성 오류

📎Chrome Headless는 UNIX에서 시작되지 않습니다.
📎Chrome Headless는 Windows에서 시작되지 않습니다.

공식 홈페이지에서 UNIX 환경에서는 Chrome Headless가 시작되지 않는다 경고하였습니다.
그래서 도커 이미지에서 미리 크롬 종속성 관련 어플리케이션들을 설치해야합니다.

https://paul.kinlan.me/hosting-puppeteer-in-a-docker-container/

# Install latest chrome dev package and fonts to support major 
# charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version 
# of Chromium that Puppeteer
# installs, work.
RUN apt-get update && apt-get install -yq libgconf-2-4
RUN apt-get update && apt-get install -y wget --no-install-recommends \
  && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
  && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
  && apt-get update \
  && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
  --no-install-recommends \
  && rm -rf /var/lib/apt/lists/* \
  && apt-get purge --auto-remove -y curl \
  && rm -rf /src/*.deb

해당 아티클을 참고하여 Dockerfile의 위 코드를 인용하여 도커 이미지를 빌드하였습니다.


Reference