오늘은 작년 회사업무를 하면서 굉장히 친해졌던 퍼페티어라는 라이브러리를 찍먹 해보는 글을 작성해 보겠습니다.
주 내용은 제 블로그 게시물들의 정보(제목, 작성날짜)의 수집 자동화 코드를 작성하는 내용이 되겠습니다.



puppeteer란?

puppeteer는 구글에서 개발한 Node.js용 Chrome/Chrominm 제어 API입니다. 일반적으로 자동화된 웹 테스트, 웹 스크래핑, 웹 크롤링 등 다양한 웹 자동화 작업을 수행할 때 사용합니다.

특히, 마우스 클릭, 키보드 입력, 페이지 스크롤 등의 이벤트를 자동화해야하는 작업을 수행할 때 적합합니다.




 

puppeteer 설치

npm 명령어를 통해 puppeteer 패키지를 설치해줍니다

npm i puppeteer


puppeteer설치시에 함께 작동할 최선 버전의 Chromium을 자동으로 다운로드하기 때문에 용량이 꽤나 큽니다.

이미 설치된 Chrom/Chrominum을 사용하고 싶다면 Puppeteer Core를 설치하셔서 사용하시면 됩니다(용량을 아낄 수 있습니다)!




 

puppeteer 사용해 내 블로그 글 정보 수집하기

 

 

1. puppeteer를 사용해 Chromium띄우기

const  puppeteer = require('puppeteer');

class Crawler {
    browser = null;
    page = null;
  
    async setConfig() {
      this.browser = await puppeteer.launch({
        headless: false,
      });
      this.page = await this.browser.newPage();
    }
}

async function crawl(){
    const crawler = new Crawler();
    await crawler.setConfig();
}

crawl();

먼저 Crawler라는 이름의 클래스를 정의합니다.

  • setConfig메서드에서는 Chromium의 설정값을 정의하고 새로운 page를 띄웁니다.

crawl함수에서는 정의한 Crawler 클래스를 사용해 정보 수집 로직을 실행할겁니다.

위 코드를 실행시키면 아래 이미지와 같이 Chromium브라우저가 실행돼고 새로운 page를 띄운 모습을 확인할 수 있습니다.

 

 

 

2. 특정 url로 page를 이동시키기

제 블로그의 게시물들의 정보를 수집하기 위해 https://coding-lks.tistory.com/category 분류 전체 보기 url로 페이지를 이동시켜 보겠습니다.

const  puppeteer = require('puppeteer');

class Crawler {
    browser = null;
    page = null;
    targetUrl = 'https://coding-lks.tistory.com/category';
  
    async setConfig() {
      this.browser = await puppeteer.launch({
        headless: false,
      });
      this.page = await this.browser.newPage();
    }
  
    async accessSite() {
      await this.page.goto(this.targetUrl);
    }
}
  

async function crawl(){
    const crawler = new Crawler();
    await crawler.setConfig();
    await crawler.accessSite();
}

crawl();

    1. targetUrl 변수는 이동시킬 url의 값을 가지게 됩니다.

    2. accessSite메서드를 실행시켜 아래 스크린샷과 같이 targetUrl로 페이지를 이동시키는 동작을 수행하게 됩니다.

    3. 현재 page의 블로그 정보(제목과 작성날짜) 수집하기

targetUrl에 접근한 화면

 

 


3. blogData 멤버변수와 getPartOfPost메서드를 추가 작성해서 블로그 정보를 수집하기

const  puppeteer = require('puppeteer');

class Crawler {
    browser = null;
    page = null;
    targetUrl = 'https://coding-lks.tistory.com/category';
    blogData = null;
  
    async setConfig() {
        this.browser = await puppeteer.launch({
            headless: false,
        });
        this.page = await this.browser.newPage();
        this.blogData = [];
    }
  
    async accessSite() {
        await this.page.goto(this.targetUrl);
    }

    async getPartOfPost() {
        await this.page.waitForSelector('#body > ul > li', {
            timeout: 1000,
        });

        const posts = await this.page.$$('#body > ul > li');
        for (const post of posts) {
            const name = await post.$eval('a', (e) => e.innerText);
            const date = await post.$eval('.date', (e) => e.innerText);
            this.blogData.push({
                name,
                date
            });
        }
    }
}

async function crawl(){
    const crawler = new Crawler();
    await crawler.setConfig();
    await crawler.accessSite();
    await crawler.getPartOfPost();
}

crawl();
  1. puppeteer의 waitForSelector메서드와 특정 요소의 Selector를 사용해 특정요소가 화면에 랜딩 될때까지 기다립니다.
    timeout옵션을 사용하여 최대 대기 시간도 설정할 수 있습니다.
  1. puppeteer의 $$메서드를 사용해 DOM에서 특정 selector에 해당하는 요소들의 배열을 가져옵니다.
  1. 원하는 요소들의 배열을 순회하면서 innerText를 사용해 글의 제목과 날짜 데이터를 텍스트로 수집합니다.저는 name는 html태그를, date는 class값을 사용해 가져왔습니다.
  1. 수집한 데이터들을 this.blogData에 추가합니다.

이런 방식으로 1 페이지의 블로그글의 데이터를 수집할 수 있습니다.



 4. 이벤트를 사용해 다음 페이지로 이동하기

const  puppeteer = require('puppeteer');

class Crawler {
    browser = null;
    page = null;
    targetUrl = 'https://coding-lks.tistory.com/category';
    blogData = null;
  
    async setConfig() {
        this.browser = await puppeteer.launch({
            headless: false,
        });
        this.page = await this.browser.newPage();
        this.blogData = [];
    }
  
    async accessSite() {
        await this.page.goto(this.targetUrl);
    }

    async getPartOfPost() {
        await this.page.waitForSelector('#body > ul > li', {
            timeout: 1000,
        });

        const posts = await this.page.$$('#body > ul > li');
        for (const post of posts) {
            const name = await post.$eval('a', (e) => e.innerText);
            const date = await post.$eval('.date', (e) => e.innerText);
            this.blogData.push({
                name,
                date
            });
        }
    }

    async moveNextPage() {
        await this.page.click('#paging > a.next');
    }
}

async function crawl(){
    const crawler = new Crawler();
    await crawler.setConfig();
    await crawler.accessSite();
    await crawler.getPartOfPost();
    await crawler.moveNextPage();
}

crawl();

 

- 다음 페이지로 이동할 수 있는 클릭 이벤트가 있는 next요소를 클릭할 수 있도록 moveNextPage메서드를 작성했습니다.

 

 

 

5. 모든 블로그 글 수집하는 소스코드 완성하기

const  puppeteer = require('puppeteer');

class Crawler {
    browser = null;
    page = null;
    targetUrl = 'https://coding-lks.tistory.com/category';
    blogData = null;
  
    async setConfig() {
        this.browser = await puppeteer.launch({
            headless: false,
        });
        this.page = await this.browser.newPage();
        this.blogData = [];
    }
  
    async accessSite() {
        await this.page.goto(this.targetUrl);
    }

    async getPartOfPost() {
        await this.page.waitForSelector('#body > ul > li', {
            timeout: 1000,
        });

        const posts = await this.page.$$('#body > ul > li');
        for (const post of posts) {
            const name = await post.$eval('a', (e) => e.innerText);
            const date = await post.$eval('.date', (e) => e.innerText);
            this.blogData.push({
                name,
                date
            });
        }
    }

    async moveNextPage() {
        const flag = await this.page.waitForSelector('#paging > a.next.no-more-next', {
            timeout: 300,
        }).then(e => true)
            .catch(e => false);

        if (flag === true)
            return false;
              
        await this.page.click('#paging > a.next');
    }

    async checkLastPage() {
        const flag = await this.page.waitForSelector('#paging > a.next.no-more-next', {
            timeout: 300,
        }).then(e => true)
          .catch(e => false);

        return flag;
    }

    async closeBrowser() {
        await this.browser.close();
    }
} 

async function crawl(){
    const crawler = new Crawler();
    await crawler.setConfig();
    await crawler.accessSite();

    for (let i = 1; i <= 100; i++) {
				await crawler.getPartOfPost();
        const flag = await crawler.checkLastPage();
        if (flag === true)
            break;
        await crawler.moveNextPage();
    }
    
    await crawler.closeBrowser();
    console.log(crawler.blogData);
}

crawl();
  • 블로그 리스트의 마지막 페이지에 도달했는지 체크하는 메서드(checkLastPage) 추가
  • 브라우져 종료 시키는 메서드(closeBrowser) 추가

위 두가지 함수를 추가해 소스 코드를 완성했습니다.

 

 

총 로직이 아래와 같이 진행됩니다!

  1. puppeteer 세팅 - setConfig 메서드
  1. 특정 페이지로 접근 - accessSite 메서드
  1. 페이지 접근하며 반복문 수행
    1. 해당 페이지의 게시물의 정보 수집 - getPartOfPost 메서드
    1. 마지막 페이지인지 체크 - checkLastPage 메서드
    1. 다음 페이지로 이동 - moveNextPage 메서드
  1. 브라우져 종료

 

 

정리

회사에서 크롤링 업무를 진행하게 되면서 처음 접했던 브라우져 자동화 라이브러리인 puppeteer 찍먹해보는 게시물을 작성하게 되었습니다.

정리를 해야지 해야지 하면서 미루다가 4,5개월이 지난 지금에야 처음 관련 글을 작성하게 되었는데 한번 더 공부하는 기회가 되엇습니다.
puppeteer에서 제공하는 메서드는 제가 다룬 메서드들 이외에도 엄청나게 많고 여러가지 작업에 활용하기 좋기도하고 구글에서 만들어서 지속적으로 업데이트가 진행중이니 처음이신 분들은 한번 학습해보는걸 완전 추천드립니다 🙂

특히 저는 사이드 프로젝트를 시작할 때 더미데이터를 수집할 때 용이하게 사용하고 있습니다!

반응형

+ Recent posts