본문 바로가기
개발 학습/프론트엔드

Node.js에서 Puppeteer로 동적웹 크롤링하기

by StelthPark 2022. 2. 8.

문제점

클레이튼 스코프 웹에서 내 계정을 검색하고 나온 결과에서 KIP17 Balanace 같은 버튼을 누른뒤 바뀌는 HTML 소스를 크롤링 하고 싶었다. 하지만 이는 내가 해당하는 버튼을 누른뒤에 HTML이 동적으로 바뀌게 되고 우리가 자주쓰는 Axios나 Cheerio, Postman은 정적인 웹의 소스만 가져올 수 있어 원하는 것을 크롤링 할 수 없었다.

 


크롬에서 개발자 도구로 열었을땐 html소스가 정상적으로 보인다.

 

Postman으로 같은 주소를 날리면 body가 거의 빈채로 응답된다.

 

이렇게 body나 원하는 html소스를 얻을 수 없어 크롤링을 할 수가 없으므로 우선 동적으로 내가 눌러서 접근한 버튼에서까지 보이는 전체 html소스를 받아 올 수 있어야 했다.

 


해결

서버단에서 puppeteer를 사용했다. 이상하게 클라이언트 단에서 npm하여 import하면 오류가 계속났다.

Cannot read properties of undefined (reading 'O_CREAT') 해당 오류가 계속 났는데 구글링 해보니 어떤 개발자가 절대 해결할 수 없었다고 한다. 그러면서 puppeteer를 쓰지말라고 했지만 셀레니움보다 쓰기 간편할 것 같아서 서버단에서 한번 시도해보고자 클라이언트에서 요청을 보내 서버에서 받고 거기서 require를 하도록 해보았다. 아까 나던 오류는 더이상 나지 않았고 전체 html을 불러오는 과정을 진행하기로 했다.

 

우선 나는 puppeteer로 전체 html소스를 불러와주고 cheerio로 그것을 크롤링 하고자 했다.

 

<Server>

const browser = await puppeteer.launch({
    headless: true,
  });

  // 새로운 페이지를 연다.
  const page = await browser.newPage();
  // 페이지의 크기를 설정한다.

  await page.goto(`https://baobab.scope.klaytn.com/account/${account}`);

  try {
    await page.click(
      "#root > div > div.SidebarTemplate > div.SidebarTemplate__main > div > div > div.DetailPageTableTemplate > div > div.Tab__tabItemList > div:nth-child(4)"
    );
    await page.waitForSelector(
      "#root > div > div.SidebarTemplate > div.SidebarTemplate__main > div > div > div.DetailPageTableTemplate > div > div.Tab__content > section > article.TokenBalancesList__body > div > div.Table__tbody"
    );
    const content = await page.content();
    let list = [];
    const $ = cheerio.load(content);
    const $bodyList = $(
      "div.Table__td.TokenBalancesListDesktop.TokenBalancesListDesktop__kip17-balance__name.TokenBalancesListDesktop.TokenBalancesListDesktop__kip17-balance__nameTd > a"
    );
    for (let i = 0; i < $bodyList.length; i++) {
      list.push($bodyList[i].attribs.href.slice(5));
    }
    await browser.close();
    res.status(200).send(list);

 

launch메소드를 통해 browser를 받게되는데 headless가 true인지 false인지에 따라 가상웹을 띄워줄건지 아닌지 결정하게된다. 사실 서버단에서 실행되는거고 나는 크롤링만 원하기때문에 true로 하여 화면에 나타나는 웹을 숨겼다고 보면 된다. 이후 newpage로 새페이지를 열고 goto메소드를 통해 내가 원하는 웹으로 이동시킨다.

 

click 메소드는 화면에 보이는 것을 셀렉터를 통해 누르는 과정을 하게 된다. 실제로 맨 처음 내가원하는 페이지로 이동한 뒤 click메소드 인자로 넣은 셀렉터를 가상웹이 버튼을 누르게되고 이동하게 된다. 동적인 처리를 자기가  로직대로 알아서 해주는 것이다. content.메소드는 현재 페이지의 전체 html을 모드 담게 된다.

 

waitForSelector는 뒤에 인자로 오는 셀렉터가 생성되고 나야 다음 로직을 실행시킨다. kip-17 balance 버튼을 눌러 이동시키면 실제로 일정 로딩을 거친뒤에 아래 해당하는 탭들이 나타나는데 만약 waitForSelector없이 바로 다음로직을 수행한다면 우리가 크롤링 할 소스가 다보이지않고 넘어가게 된다. 메소드중에 wait메소드도 있는데 인자로 시간을 주어 해당 시간이 지난뒤에 다음 로직을 작동시킬 수 있으나 waitForSelector 처럼 셀럭터가 생성된 이후 로직이 넘어가는 방식이 더 나아보인다.

 

담은 html소스를 content 변수에 할당하였고 cheerio를 통해 load하여 html로 다시 뽑아 $을 통해 원하는 셀렉터를 다시 걸러주었다. for문을 통해 걸러진 html소스중에 자식요소가 a태그의 href만을 걸러 list에 푸시하여 응답으로 클라이언트에게 내주었다. 마지막으로 모든 작업이 끝나면 close로 브라우저를 종료해준다.

 

처음부터 순서대로 가상웹이 마치 나인것 처럼 작동하는 순서를 거치게되고 결국 내가 직접 웹에 접속하여 kip-17 balance탭을 누른것과 같으며 거기서 보이는 html소스를 담아 크롤링하여 뱉은거나 마찬가지인것이다.

 

 

댓글