Puppeteer 웹 스크래핑

soya·2020년 8월 11일
4

NODE.JS라이브러리

목록 보기
1/1
post-thumbnail

Puppeteer

Puppeteer는 Node.js 의 라이브러리로, 크롬 혹은 크로미움을 Headless 브라우저( GUI 가 없는 웹 브라우저) 상태로 조작할 수 있게 도와주는 API를 제공해 준다.

SPA 는 HTML에 내용이 바로 담겨 오지 않고 JavaScript를 이용해 비동기적으로 혹은 뒤늦게 불러와 지기 때문에,
Cheerrio 같은 마크업 문서를 파싱하는 라이브러리로 페이지 데이터를 가져오는데는 한계가 있다.
(요녀석도 나중에 글써야지)

이런 비동기적으로 불러진 데이터를 가져오기위해 Puppeteer를 사용하려고 한다.

비슷한 라이브러리로는 Selenium이 대표적이다.
파이썬 진영에서 스크래핑, 크롤링 할때 많이 쓰이는듯 하다. (다른 언어도 지원)(안써봄)
Puppeteer는 Node.js!

주요 기능

  • 페이지 스크린샷 혹은 PDF 생성
  • SPA 를 크롤링하고, pre-rendering 컨텐츠를 생성할 수 있다. ( 대충 SPA를 pre-렌더링해서 크롤링한단뜻인가)
  • 폼 양식 전송, UI 테스트, 입력값 넣기 등 자동화 (테스트 프로그램 만들때 유용)
  • 웹 페이지에서 자동화 테스트를 진행한다. 최신버전의 JS와 크롬으로 수행 가능하다.
  • timeline trace 를 기록해 당신의 사이트의 성능 이슈를 진단할 수 있다.

대략 웹 크롤링 혹은 웹 테스트와 성능체크에 사용할 수 있다는것

https://github.com/puppeteer/puppeteer
공식 깃헙으로 상단 사이트에서 자세한 설명과 사용법을 알 수 있다.

글의 목적

Puppeteer를 사용하며 겪었던 문제를 기록해 다음번에 할때 또 헤메지 않도록 기록하고자 한다.
스크래핑을 위주 기능 중심으로 작성하려고 한다.

설치나 기본 셋팅은 적지 않겠다.
상단 깃헙 링크에서 확인 가능하다.

써먹을곳이 많은 라이브러리이니 확실하게 익혀두고 알차게 써먹자

주요 API


https://github.com/puppeteer/puppeteer/blob/v5.2.1/docs/api.md
이곳에서 더 다양한 API 콜 들을 볼 수 있다.
"~~ 요래요래 ~~ 하고싶은데 뭐써야되지!!" 싶으면 들어가보자

밑에서 부턴 자주쓰이는 혹은 헷갈릴 만한(내가 헷갈렸던..) API 위주로 작성했다.

launch()


브라우저를 열때, 앞서 말했듯 Puppeteer는 headless 브라우저로 동작하기때문에, GUI가 없다!
만약 실행이 잘되는지 체크하기 위해 화면을 보고싶다면 headless : false 로 설정하여 런치 해주면된다.

	new browser = await puppeteer.launch({ headless:  false });

화면 사이즈가 작다면 추가로 view 사이즈를 설정해준다

         const page = await browser.newPage();
              await page.setViewport({
                width: 1920,
                height: 1080,
         });

$, $$, $$eval, $eval


$()는 선택자를 인자로 받아 하나의 element를 반환한다
$$(),$$eval()은 해당 선택자의여러 element를 배열로 반환한다.
(여러개의 엘리먼트가 존재할 경우 $로 찾으면 최상단 하나의 엘리먼트만 리턴된다)

$eval() 첫번째 인자로는 선택자를( $() 에 넣던 선택자 ), 두번째인자로는 콜백함수를 받게 된다.
$()가 리턴하는 엘리먼트를 바로 두번째 인자인 콜백함수의 변수로 넘겨 evaluate를 실행할 수 있게 한다.

        let  lastIndex = await  page.$$eval(".paging > ul > li", (element) => {
            return  element.length;
        });
        console.log(lastIndex); // .paging > ul 태그 하단의 li 엘리먼트의 갯수 반환 

evaluate()


page.evaluate()는 변수를 받아, DOM 안에서 자바스크립트를 실행 하도록 해준다.
그리고 실행한 내용의 값을 리턴해 가져올 수 있다.

	const el = await page.$(".paging > ul");
	
	const  len = await  page.evaluate((el ) => {	
		return ul.innerHTML;
	}, el );

엘리먼트 배열은 넣지 못하더라. evaluate 인자로 넣어주려면 하나씩 넣어야 한다.

	const els = await page.$$(".paging > ul > li")
	await page.evaluate((el) => el.innerHTML, els[0]);

그래서 foreach 를 사용해 엘리먼트 배열을 조회했다.

	let  els = await  page.$$(".paging > ul > li");
		els.forEach(async (el) => {
		const  a = await  page.evaluate((el) => {
			return  el.innerHTML;
		}, el);
		console.log(a);
	});

async 안해주고 await 하려고 삽질했다. 🤔🤔🤔 다른방법 아시는분?

click()


page.click(selecter); 를 통해 엘리먼트를 클릭할 수 있다. 클릭 시 해당 기능이 실행된다.

이를 포함하고 있는 상위 엘리먼트를 클릭해도 동일하다.

	const page_now = await  page.$(".paging > ul > li:nth-child(2)");
	await page_now.click(); 
	await page.click(".paging > ul > li:nth-child(2)"); //2page로 이동

pagination 되어진 SPA 의 페이지들을 조회하기 위해서는 각 페이지 이동을 클릭해 줘야한다.

이때 click 을 누른뒤 바로 로직을 실행하면, 아직 DOM이 로딩되지 않아 기존 페이지에서 로직이 실행된다.

waitForSelector()


그래서 렌더링 될 때까지 기다려 줘야 하는데...
waitFor(10000); 이런식으로 시간을 기다려주는건 비효율적이잖아.

이럴때 page.waitForSelector() 를 사용한다.

해당 태그의 콘텐츠가 로드될 때까지 대기한다.

	await  next_page.click();
	await  page.waitForSelector(".paging > ul > li:nth-child(" + index + ") > a.active" );

내가 스크래핑 하는 사이트의 경우
해당 페이지의 넘버링이 된 태그에 active 라는 클래스가 생성되어 있엇다.

내가 원하는 index의 페이지( a태그에 active 클래스가 있는 )가 로드 될 때까지 기다리라는 것.

Trouble


1) page.evaluate() 외않되?

: 왜 page.evaluate() 안에서는 밖에 선언한 아이들이 힘을 못쓸까 ?
ex)

	const URL = "https://www.google.com"
	
	const google = page.evaluate(()=>{
		const link = URL + ... ;	
		return link;
	})
	console.log(link) //  function URL() { [native code] } ...

URL이 function URL() { [native code] } 로 출력이 되었다.

evalueate 내부에서는 외부에서 선언한 변수들이 사용이 안된다.

해결

page.evaluate()는 실행중인 브라우저 페이지 내에서의 자바스크립트 코드 동작이기 때문에,
그 브라우저 스크립트에 정의되지않은 변수는 사용할 수 없는 것이다!
(=> 우리가 크롬 개발자 도구에서 자바스크립트로 코딩하는것과 동일한것 )

멍청한 나!


2) document.queryselector() 귀찮아요..

document.querySelector를 사용해 엘리먼트에 접근

코드를 편하게 쓰기 위해 cheerio 라이브러리를 사용할까 했지만,
DOM내에서 사용해야 하는 경우( page.evaluate() )라 브라우저 내에서 cheerio 라이브러리를 돌리게 되므로 무거워 질 수 있다.
(물론 내코드는 스크래핑 규모가 작아서 괜찮겠지만! )

그래서 document.querySelector 하니 너무 코드가 길다... 깔끔하지 않다

해결

대신! 브라우저에서 JQuery는 사용할 수 있다! 왜냐면 그 브라우저에서 JQuery를 사용할테니까!(아마?)

결론은 jQuery 문법을 이용해 데이터를 가져올 수 있다

++ ) jQuery를 지원하지 않는다면, 내가 직접 jQuery를 올려서 사용할 수도 있다고한다. 그건 일단 모르고 있어야지.


3) 재귀

페이지를 넘기고, 조회하는것을 반복하기 위해 next 라는 꼬리재귀 함수를 만들었다. ( return 에서 재귀하는 경우 )

	async  function  next(page, dataArray, index, lastIndex) {
		if (index > lastIndex) return;
		...// 페이징 해줌 
		
		return  getAllData(page).then((resolvedData) => { //getAllData() : 데이터 스크래핑 함수 
			let  newArray = dataArray.concat(resolvedData);
			if (index < lastIndex) {
				return  next(page, newArray, ++index, lastIndex);
			} else {
				return  newArray;
			}
		});
	}

마무리

요녀석 가지고 재밋는 토이프로젝트 하나 해보려고 한다
뭔가 쓸모없지만 나한텐 필요한거 혹은 귀여운거 🤔🤔🤔

또 정리할게 생기면 정리할 생각이다.

프로젝트기간(2020/08/05~08/07)
2020/08/11 작성

profile
re제로부터시작하는코딩생활

0개의 댓글