뉴스 크롤링을 넘어서 형태소 분석으로 [데이터 분석 with C#]

안녕하세요. 언제나 휴일, 언휴예요.

이전 강의에서 뉴스 크롤링 라이브러리 제작을 마쳤습니다.

이제 수집한 뉴스를 분석하는 코드를 작성할 시점이 왔어요.

이번에 문장으로 만들어진 콘텐츠를 분석할 때 가장 기초적인 형태소 분석을 해 볼 거예요.

자세한 내용은 동영상 강의를 참고하세요.

2020년 5월 4일 저녁 7시 30분 경에 “스포츠”로 검색하여 나오는 1100개의 기사 중에 가장 많이 나온 단어 BEST 50입니다. *참고로 각 기사의 앞 부분만 발췌하기 때문에 빈도 수가 크지 않습니다.*

  • 스포티비뉴스:33
  • KBO:33
  • 손진아:33
  • 첫:34
  • 예정:34
  • 최고:35
  • 활동:35
  • 트랙스:35
  • 교육기부:36
  • 모델:38
  • 72개월:38
  • 스포츠마케팅:39
  • 한도:40
  • 대표적인:40
  • 후원:42
  • 관계자:43
  • 개막전:45
  • 5일:46
  • 사진:47
  • SUV:47
  • 개막:49
  • 연계:50
  • 지원:51
  • 브랜드:51
  • 옥영화:51
  • 마케팅:52
  • 잠실야구장에서:54
  • 대비:55
  • 오전:60
  • 출시:60
  • KBO리그:63
  • 미국:64
  • 경기:66
  • 잠실:68
  • 선수:69
  • 렉스턴:69
  • 트윈스:72
  • 스포츠투데이:75
  • 시즌:77
  • 훈련:81
  • 코로나19:82
  • 두산:87
  • LG:88
  • 2020:93
  • 프로야구:117
  • 매경닷컴:127
  • MK스포츠:130
  • 기자:207
  • 4일:216
  • 스포츠:442
  • Morpheme.cs
using System;

namespace 형태소_분석기_만들기
{
    ///
    /// 형태소
    /// 
    public class Morpheme:IComparable
    {
        ///
        /// 형태소 단어 - 가져오기 및 설정하기
        /// 
        public string Name
        {
            get;
            set;
        }
        ///
        /// 빈도수 - 가져오기 및 설정하기
        /// 
        public int Count
        {
            get;
            set;
        }
        ///
        /// 형태소 생성자
        /// 
        ///단어
        ///빈도수
        public Morpheme(string name,int count)
        {
            Name = name;
            Count = count;
        }
        ///
        /// ToString 재정의
        /// 
        /// 단어
        public override string ToString()
        {
            return Name;
        }

        ///
        /// 비교 - 정렬을 위해 제공
        /// 
        ///비교 대상 - 형태소 형식 개체여야 함
        /// 비교 결과
        public int CompareTo(object obj)
        {
            Morpheme mo = obj as Morpheme;
            if(mo == null)
            {
                throw new ApplicationException("비교 대상 개체는 Morpheme 형식이어야 합니다.");
            }
            return Count.CompareTo(mo.Count);
        }
    }
}
  • MorphemeParser.cs
using System;
using System.Collections.Generic;

namespace 형태소_분석기_만들기
{
    ///
    /// 형태소 분석기 - 정적 클래스
    /// 
    public static class MorphemeParser
    {
        static string[] filters = {"~","!","#","$","%","^","&","*","(",")","-","_","=","+","|",@"\",
                "{","}","[","]",";",":","\"","'","<",">",",",".","/","?"," ","","quot","amp","apos","lt","gt"};
        static string[] pf_filters = { "는", "은", "을", "를", "가", "에", "게", "에게","의","들","있다.","없다","이다","입니다", "합니다" ,
            "합시다"};

        ///
        /// 형태소 분석 메서드
        /// 
        ///원본 문자열
        /// 형태소 컬렉션
        public static List Parse(string source)
        {
            List mlist = new List();
            string[] elems = source.Split(filters, StringSplitOptions.RemoveEmptyEntries);
            foreach(string elem in elems)
            {
                ParseElem(elem, mlist);
            }
            mlist.Sort();
            return mlist;
        }

        private static void ParseElem(string elem, List mlist)
        {
            if(elem == string.Empty)
            {
                return;
            }
            foreach(string pf_filter in pf_filters)
            {
                if(elem.EndsWith(pf_filter))
                {
                    MakeElem(pf_filter, elem, mlist);
                    return;
                }
            }
            RecordElem(elem, mlist);
        }

        private static void RecordElem(string elem, List mlist)
        {
            foreach(Morpheme mo in mlist)
            {
                if(mo.Name == elem)
                {
                    mo.Count++;
                    return;
                }
            }
            Morpheme mo2 = new Morpheme(elem, 1);
            mlist.Add(mo2);
        }

        private static void MakeElem(string pf_filter, string elem, List mlist)
        {
            int pos = elem.Length - pf_filter.Length;
            string sub = elem.Substring(0, pos);
            RecordElem(sub, mlist);
        }

        ///
        /// 형태소 통합 메서드
        /// 
        ///원본 형태소 컬렉션
        ///타겟 형태소 컬렉션
        /// 타겟 형태소 컬렉션
        public static List Merge(List src, List dest)
        {
            foreach(Morpheme mo in src)
            {
                RecordElem2(mo, dest);
            }
            dest.Sort();
            return dest;
        }

        private static void RecordElem2(Morpheme mo, List dest)
        {
            foreach(Morpheme mo2 in dest)
            {
                if(mo2.Name == mo.Name)
                {
                    mo2.Count += mo.Count;
                    return;
                }
            }
            dest.Add(mo);
        }
    }
}

  • Program.cs
using System;
using System.Collections.Generic;
using 네이버_뉴스_크롤링_라이브러리_제작;

namespace 형태소_분석기_만들기
{
    class Program
    {
        static void Main(string[] args)
        {
            string id = [네이버 개발자센터에서 발급받은 애플리케이션 ID];
            string secret = [네이버 개발자센터에서 발급받은 애플리케이션 Secret];
            NaverNews nn = new NaverNews(id, secret);
            int total = nn.Find("스포츠");
            Console.WriteLine(total);

            List morphemes = new List();
            List src_morphemes;
            List nc;
            for (int start = 1; (start < 1000) && (start < total); start += 100)
            {
                nc = nn.FindNews(start, 100);
                foreach (News news in nc)
                {
                    Console.WriteLine(news.Title);
                    Console.WriteLine("==");
                    Console.WriteLine(news.Description);
                    Console.WriteLine("==================================================");

                    src_morphemes = MorphemeParser.Parse(news.Description);
                    MorphemeParser.Merge(src_morphemes, morphemes);
                }
            }

            foreach(Morpheme morpheme in morphemes)
            {
                Console.WriteLine("{0}:{1}", morpheme, morpheme.Count);
            }
        }
    }
}

*이전 강의에서 작성한 뉴스 크롤링 라이브러리를 참조 추가하셔야 합니다.*