원하는 내용 크롤링하기
css 선택자를 사용해서 크롤링을 할 것이다.
js에서는 css선택자를 이용해 크롤링을 할 수 있도록 도와주는 모듈을 갖고 있다.
axios와 cheerio를 사용하면 쉽게 크롤링을 할 수 있다.
사용하기 위해 먼저 모듈 설치가 필요하다.
npm install axios
npm install cheerio
axios는 node.js와 브라우저를 위한 promise 기반 http 클라이언트이다. axios를 이용해 페이지를 가져온다.
cheerio를 사용해 가져온 html에서 css 선택자를 찾는다. cheerio는 jQuery와 사용법이 비슷하다.
url인자로 입력받아 axios로 html 코드를 받아온다.
cheerio를 이용해 html 코드에서 인자로 받은 css 선택자에 맞는 요소를 찾는다.
css 선택자로 불러온 값을 elements에 저장하고 elements를 돌면서 text를 뽑아 res에 저장한다.
내가 필요한 건 중식 식단이다.
크롬에서 개발자 도구를 열고 크롤링이 필요한 css에서 우클릭 > copy > css copy를 하면 필요한 css 선택자 경로를 쉽게 카피할 수 있다.
// 크롤링에 필요
const axios = require('axios');
const cheerio = require('cheerio');
// 크롤링하는 함수
async function webScraping(url, selector) {
const res = [];
const html = await axios.get(url);
const $ = cheerio.load(html.data);
const elements = $(selector);
elements.each((i, el) => {
res[i] = $(el).text();
});
return res;
}
const url = 'https://sobi.chonbuk.ac.kr/menu/week_menu.php';
const selector = 'div.contentsArea.WeekMenu > div:nth-child(229) > div:nth-child(2) > table > tbody > tr > td > ul > li:has(span, font)';
// 비동기식으로 사용해야 하므로 then 필수
webScraping(url, selector).then((res) => {
console.log(res);
});
- css selector에 has로 필터링 추가, span과 font를 가진 부분을 모두 elements에 저장
lunch.js 실행 결과
학식 페이지를 보면 테이블이 3개 있는데 class를 다르게 짠 것도 아니고 id가 있는 것도 아니고 그래서 통째로 불러와버렸다.
요일별 간격이 너무 달라서 일단 하드코딩으로 불러왔다. 리팩토링이 필요해 보인다. 일단은 출력이 되는 게 먼저라..(과제가 급함)
css selector의 has를 사용하여 필터링하고 가져와 span과 font를 가진 부분을 모두 가져와 res에 저장한다.
오늘 날짜와 비교하여 메뉴 받아오기
js에서 제공하는 Date() 함수를 사용할 것이다.
// 오늘 날짜 받아오기 위함
const todayDate = new Date();
// 오늘 요일 받아오기 위함
const todayDay = todayDate.getDay();
위와 같이 getDay()를 사용하면 오늘의 요일을 정수로 나타낸다.
일요일에 0으로 시작해서 토요일에 6으로 끝난다.
lunch.js
// 크롤링에 필요
const axios = require('axios');
const cheerio = require('cheerio');
// 오늘 날짜 받아오기 위함
const todayDate = new Date();
// 오늘 요일 받아오기 위함
const todayDay = todayDate.getDay();
// 크롤링하는 함수
// url과 selector를 받아서 오늘 날짜에 맞는 메뉴를 배열 형태로 return
async function webScraping(url, selector) {
const res = [];
const html = await axios.get(url);
const $ = cheerio.load(html.data);
const elements = $(selector);
elements.each((i, el) => {
res[i] = $(el).text();
});
// 오늘 요일을 받아 res에서 찾을 index
const index = (todayDate.getDay() - 1) * 4;
const menu = [res[index], res[index + 1], res[index + 2], res[index + 3]];
return menu;
}
lunch.js 파일을 수정했다.
lunch.js 실행 결과
이 글을 적는 날이 목요일이므로 메뉴를 제대로 불러오고 있다. 이제 토요일, 일요일 예외처리를 하고 봇에 연결하자.
토요일, 일요일 예외처리
추후 추가
index.js에 대답할 파일 추가하기
슬랙 봇에 '오늘 점심 뭐야'라고 물으면 봇이 대답을 내놓도록 index.js에 추가한다.
rtm.on으로 메시지를 입력 받고 이 메세지가 '오늘 점심 뭐야'이면 lunch 파일을 실행시킨다.
index.js 전문
const lunch = require('./lunch'); // 폴더 내의 lunch.js 파일 불러오기
rtm.on('message', (message) => {
const { channel } = message;
const { text } = message;
if (!Number.isNaN(Number(text))) {
square(rtm, text, channel);
} else {
switch (text) {
case 'hi':
greeting(rtm, channel);
break;
case '오늘 점심 뭐야': // 추가한 부분
lunch(rtm, channel);
break;
default:
rtm.sendMessage('I`m alive', channel);
}
}
});
lunch.js에서 봇에 메세지를 넘기는 함수가 필요하다.
lunch.js에 추가한 내용
// bot에 메세지 넘기는 함수
const lunch = function (rtm, channel) {
console.log('진수원 점심 메뉴 안내 및 평가');
try {
// 크롤링 결과 res로 받아와서 배열 내용 하나씩 출력
webScraping(url, selector).then((res) => {
rtm.sendMessage(`${res[0]}, ${res[1]}, ${res[2]}, ${res[3]}`, channel);
});
return Promise.resolve('success');
} catch (error) {
return Promise.resolve('error');
}
};
module.exports = lunch;
lunch가 실행되면 콘솔에 '진수원 점심 메뉴 안내 및 평가' 부분이 찍힌다.
sendMessage에서 시간이 좀 걸렸는데 res를 통째로 보내려고 해서 계속 에러가 났었다.
sendMessage 부분에서 에러가 나는 것 같은데 무슨 에러인지를 못 찾고 이것저것 건드리다가 계속 같은 에러로 귀결되길래 혹시 이거 지금 배열을 통째로 보내서 못 읽어오는 건가 해서 하나씩 읽어오도록 했더니 제대로 반환했다.
슬랙봇 대답
찍힌 로그
로그는 중요하다 ! !
코드 전문
lunch.js 전문
// 크롤링에 필요
const axios = require('axios');
const cheerio = require('cheerio');
// 오늘 날짜 받아오기 위함
const todayDate = new Date();
// 오늘 요일 받아오기 위함
const todayDay = todayDate.getDay();
// 크롤링하는 함수
// url과 selector를 받아서 오늘 날짜에 맞는 메뉴를 배열 형태로 return
async function webScraping(url, selector) {
const res = [];
const html = await axios.get(url);
const $ = cheerio.load(html.data);
const elements = $(selector);
elements.each((i, el) => {
res[i] = $(el).text();
});
// 오늘 요일을 받아 res에서 찾을 index
const index = (todayDate.getDay() - 1) * 4;
const menu = [res[index], res[index + 1], res[index + 2], res[index + 3]];
return menu;
}
const url = 'https://sobi.chonbuk.ac.kr/menu/week_menu.php';
const selector = 'div.contentsArea.WeekMenu > div:nth-child(229) > div:nth-child(2) > table > tbody > tr > td > ul > li:has(span, font)';
// bot에 메세지 넘기는 함수
const lunch = function (rtm, channel) {
console.log('진수원 점심 메뉴 안내 및 평가');
try {
// 크롤링 결과 res로 받아와서 배열 내용 하나씩 출력
webScraping(url, selector).then((res) => {
rtm.sendMessage(`${res[0]}, ${res[1]}, ${res[2]}, ${res[3]}`, channel);
});
return Promise.resolve('success');
} catch (error) {
return Promise.resolve('error');
}
};
module.exports = lunch;
크롤링할 때마다 생각하는 건데 할 때마다 욕하는데 재밌다.
참고
git repository 바로가기
https://github.com/su-mmer/JBNU_OSS_PROJECT
Node.js에서 CSS 선택자로 웹 스크래핑하기
진짜 저번에 깃 커밋 내용 안 써서 점수 깎인 거 아까워서 이 꽉 깨물고 블로그에 전부 기록해놓을 거다.ㅎㅎ