처음부터 차근차근

[Puppeteer] Page.on을 통해 response 가지고 오기 본문

Library/puppeteer

[Puppeteer] Page.on을 통해 response 가지고 오기

HangJu_95 2024. 9. 5. 21:53
728x90

Page.on()이란

Puppeteer 공식문서에는 이렇게 적혀있다.

Listen to page events.

This method exists to define event typings and handle proper wireup of cooperative request interception. Actual event listening and dispatching is delegated to EventEmitter.

즉, 어떤 이벤트가 발생하면, 어떤 동작을 할 수 있도록 설정하는 것이다.

class Page {
  on<K extends keyof PageEventObject>(
    eventName: K,
    handler: (event: PageEventObject[K]) => void
  ): EventEmitter;
}

이 메서드를 사용하려면 우선, 모든 Request를 intercept 할 수 있도록 설정해줘야 한다.

공식문서 예제를 보고 따라해보자.

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // page에서 인터셉트 할 수 있도록 설정
  await page.setRequestInterception(true);
  page.on('request', interceptedRequest => {
    if (interceptedRequest.isInterceptResolutionHandled()) return;

    // 만약 Request의 url이 이미지 형식이라면, 중단하겠다.
    if (
      interceptedRequest.url().endsWith('.png') ||
      interceptedRequest.url().endsWith('.jpg')
    )
      interceptedRequest.abort();
    else interceptedRequest.continue();
  });
  await page.goto('https://example.com');
  await browser.close();
})();

이때 PageEventObject에는 다양하게 들어가며, response도 들어갈 수 있다.

왜 Page.on()으로 response를 가지고 오는가?

  1. 브라우저에서는 보이지 않는 데이터를 가지고 오고 싶을 때

크롤링을 하다보면, API Response에는 훨씬 더 많은 데이터를 가지고 있을 때가 있다.
(예시를 들면, 상품에 대한 상세한 정보..?)
이런 것들을 조금 더 가지고 오기 위해 API의 response를 가지고 온다.

  1. API의 Response는 대부분 JSON으로 오기 때문에, Selector로 선택하지 않아도 된다.

가끔 크롤링을 하다보면, Selector가 무진장하게 길거나, class 명 혹은 id 명이 이상해서 잘 가지고 오지 않는 점이 존재한다.
API Response는 그런 것이 전혀 없이, Key - value 형태로 간단하게 나와있다. (굉장히 직관적이다.)

  1. URL에 입력한 후, API 데이터를 바로 가지고 오고 싶은 경우

이 부분에서 가장 필요하다고 느껴 page.on을 사용하였다.
사실 데이터를 불러올 때, waitForResponse로 해도 되지 않느냐?? 라고 할 수 있다.
그러나, URL에 바로 접속한다면, waitForResponse를 사용할 수 없다. 이미 Response가 와있기 때문에.
따라서 page.on을 사용하여 데이터 크롤링을 하였다.

이런 부분에 의해, 내가 puppeteer를 사용하지만, API response로 데이터 크롤링을 선호하는 이유이다.
(Axios 호출이 된다면 편하지만.. 안되는 것도 존재하기에..)

그렇다면 어떻게 가지고 왔는가??

page.on에서 해당하는 event는 다양하다.

error, close등 다양하게 존재한다.

여기서 나는, response를 가지고 올 것이다.

코드는 대략 이렇게 작성했다.

import puppeteer from 'puppeteer';

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    // page에서 인터셉트 할 수 있도록 설정
    await page.setRequestInterception(true);

    // 필요한 데이터를 변수명으로 지정
    let needData;

    // on에 들어가는 callback 함수 작성
    const responseHandler = async (response) => {
        // 내가 찾는 특정 API의 url
        if (response.url().includes('API에 해당하는 url')) {
            let responseData = await response.json();
            needData = responseData;
            console.log(needData);

            // 필요한 응답을 받았으므로 이벤트 리스너 제거
            page.off('response', responseHandler);
        }
    };

    // page.on 설정
    page.on('response', responseHandler);

    // 스크랩핑 하고 싶은 url 참조
    await page.goto('https://example.com');

    // 데이터 잘 가지고 왔는지 확인
    console.log(needData);
    await browser.close();
})();

이렇게 작성하면, needData에 잘 가지고 올 줄 알았다. 하지만 문제점이 발생했다.

Response는 잘 찾아오지만, 데이터를 못가지고 오는 현상 발생

분명히, 데이터를 잘 가지고 오는 줄 알았다 하지만, 이상하게도 log에 찍어보면 데이터는 undefined가 나온다.

이후에 log를 확인하니, response의 데이터를 console에는 잘 가지고 오기는 하지만, 변수에는 지정이 안되어있었다.

(아마 비동기 처리로 인해 문제가 발생한 것 같았다.)

따라서 이 부분을 이렇게 코드를 수정하여 해결하였다.

    // 스크랩핑 하고 싶은 url 참조
    await page.goto('https://www.aliexpress.com/gcp/300001014/Beauty');

    while (needData) {
        await sleep(0.1);
    }
    // 데이터 잘 가지고 왔는지 확인
    console.log(needData);
    await browser.close();

데이터를 가지고 올 때까지 0.1초간 sleep하도록 코드를 작성하였다.

물론 이 코드가 좋은 코드는 아니지만, 데이터를 무조건 가지고 오고 싶다면 이렇게 작성할 수 있다.
page.on에 대한 코드를 확인해보니,

 

(method) Page.on<"response">(eventName: "response", handler: (event: puppeteer.HTTPResponse) => void): puppeteer.EventEmitter

 

Listen to page events.

:::note

This method exists to define event typings and handle proper wireup of cooperative request interception. Actual event listening and dispatching is delegated to EventEmitter.

:::

 

@param event — the event type you'd like to listen to. Can be a string or symbol.

@param handler — the function to be called when the event occurs.

 

handler로 들어가는 함수는 동기 함수이다. 따라서 이 부분이 문제인 것으로 확인된다.
나는 비동기처리를 하였지만, 사실 동기 함수를 작성해야 하는 것이다.

 

참조

 

Request Interception | Puppeteer

Once request interception is enabled, every request will stall unless it's

pptr.dev

 

puppeteer/docs/api/puppeteer.page.on.md at v18.1.0 · puppeteer/puppeteer

JavaScript API for Chrome and Firefox. Contribute to puppeteer/puppeteer development by creating an account on GitHub.

github.com

 

'Library > puppeteer' 카테고리의 다른 글

[Puppeteer] page.waitForXXX 관련 정리  (0) 2024.08.27