크롤링(crawling)이란 데이터를 수집하고 분류하는 것을 의미한다. 주로 인터넷상의 웹페이지를 수집해서 분류하고 저장하는 것을 뜻하며 데이터가 어디에 저장되어 있는지 위치에 대한 분류 작업이 크롤링의 주요 목적이다. 크롤링의 주요 대상은 다양한 형태로 존재하는 데이터로, 데이터 생성 스타일에 따라, 정형, 반정형 그리고 비정형 데이터로 구분되기도 하지만, 데이터를 생산하는 주체에 따라 기업과 사용자가 생성하는 데이터로 분류할 수 있다
정적 크롤링은 정적인 데이터를 수집하는 방법을 말한다. 정적인 데이터란 변하지 않는 데이터를 의미한다. 한 폐이지 안에서 원하는 정보가 모드 드러날 때 정적 데이터라고 할 수 있다. 즉 정적 크롤링은 주소를 통해 단발적으로 접근하여 한 페이지 내에서 모든 작업이 이루어지기 때문에 속도가 매우 빠르다는 장점이 있지만, 수집 데이터의 한계가 존재한다는 단점이 있다.
동적 크롤링은 동적인 데이터를 수집하는 방법을 말한다. 동적인 데이터란 입력, 클릭, 로그인 등과 같이 페이지 이동이 있어야 보이는 데이터를 의미한다. 예를 들어서, 메일함에 있는 메일 제목 데이터를 수집할 때 로그인 과정을 거친 후 메일함에 들어가야 하는 동적인 과정이 필요하다. 이런 경우가 동적인 크롤링이다. 동적인 크롤링은 브라우저를 사용하여 연속적으로 접근하여 수집 데이터의 한계가 없지만, 속도가 느리다는 단점이 있다.[5]
cheerio 는 nodejs 의 라이브러리로 http 통신을 통해 데이터를 가져오는 기능을 포함하고 있지 않은 라이브러리이다.
그럼 왜쓰냐?
HTML문서를 문자열로 cheerio에 집어 넣어주면 jQuery 및 쿼리 셀렉터의 선택자 형식으로 데이터를 추출할 수 있기 때문이다.
Cheerio와 같은 HTML 문서 파싱 라이브러리를 사용하지 않는다면 정규식으로 매번 필요한 값을 문서에서 찾아야 하기 때문에 생산성 향상에 여러모로 불리 할 수 있다.
만약 HTTP 통신으로 데이터를 가져오고 싶다면 자바스크립트의 기본 라이브러리인 XMR(XMLHttpRequest)을 이용하거나 node js 라이브러리인 request 또는 request-promise 라이브러리를 이용하여 HTTP 통신의 Response 결과를 받아 온 다음 response 결과 중에서 HTML 문서를 cheerio에 로딩시켜 줘야 한다.
Puppeteer는 Chrome 팀이 개발한 Node 라이브러리 입니다.
Headless 혹은 온전한 크롬도 컨트롤 할 수 있는 고차원 API를 제공합니다. 쉽게 말해 Broswer에서 여러분이 수동으로 하는 대부분의 일들을 Puppeteer를 통해 할 수 있습니다.
예시
- 페이지의 스크린샷 혹은 PDF 파일 생성
- 싱글 페이지 어플리케이션 크롤링 및 미리 렌더링 된 컨텐츠 생성
- 폼 제출, UI 테스트, 키보드 입력등 자동화
- 테스트를 작성 하고, 최신의 크롬에서 최신 자바스크립트와 브라우저 기능들을 돌려 볼 수 있습니다.
puppeteer 라이브러리를 이용하여 내가 직접 남도학숙 홈페이지에서 식단표를 확인하는 로직을 구현한다. 그 과정에서 필요한 정보를 cheerio를 이용하여 쉽게 가져온다.
1. cheerio, puppeteer 설치
npm install cheerio puppeteer
위의 명령어를 프로젝트의 루트 디렉토리(MEAL_PLANNER_ALARM_NDHS)에서 실행한다.
[프로젝트 구조]
📂 MEAL_PLANNER_ALARM_NDHS
| -- 📂 client(React를 사용한 프론트 디렉토리)
| -- 📄 index.js(Express 서버 - 추후 설명 예정)
| -- 📄 crawl.js(크롤링 관련 코드)
2. crawl.js 코드
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');
let MealList = {
"date": "",
"breakfast": "",
"lunch": "",
"dinner": "",
};
async function crawl(){
// 가상 브라우져를 실행, headless: false를 주면 벌어지는 일을 새로운 창을 열어 보여준다(default: true)
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
const ndhs_id = ''; // 추후 로그인 폼에서 각자의 아이디 비밀번호를 입력받게 할 예정
const ndhs_pw = '';
// headless: false일때 브라우져 크기 지정해주는 코드
// await page.setViewport({
// width: 1366,
// height: 768
// });
//페이지로 가라
await page.goto('http://portal.ndhs.or.kr/index');
//해당 페이지에 특정 html 태그를 클릭해라
await page.click('body > div > div > div > div > div > div.row > div > div.login-body > div > div.col-xs-12.col-sm-5.login-con.pt20 > div > form > ul > li:nth-child(2)');
//아이디랑 비밀번호 란에 값을 넣어라
await page.evaluate((id, pw) => {
document.querySelector('#stuUserId').value = id;
document.querySelector('#stuPassword').value = pw;
}, ndhs_id, ndhs_pw);
//로그인 버튼을 클릭해라
await page.click('#student > div > div:nth-child(2) > button');
//로그인 화면이 전환될 때까지 기다려라, headless: false 일때는 필요 반대로 headless: true일때는 없어야 되는 코드
//await page.waitForNavigation()
//로그인 성공 시(화면 전환 성공 시)
if(page.url() === 'http://portal.ndhs.or.kr/dashboard/dashboard'){
//학사 페이지로 가서
await page.goto('http://portal.ndhs.or.kr/studentLifeSupport/carte/list');
// 현재 페이지의 html정보를 로드
const content = await page.content();
const $ = cheerio.load(content);
const lists = $("body > div.container-fluid > div:nth-child(6) > div > table > tbody > tr");
lists.each((index, list) => {
MealList[index] = {
date: $(list).find("th").text().replace('\n\t\t\t\t\t\t\t\t',""),
breakfast:$(list).find("td:nth-of-type(1)").text(),
lunch:$(list).find("td:nth-of-type(2)").text(),
dinner:$(list).find("td:nth-of-type(3)").text()
}
console.log(MealList[index]);
})
}
//로그인 실패시
else{
console.log('실패');
ndhs_id = 'nope';
ndhs_pw = 'nope';
}
//브라우저 꺼라
await browser.close();
};
crawl();
3. 크롤링 결과
프로젝트 루트 디렉토리에서 node crawl.js
실행 결과
- https://no-free-lunch.tistory.com/10
- http://wiki.hash.kr/index.php/%ED%81%AC%EB%A1%A4%EB%A7%81
- https://velog.io/@recordboy/Express-Puppeteer-React-Express%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%B4-%ED%81%AC%EB%A1%A4%EB%9F%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B0%8F-Heroku%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0