개발일기장

Node.JS로 웹 크롤링 하기. puppeteer, cheerio, 동적 크롤링 본문

node.js

Node.JS로 웹 크롤링 하기. puppeteer, cheerio, 동적 크롤링

게슬 2022. 5. 18. 17:06
728x90

1. 왜 puppeteer를 사용하는지

https://www.npmjs.com/package/puppeteer

 

puppeteer

A high-level API to control headless Chrome over the DevTools Protocol. Latest version: 14.1.0, last published: 5 days ago. Start using puppeteer in your project by running `npm i puppeteer`. There are 4976 other projects in the npm registry using puppetee

www.npmjs.com

일반적으로 axios나 fetch같은 단순히 HTTP GET method를 이용해서 해당 페이지를 긁어오는 방식이 있지만, 이것은 클라이언트 사이드에서 데이터를 로딩하는 방식의 웹페이지에서는 원하는 정보 모두를 가지고 올 수 없었음.

(내가 크롤링 하고 싶었던 내용은 내용은 기본적으로 서버사이드에서 로딩을 한 다음에 댓글부분은 사용자단에서 로딩이 되는것 같았음.)

 

puppeteer를 사용하는 경우 Chromium으로 원하는 링크를 연 다음에 페이지가 전부 랜더링 된 다름에 정보를 가지고 오는 방식이다.(인것 같다.. 정확한건 몰라)

-> axios, fetch는 정적크롤링,  puppeteer 동적크롤링으로 검색하니깐 나왔음..

 

+ 추가

puppeteer에서는 Chromium으로 페이지를 연 다음에 거기서 클리이나 다른 작업을 코드로 할 수 있도록 기능을 제공한다.

 

2. 방법

1. puppeteer를 이용하여 데이터를 가지고 오자

1-1) browser를 실행한다(여기서 여러가지 옵션을 줄 수 있다. 브라우저(Chromium)를 직접 열어서 어떤 태그를 가지고 올지 확인할 수 있다.)

1-2) 새로운 페이지 시작

1-3) 원하는 링크를 연다

1-4) HTML 정보를 가지고 온다

1-5) 열었던 것들을 닫는다.

const puppeteer = require('puppeteer');

//1. 크로미움으로 브라우저를 연다. 
const browser = await puppeteer.launch(); // -> 여기서 여러가지 옵션을 설정할 수 있다.
        
//2. 페이지 열기
const page = await browser.newPage();
        
//3. 링크 이동
await page.goto(`${href}`);

//4. HTML 정보 가지고 온다.
const content = await page.content();
        
//5. 페이지와 브라우저 종료
await page.close();
await browser.close();

이렇게 하면 content 변수에 우리가 원하는 데이터를 가지고 올 수 있다.

(이 경우 axios(fetch) GET을 이용해서 가지고 온 방식이랑 같다고 보면 된다.)

(크로미움으로 내용이 로딩 한 다음에 컨탠츠를 가지고 오는 것이기 때문에 시간이 조오오오금 걸린다.)

 

2. cheerio를 이용하여 원하는 데이터를 추출한다.

내가 가지고 오고 싶은 데이터는 일단

대충 이렇게 구성되어 있다.

해당 게시판의 글 번호, 제목, 링크, 작성자, 작성 날짜를 가지고 오고 싶었다.

그리고 크롤링 코드를 재사용 하기 위해서 크롤링 정보만들 원하는 Object를 만들었음

const post_list_object = {
    href: `${원하는 링크}`,
    select_path: '#content-wrap > div > div.board-list > ul > li',
    items: [
        { name: 'counter', path: 'span.count', text: true, href: false },
        { name: 'href', path: 'span.title > a', text: false, href: true },
        { name: 'title', path: 'span.title > a', text: true, href: false },
        { name: 'nick', path: 'span.global-nick', text: true, href: false },
        { name: 'date', path: 'span.date', text: true, href: false },
    ],
    type: 'post',
};

이렇게 구성을 했다. items에서 text의 경우 그냥 html태그 내부의 데이터를 가지고 오기 위한 것이고, href는 <a>에서 링크값을 가지고 오고 싶었다.

 

이것을 crawl_obejct로 파라미터로 넘겨준다.

const cheerio = require('cheerio');

////////////////////////////

//0. crawl_object는 위에 있는 post_list_object임.
        
//1. 위에서 가지고온 데이터(content)를 cheerio에 넣는다.
const $ = cheerio.load(content);
const result = [];

//2. li를 로딩한다(각각의 데이터를 추출하기 위해서 each사용)
$(crawl_object.select_path).each(function (idx, element) {
        
      //3. 각각의 li내부의 데이터를 cheerio에 넣은 다음
      const $data = cheerio.load(element);
      const return_data = {};
      //4. 그 li내부의 이름, 날짜, 링크 등등 가지고 오고싶음
      crawl_object.items.forEach((item) => {
            
            //4-1.일반 태그 내부의 text데이터 가지고 오기
            if (item.text === true) {
                return_data[item.name] = $data(item.path).text();
            }
			//4-2.<a>에서 href속성을 가지고 오기위한것,
            if (item.href === true) {
                return_data[item.name] = $data(item.path).attr('href');
            }
        });		
            ///////
            "이 밑으로는 return_data를 이용한 로직이 있음."
            "result에 push하기전에 좀 다듬을 게 있어서.."
            ///////
});

이렇게 하면 된다.

 

3. 전체 코드

const puppeteer = require('puppeteer');
const cheerio = require('cheerio');

const crawler = async (crawl_object) => {
    try { 
        const browser = await puppeteer.launch(); .
        const page = await browser.newPage();
        await page.goto(crawl_object.href);
        
        const content = await page.content();
        await page.close();
        await browser.close();

        const $ = cheerio.load(content);
        const result = [];
        $(crawl_object.select_path).each(function (idx, element) {
            const $data = cheerio.load(element);
            const return_data = {};
            crawl_object.items.forEach((item) => {
                if (item.text === true) {
                    return_data[item.name] = $data(item.path).text();
                }

                if (item.href === true) {
                    return_data[item.name] = $data(item.path).attr('href');
                }
            });

			// -> 여기부터는 내부 로직
        });
        //console.log(result);
        return Promise.resolve(result);
    } catch (err) {
        return Promise.reject([]);
    }
};

module.exports = {
    crawler,
};

async await사용해서 문제 생길 수 있으니깐 try catch로 감싸주고 Promise로 반환하자.(더 써먹기 위해서)

 

 

 

포스트랑 내용이 좀 다르지만 있음.

https://github.com/tlqckd0/web-crawling/tree/main/crawl

 

GitHub - tlqckd0/web-crawling: web-crawling & analysis

web-crawling & analysis. Contribute to tlqckd0/web-crawling development by creating an account on GitHub.

github.com

 

728x90
Comments