관련지식
node.js, request, cheerio, moment

예전에 https://velvio.tistory.com/219 블로그의 글을 참고하여 EBS의 외국어 라디오를 녹음해서 듣고 있었습니다. 그런데 파일명이 항상 고정으로 만들어지는게 아쉬워서 EBS 라디오 편성표 정보 를 이용하여 방송 주제명을 파일로 사용하려고 합니다. 예를들어 기존에 RHK 세계 명작 시리즈 01.mp3 로 파일이 만들어졌다면 RHK 세계 명작 시리즈 - 플란더스의 개 (A Dog of Flanders) Part 4.mp3 이렇게 만들려는 것입니다.

준비

따라서 EBS에서 제공하는 라디오 방송 편성표를 파싱하여 데이터로 바꾸고 그 정보를 이용하여 녹음할것입니다. 여기서는 데이터를 파싱하는 과정을 정리하겠습니다. 방송 편성표 URL은 아래 url을 사용할 것입니다.

http://www.ebs.co.kr/schedule?channelCd=IRADIO&date=20190925&onor=IRADIO

이번 프로그램에서 사용할 패키지는 request와 cheeriomoment 입니다. request는 URL의 request/response를 쉽게 해주는 패키지이고 cheerio는 HTML 텍스트를 마치 JQuery를 이용한것처럼 탐색을 할수 있도록 하는 패키지입니다. 일일이 문자열을 파싱하고 싶지 않다면 반드시 사용하는것이 좋습니다. moment는 날짜를 다루는 매우 유용한 기능을 제공합니다.

설치가 안되어있다면 아래 커맨드를 입력하여 설치하면 됩니다.

  1. npm install request
  2. npm install cheerio
  3. npm install moment

데이터 요청

request 패키지를 이용할땐 여러 옵션을 사용할 수 있지만 지금은 url과 인코딩 정도만 설정하고 호출하면 됩니다.

  1. const request = require('request');
  2. const cheerio = require('cheerio');
  3. const moment = require('moment');
  4. let url = 'http://www.ebs.co.kr/schedule?channelCd=IRADIO&onor=IRADIO&date=20190927';
  5. request({
  6. uri : url,
  7. encoding: 'utf-8'
  8. }, function(error, response, body) {
  9. });

EBS의 URL을 보면 쿼리 스트링에 년월일로 된 날짜가 있습니다. 해당 날짜에 맞는 편성표 정보를 보여주는것이죠. 프로그램이 실행되는 날짜로 세팅되도록 수정합니다.

  1. let today = moment().format('YYYYMMDD');
  2. let url = 'http://www.ebs.co.kr/schedule?channelCd=IRADIO&onor=IRADIO&date=' + today;

데이터 가공

cheerio를 이용하여 HTML Element를 찾는 방법은 Dom Selector 와 거의 동일합니다. JQuery 경험이 있으면 어렵지 않게 사용할수 있지만 다른 부분도 있으므로, 처음 사용한다면 API를 찾아보시는것이 좋습니다. 이제 HTML 문서에서 데이터를 추출하면 됩니다.

  1. let $lis = $('.main_timeline li');
  2. for(let i = 0; i < $lis.length; i++) {
  3. let $li = $lis.eq(i);
  4. let program = {
  5. beginTime : $li.find('.time > span').text(), //방송 시작시간
  6. title : $li.find('.tit strong').text(), //방송 이름
  7. part : $li.find('.tit .txt_cnt').text() //방송 주제
  8. };
  9. schedule.push(program);
  10. }

위 소스로로 방송 시작시간, 제목, 주제를 추출할수 있습니다. 여기에 방송 길이(몇분짜리 방송인지)도 추가 하겠습니다. 방송 길이는 앞뒤 방송의 시작시간을 계산하면 알수 있습니다. 계산을 위해 시작시간을 타임스탬프로 저장해두는게 편합니다.

  1. let beginTime = $li.find('.time > span').text();
  2. let [hh, mm] = beginTime.split(':').map(function(v) {
  3. return parseInt(v);
  4. });
  5. let program = {
  6. beginTime : beginTime, //방송 시작시간
  7. timestamp : baseDT.clone().minute(hh * 60 + mm).format('X') * 1000, //방송 시작시간(timestamp로 변환)
  8. title : $li.find('.tit strong').text(), //방송 이름
  9. part : $li.find('.tit .txt_cnt').text() //방송 주제
  10. };

이제 앞뒤 방송의 타임스탬프 값을 계산하면 앞 방송의 러닝타임을 알수 있습니다. timestamp는 밀리초 단위이므로 방송길이는 초(Second)로 저장하겠습니다.

  1. if(i > 0) {
  2. let prev = schedule[i - 1];
  3. prev.running = (program.timestamp - prev.timestamp) / 1000; //현재 방송의 시작시간으로 이전 방송의 방송길이를 계산한다
  4. }

그런데 이 방법으로는 마지막 방송의 길이는 계산할 수가 없습니다. 하루에 동일한 방송을 2,3회씩 하고 있으니 마지막 방송 정도는 데이터에서 제외해도 될것 같네요. 아니면 낮에 했던 동일한 방송의 시간을 동일하게 세팅할수도 있습니다. 설명은 따로 안하지만 최종 샘플에는 동일한 방송시간을 복사하는 로직을 포함하겠습니다.

이렇게 뽑아낸 데이터는 사용 용도에 따라 파일로 저장해도 되고, url 요청에 response를 해줘도 됩니다. 저는 이 정보를 EBS 라디오를 녹음하는 쉘 스크립트에서 사용할 것이기 때문에 하루에 한번 json 파일로 저장시킬 것입니다. 제가 사용하는 편성표 JSON URL이 필요하신 분은 연락주시면 알려드리겠습니다.

최종 소스

  1. const request = require('request');
  2. const cheerio = require('cheerio');
  3. const moment = require('moment');
  4. let today = moment().format('YYYYMMDD');
  5. let url = 'http://www.ebs.co.kr/schedule?channelCd=IRADIO&onor=IRADIO&date=' + today;
  6. request({
  7. uri : url,
  8. encoding: 'utf-8'
  9. }, function(error, response, body) {
  10. if(error) {
  11. console.error(error);
  12. return;
  13. }
  14. let $ = cheerio.load(body);
  15. let schedule = [];
  16. let tmpHash = [];
  17. let baseDT = moment(today, 'YYYYMMDD');
  18. let $lis = $('.main_timeline li');
  19. for(let i = 0; i < $lis.length; i++) {
  20. let $li = $lis.eq(i);
  21. let beginTime = $li.find('.time > span').text();
  22. let [hh, mm] = beginTime.split(':').map(function(v) {
  23. return parseInt(v);
  24. });
  25. let program = {
  26. beginTime : beginTime, //방송 시작시간
  27. timestamp : baseDT.clone().minute(hh * 60 + mm).format('X') * 1000, //방송 시작시간(timestamp로 변환)
  28. title : $li.find('.tit strong').text(), //방송 이름
  29. part : $li.find('.tit .txt_cnt').text() //방송 주제
  30. };
  31. if(i > 0) {
  32. let prev = schedule[i - 1];
  33. prev.running = (program.timestamp - prev.timestamp) / 1000; //현재 방송의 시작시간으로 이전 방송의 방송길이를 계산한다
  34. tmpHash[prev.part] = prev.running; //방송의 주제 이름을 해시키로 사용한다
  35. if(i == $lis.length - 1) { //마지막 방송
  36. program.running = tmpHash[program.part]; //동일한 방송 주제의 러닝타임을 복사
  37. }
  38. }
  39. schedule.push(program);
  40. }
  41. console.log(schedule);
  42. });