[nodejs 크롤링] 3장. 이미지와 CSS선택자

Joey Hong·2020년 9월 26일
0

크롤링

목록 보기
3/5

제로초 github

3-1. puppeteer 시작하기

  • 매일 스크린샷을 찍을 수 있다
  • 개별 이미지도 다운 가능
    • 링크 주소를 소스에 넣으면 보이지만 남의 이미지 트래픽을 사용하는 것이기때문에 부담을 줄 수 있다
    • 직접 다운 받아 사용

이미지 요청을 하면 0과 1로 이루어진 buffer형태로 오고 그것을 fs로 읽는다

🤖 노드로 폴더 만들기

  • axios 설치
npm i axios

이미지 소스 주소 뒤 query string을 제거

  • 이미지가 더 커지는 등의 이점을 얻을 수도 있다
//index.js
const axios = require('axios');		//axios
const fs = require('fs');			//buffer를 읽을 fs

//'screenshot' 폴더가 있는지 확인
//없으면(err) 폴더 생성
fs.readdir('screenshot', (err) => {		
	if (err) {
    	console.error('screenshot 폴더가 없어 screenshot 폴더를 생성합니다.');
        fs.mkdirSync('screenshot');
    }
});

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

🥊 Sync

  • sync로 하면 동기로 처리
    • sync 메서드는 프로그램의 처음과 끝에만 사용 권장
      ☛ 서버는 끝이 없이 계속 살아있으니 처음에만 사용
    • fs.readdir에서 (err) => {} 콜백함수는 이벤트루프 속에서 동작
      ☛ 여기서는 fs.mkdir('screenshot')으로 해도 상관없다

🥊 선택자

$('.poster img')
//<img src="https://" alt="" >

$('.poster img').src
//"https://"

🤖 이미지 가져오기

//page.evaluate 내에 DOM 사용가능
const result = await page.evaluate(() => {
  const scoreEl = document.querySelector('.score.score_left .star_score');
  let score = '';
  if (scoreEl) {
    score = scoreEl.textContent;
  }
  //이미지 가져오는 부분
  const imgEl = document.querySelector('.poster img');
  let img = '';
  if (imgEl) {
    img = imgEl.src;
  }
  //객체로 묶어서 리턴
  return { score, img };
});
//평점이 있으면 
if (result.score) {
  console.log(r.제목, '평점', result.score.trim());
  const newCell = 'C' + (i + 2);
  add_to_sheet(ws, newCell, 'n', parseFloat(result.score.trim()));
}

3-2. axios로 이미지 저장하기

🤖 이미지 저장하기

if (result.img) {
  //imgResult에 이미지들의 버퍼형태 저장
  const imgResult = await axios.get(result.img, {	//이미지 주소 result.img를 요청
    responseType: 'arraybuffer',	//buffer가 연속적으로 들어있는 자료 구조를 받아온다
  });
  //fs로 읽어준다
  //console에서 이미지 확장자 확인 후 같은 것으로 적용
  fs.writeFileSync(`poster/${r.제목}.jpg`, imgResult.data);
}
  • 아래처럼 poster폴더에 이미지들이 저장된다

🤖 이미지 저장하기 완성 코드

//index.js
const xlsx = require('xlsx');
const puppeteer = require('puppeteer');
const add_to_sheet = require('./add_to_sheet');
const axios = require('axios');		//axios
const fs = require('fs');			//buffer를 읽을 fs

const workbook = xlsx.readFile('xlsx/data.xlsx');
const ws = workbook.Sheets.영화목록;
const records = xlsx.utils.sheet_to_json(ws);

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

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

const crawler = async () => {
  try {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();
    await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.0 Safari/537.36');	
    add_to_sheet(ws, 'C1', 's', '평점');

    for (const [i, r] of records.entries()) {
      await page.goto(r.링크);
      const result = await page.evaluate(() => {
        const scoreEl = document.querySelector('.score.score_left .star_score');
        let score = '';
        if (scoreEl) {
          score = score.textContent;
        }
        //이미지 가져오는 부분
        const imgEl = document.querySelector('.poster img');
        let img = '';
        if (imgEl) {
          img = imgEl.src;
        }
        //객체로 묶어서 리턴
        return { score, img };
      });
      //평점이 있으면 
      if (result.score) {
        console.log(r.제목, '평점', result.score.trim());
        const newCell = 'C' + (i + 2);
        add_to_sheet(ws, newCell, 'n', parseFloat(result.score.trim()));
      }
      if (result.img) {
        //imgResult에 이미지들의 버퍼형태 저장
        const imgResult = await axios.get(result.img, {	//이미지 주소 result.img를 요청
        	responseType: 'arraybuffer',	//buffer가 연속적으로 들어있는 자료 구조를 받아온다
        });
        //fs로 읽어준다
        //console에서 이미지 확장자 확인 후 같은 것으로 적용
        fs.writeFileSync(`poster/${r.제목}.jpg`, imgResult.data);
	  }
      await page.waitFor(1000);
    }
    await page.close();
    await browser.close();
    xlsx.writeFile(workbook, 'xlsx/result.xlsx');	//엑셀에 입력
  } catch (e) {
  	console.error(e);
  }
}
crawler();

3-1. 브라우저 사이즈 조절과 스크린샷

  • puppeteer 크롤링 동안 화면이 계속 잘려있는 것을 확인할 수 있다
    • 스크린샷을 찍기 위해 먼저 브라우저와 화면을 키워야한다

🤖 사이즈 조절

🥊 브라우저 사이즈 조절

const browser = await puppeteer.launch({
	headless: process.env.NODE_ENV === 'production',
  	args: ['--window-size=1920,1080']
});

🥊 화면 사이즈 조절

const page = await browser.newPage(); //이거 아래에
await page.setViewport({
	width: 1920,
  	height: 1080,
})

🤖 페이지 스크린샷 찍기

//방법1
const buffer = await page.screenshot();	//스크린샷 찍어서 buffer에 저장
fs.writeFileSync('screenshot/', buffer);	//fs로 읽은 것을 screenshot폴더에 저장

//방법2
await page.screenshot({ path: `screenshot/${r.제목}.png` });	//바로 스크린샷 폴더에 제목 이름으로 저장
  • puppeteer 스크린샷은 default가 png
    • jpg로 변환 가능

🥊 풀스크린

웹툰처럼 긴 것 한 번에 캡처 가능

await page.screenshot({ path: `screenshot/${r.제목}.png`, fullPage: true });

🥊 클립

  • 원하는 부분만 클립
  • 좌측 상단이 0, 0
  • puppeteer 클립을 위해 필요한 정보
    • 보여줄 이미지의 시작점(빨간부분)
    • 시작점부터의 너비와 높이
  • clip과 fullPage는 둘 중 하나만 사용해야한다
await page.screenshot({ 
  path: `screenshot/${r.제목}.png`, 
  clip: {
    x: 100,
    y: 100,
    width: 300,
    height: 300,
  }
});

🤖 스크린샷까지 완성 코드

//index.js
const xlsx = require('xlsx');
const puppeteer = require('puppeteer');
const add_to_sheet = require('./add_to_sheet');
const axios = require('axios');
const fs = require('fs');

const workbook = xlsx.readFile('xlsx/data.xlsx');
const ws = workbook.Sheets.영화목록;
const records = xlsx.utils.sheet_to_json(ws);

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

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

const crawler = async () => {
  try {
    const browser = await puppeteer.launch({
		headless: process.env.NODE_ENV === 'production',
  		args: ['--window-size=1920,1080']
	});
    const page = await browser.newPage();
    await page.setViewport({
        width: 1920,
        height: 1080,
    })
    await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.0 Safari/537.36');	
    add_to_sheet(ws, 'C1', 's', '평점');

    for (const [i, r] of records.entries()) {
      await page.goto(r.링크);
      const result = await page.evaluate(() => {
        const scoreEl = document.querySelector('.score.score_left .star_score');
        let score = '';
        if (scoreEl) {
          score = score.textContent;
        }
        const imgEl = document.querySelector('.poster img');
        let img = '';
        if (imgEl) {
          img = imgEl.src;
        }
        return { score, img };
      });
      if (result.score) {
        console.log(r.제목, '평점', result.score.trim());
        const newCell = 'C' + (i + 2);
        add_to_sheet(ws, newCell, 'n', parseFloat(result.score.trim()));
      }
      if (result.img) {
        //이미지 처리하는 곳에서 스크린샷 바로 저장
        await page.screenshot({ path: `screenshot/${r.제목}.png`, fullPage: true });
        const imgResult = await axios.get(result.img, {
        	responseType: 'arraybuffer',
        });
        fs.writeFileSync(`poster/${r.제목}.jpg`, imgResult.data);
	  }
      await page.waitFor(1000);
    }
    await page.close();
    await browser.close();
    xlsx.writeFile(workbook, 'xlsx/result.xlsx');
  } catch (e) {
  	console.error(e);
  }
}
crawler();

3-4 보너스 querySelector와 CSS선택자

🤖 선택자

크롤링할 때마다 이미지 태그들을 찾아서 사용해야한다

  • CSS 선택자를 사용해서 태그를 선택한다
    • 이미지 태그를 선택하게 도와주는 선택자
	document.querySelector('img')

🥊 $ 함수

달러 함수는 document.querySelector 함수를 뜻한다

  • 처음 하나만
document.querySelector()
$()
  • 모두 선택
document.querySelectorAll()
$$()

3-4 보너스 CSS선택자 조합하기

🤖 선택자 조합

선택자 조합해서 특정 태그 선택 가능

🥊 클래스와 아이디

  • .은 클래스
$$('div.spot')
//spot 클래스명을 가진 div 모두
  • #은 아이디
$$('div#header')
//header란 아이디를 가진 div 모두

🥊 여러 클래스 이름을 가진 태그

$$('div.score.score_left')
//<div class="score score_left>
//클래스이름이 score면서 score_left인 div 태그

🤖 자손, 자식

🥊 공백 - 자손선택자

  • 조상과 자손
$$('div a img')
//div 안의 a태그 안의 img 태그 모두

🥊 꺽쇠 - 자식선택자

  • 부모와 자식
$$('div > a > img')
//div 바로 아래 a태그, 또 그 아래 바로 img인 태그 모두

🥊 배열의 몇 번째

$$('div.poster img')[1]
//poster라는 클래스명을 가진 div 안에 있는 img 중 두번째 것

🤖 선택자 최적화

  • 선택자는 최소화하는 것이 중요
  • div도 제거해도 된다
$$('.poster img')
//poster라는 클래스명을 가진 div

🤖 선택자 세부정보

  • 세부 정보
$$('.score.score_left').textContent
//8.78

$$('.score.score_left').innerHTML
//안에 들어있는 태그들
  • 특정 옵션
$$('.poster img').src
//img의 소스만

$$('.poster img').alt
//"알리타: 배틀 엔젤"

$$('img[width="26"]')
//img의 width가 26인 태그들
profile
개발기록

0개의 댓글