본문 바로가기

Node.js/node crawling

노드 크롤링 - 인피니티 스크롤 사이트(unsplash) 크롤링하기

반응형

 

인피니티 스크롤 사이트를 크롤링하기

 

해당 사이트는 React로 제작된 사이트로써 일반적인 axios와 cheerio 조합으로는 크롤링하기 힘들다.

 

 

먼저 크롤링해서 가져올 파트를 정한다 

 

evaluate 안에서 

nDTlD 클래스 안의 

img 태그를 가져오는 방식으로 진행한다.

 

 

정상적으로 이미지 태그가 불러와지는걸 크롬내 콘솔창으로 확인 후 

 

크롤링 코드에 적용 시켜준다.

 

해당 태그를 querySelectorAll로 받아와서 

imgEls이 존재할때 

img src를 imgs의 배열을 넣어주는 코드를 적용한다.

 

const puppeteer = require("puppeteer");
const axios = require("axios");
const fs = require("fs");

const crawler = async () => {
  try {
    const browser = await puppeteer.launch({
      headless: false,
    });
    const page = await browser.newPage();
    await page.goto("https://unsplash.com");

    let result = await page.evaluate(() => {
        let imgs = [];
        const imgEls = document.querySelectorAll('.nDTlD img._2zEKz');
        if(imgEls.length){
            // document.querySelctorAll은 배열이 아니므로 forEach만 사용가능 
            imgEls.forEach(v => {
                if(v.src){
                    imgs.push(v.src);
                }
                v.parentElement.removeChild(v);
            })
        }
        return imgs;
    });
    
    console.log(result);
    console.log(result.length);
    await page.close();
    await browser.close();
  } catch (err) {
    console.log(err);
  } finally {
  }
};
crawler();

 

해당 메소드를 실행하면 이렇게 정상적으로 잘 받아와진다.

 

 

 

 

이제 인피니티 스크롤(즉 스크롤이 될때마다 로딩을 해서 데이터를 가져오는 방식)

이기 때문에 크롤링에서도 강제로 스크롤링을 해주면서 진행을 하여야한다.

 

 

imgEls의 값을 받아와서 크롤링을해서 imgs 배열안에 넣어주고 크롤링을 다 한 경우에는 

해당 크롤링한 부분의 html을 지워주는 방식으로 진행하였다.

  const imgEls = document.querySelectorAll('.nDTlD');
        if(imgEls.length){
            // document.querySelctorAll은 배열이 아니므로 forEach만 사용가능 
            imgEls.forEach(v => {
                const img = v.querySelector("img._2zEKz");
                if(img && img.src){
                    imgs.push(img.src);
                }
                v.parentElement.removeChild(v);
                
            })
        }

 

 

또한 해당 figure 태그가 들어왔을때 로딩을 해서 데이터를 가져와야 하므로

 

 

 

해당 코드도 추가해주었다

  await page.waitForSelector("figure");

 

 

변경된 코드

 

while문을 통해 배열이 result값이 30개가 넘어가게되면 가져오는것을 종료하는 방식으로 진행하였다.

const puppeteer = require("puppeteer");
const axios = require("axios");
const fs = require("fs");

const crawler = async () => {
  try {
    const browser = await puppeteer.launch({
      headless: false,
    });
    const page = await browser.newPage();
    await page.goto("https://unsplash.com");


let result = []
   while(result.length <= 30){
    let srcs = await page.evaluate(() => {
        // 절대 좌표 
        window.scrollTo(0, 0);
        let imgs = [];
        const imgEls = document.querySelectorAll('.nDTlD');
        if(imgEls.length){
            // document.querySelctorAll은 배열이 아니므로 forEach만 사용가능 
            imgEls.forEach(v => {
                const img = v.querySelector("img._2zEKz");
                if(img && img.src){
                    imgs.push(img.src);
                }
                v.parentElement.removeChild(v);
                
            })
        }
        //30번했는데 벌써 크롤러가 바닥으로 갔기때문에
        //상단에서 최상단으로 한번 스크롤을 해주고 진행한다 
        // scrollBy 상대좌표
        window.scrollBy(0, 100);
        setTimeout(() => {
          window.scrollBy(0, 200);
        }, 500);
        return imgs;
    });
    
    result = result.concat(srcs);
    //선택자를 기다릴 수 있다.
    await page.waitForSelector("figure");
    console.log("태그 로딩 완료");
   }
    
    console.log(result);
    console.log(result.length);
    await page.close();
    await browser.close();
  } catch (err) {
    console.log(err);
  } finally {
  }
};
crawler();


// reduce를 활용해서 한번 리팩토링을 진행해 보았으나 
    // 원본 배열을 손상시키기때문에 forEach로 하는게 맞다는 생각이 들었다.

    // while (result.length <= 30) {
    //   // 클래스명이 정말 자주 바뀌기때문에 확인해줘야한다.
    //   const srcs = await page.evaluate(() => {
    //     window.scrollTo(0, 0);
    //     const imgEls = document.querySelectorAll(".nDTlD"); // 사이트 바뀌었을 때 클래스 적절히 바꾸기
    //     if (imgEls.length) {
    //       // querySelectorAll은 배열이 아니라서 forEach만 사용이 가능하다.
    //       const data = Array.from(imgEls).reduce((acc, cur) => {
    //         const img = cur.querySelector("img._2zEKz");
    //         if (img && img.src) {
    //           acc.push(img.src);
    //         }
    //         //크롤링을 한후에 현재 값을 지워준다.
    //         cur.parentElement.removeChild(cur);
    //         return acc;
    //       }, []);
    //       window.scrollBy(0, 100);
    //       setTimeout(() => {
    //         window.scrollBy(0, 200);
    //       }, 500);
    //       return data;
    //     }
    //   });
    //   result = result.concat(srcs);
    //   await page.waitForSelector("figure");
    // }

 

 

 

크롤링한 이미지 파일 추가하기

 

해당 코드를 활용해서 폴더에 이미지를 저장하는 코드를 추가해주었다.

fs.readdir('imgs', (err) => {
  if(err){
    console.error("imgs 폴더가 없어 imgs 폴더를 생성합니다.")
    fs.mkdirSync('imgs');
  }
});
  await page.waitForSelector("figure");
    console.log("태그 로딩 완료");
   }
    result.forEach(async(src) => {
      const imgResult = await axios.get(src.replace(/\?.*$/, ''), {
        responseType:'arraybuffer'
      });
      // 개발자도구 네트워크를 활용해 확장자 파악하기
      fs.writeFileSync(`imgs/${new Date().valueOf()}.jpeg`, imgResult.data);
    })

 

전체코드

const puppeteer = require("puppeteer");
const axios = require("axios");
const fs = require("fs");

fs.readdir('imgs', (err) => {
  if(err){
    console.error("imgs 폴더가 없어 imgs 폴더를 생성합니다.")
    fs.mkdirSync('imgs');
  }
});

const crawler = async () => {
  try {
    const browser = await puppeteer.launch({
      headless: false,
    });
    const page = await browser.newPage();
    await page.goto("https://unsplash.com");

    // let result = await page.evaluate(() => {
    //     let imgs = [];
    //     const imgEls = document.querySelectorAll('.nDTlD img._2zEKz');
    //     if(imgEls.length){
    //         // document.querySelctorAll은 배열이 아니므로 forEach만 사용가능 
    //         imgEls.forEach(v => {
    //             if(v.src){
    //                 imgs.push(v.src);
    //             }
    //             v.parentElement.removeChild(v);
    //         })
    //     }
    //     return imgs;
    // });

    // 리팩토링을 진행한다
let result = []
   while(result.length <= 30){
    let srcs = await page.evaluate(() => {
        // 절대 좌표 
        window.scrollTo(0, 0);
        let imgs = [];
        const imgEls = document.querySelectorAll('.nDTlD');
        if(imgEls.length){
            // document.querySelctorAll은 배열이 아니므로 forEach만 사용가능 
            imgEls.forEach(v => {
                const img = v.querySelector("img._2zEKz");
                if(img && img.src){
                    imgs.push(img.src);
                }
                v.parentElement.removeChild(v);

            })
        }
        //30번했는데 벌써 크롤러가 바닥으로 갔기때문에
        //상단에서 최상단으로 한번 스크롤을 해주고 진행한다 
        // scrollBy 상대좌표
        window.scrollBy(0, 100);
        setTimeout(() => {
          window.scrollBy(0, 200);
        }, 500);
        return imgs;
    });
    
    result = result.concat(srcs);
    //선택자를 기다릴 수 있다.
    await page.waitForSelector("figure");
    console.log("태그 로딩 완료");
   }
    result.forEach(async(src) => {
      const imgResult = await axios.get(src.replace(/\?.*$/, ''), {
        responseType:'arraybuffer'
      });
      // 개발자도구 네트워크를 활용해 확장자 파악하기
      fs.writeFileSync(`imgs/${new Date().valueOf()}.jpeg`, imgResult.data);
    })
    console.log(result);
    console.log(result.length);
    await page.close();
    await browser.close();
  } catch (err) {
    console.log(err);
  } finally {
  }
};
crawler();

 

 

 

 

본 글은 아래 인프런 강의를 듣고 작성된 내용입니다

https://www.inflearn.com/course/%ED%81%AC%EB%A1%A4%EB%A7%81

 

Node.js로 웹 크롤링하기 - 인프런 | 강의

네이버, 아마존, 트위터, 유튜브, 페이스북, 인스타그램, unsplash.com 등의 사이트를 크롤링하며 실전에 적용해봅니다., Node.js로 웹 크롤링하기 Node.js와 Puppeteer를 활용해 웹 사이트를 크롤링하여 원

www.inflearn.com

 

반응형