상세 컨텐츠

본문 제목

[TIL] 2024.04.03 스크래핑 잘 안됨

[TIL]

by 재호링 2024. 4. 3. 22:15

본문

기존에 사용하던 스크래핑 코드

  async getHTML() {
    //   const options = {
    //     method: 'GET',
    //     url: 'https://dddd.kr/footwear',
    //     headers: {
    //       accept:
    //         'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    //       'accept-language':
    //         'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,zh;q=0.5',
    //       'cache-control': 'max-age=0',
    //       cookie:
    //         '_ga=GA1.1.707882008.1711439246; _fbp=fb.1.1711439246481.1719838514; _tt_enable_cookie=1; _ttp=8yXK6BytRt5rcoTm8Zy-i2xInfW; __qca=P0-308357420-1711439246657; rsci_vid=26936b67-4973-cbbe-e183-3675af5ae70c; permutive-id=f52685e4-3213-43bc-a35a-aef89c7deca2; dddd=shown; user-value=68; __gads=ID=e5ff9b6997257ca1:T=1711439247:RT=1712109653:S=ALNI_MY9SUVI65jdHrgm-Rafq8Fqqfxw5g; __eoi=ID=f8867c4cd76e4140:T=1711439247:RT=1712109653:S=AA-AfjadtbYv5_oXFmWa3q281RWN; _clck=1ewtjod%7C2%7Cfkm%7C0%7C1546; OptanonConsent=isGpcEnabled=0&datestamp=Wed+Apr+03+2024+11%3A00%3A55+GMT%2B0900+(%ED%95%9C%EA%B5%AD+%ED%91%9C%EC%A4%80%EC%8B%9C)&version=202310.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=0cab7763-b597-40b9-9257-d875d99277a9&interactionCount=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1%2CC0004%3A1&AwaitingReconsent=false&geolocation=KR%3B44; OptanonAlertBoxClosed=2024-04-03T02:00:55.163Z; _clsk=6ogf4k%7C1712109656280%7C1%7C0%7Cd.clarity.ms%2Fcollect; _ga_FPSZ48KG02=GS1.1.1712036567.5.1.1712109673.39.0.0',
    //       'if-none-match': '"cb05a834190ee96881dc8866e8223ed8"',
    //       referer: 'https://dddd.kr/',
    //       'sec-ch-ua':
    //         '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
    //       'sec-ch-ua-mobile': '?0',
    //       'sec-ch-ua-platform': '"Windows"',
    //       'sec-fetch-dest': 'document',
    //       'sec-fetch-mode': 'navigate',
    //       'sec-fetch-site': 'same-origin',
    //       'sec-fetch-user': '?1',
    //       'upgrade-insecure-requests': '1',
    //       'user-agent':
    //         'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
    //     },
    //   };

    //   try {
    //     const response = await axios.request(options);
    //     const html = response.data;
    //     const $ = cheerioModule.load(html);

    //     const urls = [];
    //     $('.post-box').each(function () {
    //       const url = $(this).attr('data-permalink');
    //       urls.push(url);
    //     });

    //     console.log('URLs:', urls);

    //     return urls;
    //   } catch (error) {
    //     console.error('Error:', error);
    //     return null;
    //   }
    // }

    // async fetchHiddenData(urls: string[]) {
    //   const hiddenDataPromises = urls.map(async (url) => {
    //     try {
    //       const response = await axios.post(
    //         'https://api.permutive.com/v2.0/batch/events?enrich=false&sdkp=true&k=919e734f-ccf8-4682-8bdd-bf3e30f900d4',
    //         {
    //           url: url,
    //         },
    //       );
    //       return response.data;
    //     } catch (error) {
    //       console.error('hidden data:', error);
    //       return null;
    //     }
    //   });

    //   // 모든 숨겨진 데이터를 가져온 후에 URL을 반환
    //   const hiddenData = await Promise.all(hiddenDataPromises);
    //   return hiddenData;
    // }

    // async getAllData() {
    //   const urls = await this.getHTML();
    //   if (!urls) {
    //     // HTML 가져오기에 실패한 경우 처리
    //     return null;
    //   }

    //   const hiddenData = await this.fetchHiddenData(urls);
    //   return {
    //     urls: urls,
    //     hiddenData: hiddenData,
    //   };
    // }

    // 기존에 햇던 내용
    ///////////////////////////

    const options = {
      method: 'POST',
      url: 'https://api.permutive.com/v2.0/batch/events',
      params: {
        enrich: 'false',
        sdkp: 'true',
        k: '919e734f-ccf8-4682-8bdd-bf3e30f900d4',
      },
      headers: {
        accept: '*/*',
        'accept-language':
          'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,zh;q=0.5',
        'content-type': 'text/plain',
        origin: 'https://hypebeast.kr',
        referer: 'https://hypebeast.kr/',
        'sec-ch-ua':
          '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'cross-site',
        'user-agent':
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
      },
      data: '[{"user_id":"f52685e4-3213-43bc-a35a-aef89c7deca2","name":"LinkClick","cohorts":["adv","106032","110684","114647","123913","127668","144305","144308","146170","146171"],"segments":[106032,110684,114647,123913,127668,144305,144308,146170,146171],"properties":{"dest_url":"https://hypebeast.kr/footwear/page/5?after1709582459","client":{"type":"web","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36","url":"https://hypebeast.kr/footwear","domain":"hypebeast.kr","title":"신발 | Hypebeast","referrer":"https://hypebeast.kr"}},"session_id":"96558164-9c49-45d8-b53b-1d15f360009f","view_id":"1fb32c7f-cd21-4512-8bc5-5a948790f123"}]',
    };

    axios
      .request(options)
      .then(function (response) {
        console.log(response.data);
      })
      .catch(function (error) {
        console.error(error);
      });
  }

  ////////////////////////
  // async getDetaileHtml() {
  //   try {
  //     const browser = await puppeteer.launch({ headless: false });
  //     const page = await browser.newPage();
  //     await page.goto('https://hypebeast.kr/footwear');

  //           // 페이지 내용 수집
  //           const infos = await page.$$eval('.post', (elements) =>
  //           elements.map((element) => {
  //             const title = element.querySelector('post-body-title').textContent.trim();
  //             const image = element.querySelector('img').getAttribute('data-src');
  //             const subTitle = element
  //               .querySelector('.post-box-content-excerpt')
  //               .textContent.trim();
  //             const content = element.
  //             return { title, image, subTitle, content };
  //           }),
  //         );

  //         await Promise.all(
  //           infos.map(async (info) => {
  //             const news = new News();
  //             news.newsUrl = info.url;
  //             news.newsImg = info.image;
  //             news.content = info.subTitle;
  //             news.title = info.title;
  //             news.subTitle = info.subTitle;
  //             news.newsDate = new Date();
  //             await this.newsRepository
  //               .createQueryBuilder()
  //               .insert()
  //               .values(news)
  //               .execute();
  //           }),
  //         );

  //   } catch(err) {

  //   }
  // }

  // async getHTML() {
  //   try {
  //     const browser = await puppeteer.launch({ headless: false });
  //     const page = await browser.newPage();
  //     await page.goto('https://hypebeast.kr/footwear');

  //     // 스크롤링 함수 정의
  //     async function autoScroll(page) {
  //       await page.evaluate(async () => {
  //         await new Promise((resolve, reject) => {
  //           let totalHeight = 0;
  //           // 페이지 이동 픽셀 300으로 지정
  //           const distance = 300;
  //           // 일정한 간격으로 스크롤 하기 위한 setInterval
  //           const scrollInterval = setInterval(() => {
  //             // 현재 body에 있는 총 스크롤 가능한 높이 로드
  //             const scrollHeight = document.body.scrollHeight;
  //             window.scrollBy(0, distance);
  //             // 스크롤 된 높이 업데이트
  //             totalHeight += distance;
  //             // 스크롤 된 높이 전체 스크롤한 높이 비교 후
  //             // 크거나 같으면 모든 페이지 스크롤 한 것으로 판단
  //             // 스크롤 멈추기
  //             if (totalHeight >= scrollHeight) {
  //               clearInterval(scrollInterval);
  //               resolve(void 0);
  //             }
  //           }, 100);
  //         });
  //       });
  //     }

  //     // 페이지를 스크롤하여 추가 콘텐츠를 로드
  //     await autoScroll(page);

  //     // "더 알아보기" 버튼 클릭
  //     const loadMoreButton = await page.$('.button-load-more');
  //     if (loadMoreButton) {
  //       await loadMoreButton.click();

  //       // 추가 콘텐츠가 로드될 때까지 기다림
  //       await page.waitForSelector('.post-box', { timeout: 300000 });
  //     }

  //     // 페이지 내용 수집
  //     const infos = await page.$$eval('.post-box ', (elements) =>
  //       elements.map((element) => {
  //         const title = element.getAttribute('data-title');
  //         const image = element.querySelector('img').getAttribute('data-src');
  //         const excerpt = element
  //           .querySelector('.post-box-content-excerpt')
  //           .textContent.trim();
  //         const url = element.querySelector('a').getAttribute('href');
  //         return { title, image, excerpt, url };
  //       }),
  //     );

  //     await Promise.all(
  //       infos.map(async (info) => {
  //         const news = new News();
  //         news.newsUrl = info.url;
  //         news.newsImg = info.image;
  //         news.content = info.excerpt;
  //         news.title = info.title;
  //         news.newsDate = new Date();
  //         await this.newsRepository
  //           .createQueryBuilder()
  //           .insert()
  //           .values(news)
  //           .execute();
  //       }),
  //     );

  //     await page.close();
  //     await browser.close();

  //     return infos;
  //   } catch (error) {
  //     console.log(error);
  //     return error;
  //   }
  // }

 

네트워크 패킷을 분석 후 페이지 넘버를 보내

내가 원하는 페이지까지 url을 스크래핑하여 가져오는로직이다.

async getURLsFromPage(pageNumber: number): Promise<string[]> {
    try {
      const response = await axios.post(
        `https://ddddddddddddddd/${pageNumber}`,
      );
      console.log(response.data);
      const html = response.data;
      const $ = cheerioModule.load(html);
      const urls: string[] = [];

      $('.post-box').each(function () {
        const url = $(this).attr('data-permalink');
        if (url) {
          urls.push(url);
        }
      });
      console.log(urls);
      return urls;
    } catch (error) {
      console.error(error);
      return null;
    }
  }

  async getAllURLs(): Promise<string[]> {
    const allURLs: string[] = [];
    let pageNumber = 1;
    // let hasMorePages = true;

    for (let i = 1; i <= 5; i++) {
      const urls = await this.getURLsFromPage(i);
      allURLs.push(...urls);
    }
    console.log(allURLs);
    return allURLs;
  }

 

위 로직으로 콘솔을 찍어보았을 때 찍히는 로그다.

총 페이지는 626페이지가 있는데 나는 이 페이지 전체를

크롤링해오는 것이 아니라 5개의 페이지만 나오게끔 불러드렸다.

 

근데 문제는 파싱한 데이터는 빈 배열만 출력해주고 값을 가져오지 못한다는 것이다.

이 부분을 해결하려고 여러 많은 시도를 해보았지만 결과는 똑같았다ㅜㅜ

 

내가 가져오려는 페이지의 특성이 API를 숨겨둬서 로드 될 때 페이로드를 보내주는데

그 과정에서 오류가 나는 듯 하다. 시간을 더 들이면 MVP 구현이 안될 수 있기 때문에

최신 페이지만 파싱해서 가져오고 데이터를 저장해 CRUD를 만드는 것이다.

 

그 후 계속 추가되는 데이터를 cron을 이용해서 스크래핑을 해오는 것.

 

크론을 기반으로 네스트에서 지원해주는게

스케줄이라는 라이브러리다.

https://docs.nestjs.com/techniques/task-scheduling

위 사이트를 참고하여 사용할 수 있다.

 

이 방법을 적용시켜보도록 해야겠다.

 

 

관련글 더보기