[nodejs] 프로그래밍을 통한 단어장 만들기 #2 - Medium 파싱

in #kr-dev6 years ago (edited)

서문

안녕하세요 @wonsama 입니다. 이전 글에 이어 이번 시간에는 medium 사이트 주소만 입력 받으면 해당 컨텐츠를 가져오는 방식으로 동작하도록 구현해 봤습니다.

동작방식

  1. medium 사이트 주소 입력 받기
  2. 해당 주소의 정보 scraping
  3. scraping 된 정보에서 script 부분에 포함된 data parsing
  4. UI에 해당 정보 display

DONE

  • 파일에서 읽기
  • 문장에서 단어 추출
  • 이미 알고 있는 단어는 추출 제외

ING

  • url 정보 입력을 통해 문서 읽기 ( 웹사이트마다 별도 파서가 필요예상 )
  • express로 ui 구성하기 ( 모르는 단어 선택을 쉽게 하기 위함 + 웹에서도 동작하도록 )

TODO LIST

  • google translate 연동으로 모르는 단어 자동 번역
  • 단어 맞추기 게임 ( 외운 날짜 기준으로 날짜 범위 설정 가능)
  • electron으로 desktop app 형태 만들기

( 생각 날때마다 좀 더 추가예정)

스크린샷 2018-04-17 오후 3.47.43.png

[이미지] nodejs로 개발한 단어장 v0.1

스크린샷 2018-04-17 오후 3.48.12.png

[이미지] medium 사이트에서 본 동일 컨텐츠

result

결과물을 보면 알 수 있듯이 medium 사이트에서 읽는 것보단 편하게 글만 읽을 수 있게 되었네요 ^^
읽기모드와 유사

source

일단 핵심이 되는 소스만 공개해보면 아래와 같습니다.

[index.js]

let express = require('express');
let router = express.Router();
let request = require('request');
let cheerio = require('cheerio');

/* POST home page. */
router.get('/', function(req, res, next) {
    res.render('index', { isLoad : false });
});

/* GET home page. */
router.post('/', function(req, res, next) {

    // let url = 'https://medium.com/starts-with-a-bang/we-still-dont-understand-why-time-only-flows-forward-1187a8367d74';

    console.log("req.body.inpUrl : ", req.body.inpUrl)

    request( req.body.inpUrl, function (error, response, html) {
      if (!error && response.statusCode == 200) {
        
        let $ = cheerio.load(html);
        let contents = $('script').contents()[7].data;

        // console.log( contents );

        // [replace] JSON 파싱에서 오류가 발생하여 미리 변경 처리 한다.(유니코드 파싱오류) \x3c < 
        contents = contents.replace(/\\x3c/g, '<');

        let txtStart = 'window["obvInit"]({';
        let txtEnd = '"}}}}';

        let idxStart = contents.indexOf(txtStart);
        let idxEnd = contents.indexOf(txtEnd);

        let cutStart = idxStart + txtStart.length -1;
        let cutEnd = idxEnd - cutStart + txtEnd.length;

        let json = JSON.parse(contents.substr(cutStart, cutEnd));

        //- let title = json.value.title;
        //- let createdAt = json.value.createdAt;
        //- let subTitle = json.value.content.subtitle;
        //-
        //- let p1 = json.value.content.bodyModel.paragraphs[0];    // type, text, markup
        //- type : 1 => 일반글
        //- type : 3 => 제목
        //- type : 7 => 인용
        //- type : 8 => 프로그램 소스
        //- type : 9 => 목록
        //- type : 4 => 그림 주석
        //- type : 11 => 소스 주석
        //- type : 13 => 강조글

        //- markups
        //- type : 1 => 강조
        //- type : 2 => 이탤릭

        let items = [];
        let item = {};
        let idx = 1;
        for(let para of json.value.content.bodyModel.paragraphs){
            if( para.type==3 || para.type==13 ){
                if(item.title!=undefined){
                    items.push(item);
                }
                item = {};
                item.title = para.text;
                item.idx = idx;
                item.type = para.type;
                item.paragraphs = [];
                idx++;
            }
            if( para.type == 1 || para.type == 7 || para.type == 8 || para.type == 9){
                item.paragraphs.push({text:para.text, type:para.type});
            }
        }
        items.push(item);

        for(let s of json.value.content.bodyModel.paragraphs){
            console.log(s);
            console.log();
            console.log();
        }
        console.log();

        res.render('index', { items : items, isLoad : true });
      }
    });
});



module.exports = router;

[index.pug]

extends layout

block navtop

  a.navbar-brand(href="#") Medium 단어장 v0.1
  //- div.collapse.navbar-collapse
  div
    ul.navbar-nav.mr-auto
      li.nav-item.active
        a.nav-link(href='#') HOME
          span.sr-only (current)
      li.nav-item
        a.nav-link.disabled(href='#') 학습일지
      li.nav-item
        a.nav-link.disabled(href='#', data-content='개발예정', data-toggle="popover") 단어목록
    form.form-inline(method='post')
      div.input-group
        div.input-group-prepend
          span.input-group-text @url
          input.form-control(id="inpUrl", type='text', name='inpUrl' placeholder="medium url")
        button.btn.btn-outline-success.my-2.my-sm-0(type='submit') 조회

block container
  
  //- medium 정보를 읽어들였는지 여부
  if isLoad
    div
      each item in items
        if item.type == 3
          h3(id=`list-item-${item.idx}`)= item.title
        else
          h5(id=`list-item-${item.idx}`)= item.title        

        each para in item.paragraphs
          if para.type == 7
            i "#{para.text}"
            br
            br
          else if para.type == 8
            pre= para.text
          else if para.type == 9
            li= para.text
          else
            p= para.text        


    br
    button.btn.btn-default.btn-sm(onclick="javascript:$(window).scrollTop(0);")
      i.fas.fa-angle-double-up
      p TOP

  //- medium 정보를 읽어들였는지 여부
  else
    div @url에 medium 주소 정보를 입력 후 [조회] 버튼을 눌러 주시기 바랍니다.

//- 추가 : 자바스크립트 정의
block append javascript

  script(src='/javascripts/index.js')

맺음말

일단 대충 쓸만하게 만드는 것이 목표라 UI나 기능 등이 많이 부족하네요

궁금하시거나 개선사항은 댓글로 피드백 부탁 드립니다.

행복한 하루 보내세요 ~

다음 시간에는 ...

medium 사이트에서 추출 된 컨텐츠 내에서 단어 추출을 연동해 보겠습니다. (아래 참조)

본문 | 추출단어 목록

이전 글 목록

프로그래밍을 통한 단어장 만들기 #1 - 시작

Coin Marketplace

STEEM 0.17
TRX 0.15
JST 0.028
BTC 62014.91
ETH 2410.27
USDT 1.00
SBD 2.50