본문 바로가기
Framework & Library/Spring Framework

[Spring Framework] : 웹 크롤링 후 자연어 처리

by 오주현 2021. 11. 20.
반응형
네이버 뉴스 수집 서비스 구현

네이버 뉴스 기사를 크롤링해 데이터를 수집하고 수집된 기사 내용을 통해 자연어 처리를 합니다.

 

크롤링 할 기사를 선택하고 개발자 모드로 html 소스를 분석해 봅니다.

 

네이버 기사 본문 시작점입니다.


package poly.service;

public interface INewsCollectService {

	//네이버 뉴스 기사 크롤링으로 가져오기
	String doNaverNewsContents(String url) throws Exception;
}

INewsCollectService


package poly.service.impl;

import org.apache.log4j.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;

import poly.service.INewsCollectService;

@Service("NewsCollectService")
public class NewsCollectService implements INewsCollectService {

	private Logger log = Logger.getLogger(this.getClass());
			
	@Override
	public String doNaverNewsContents(String url) throws Exception {

		//JSOUP 라이브러리를 통해 사이트에 접속되면 그 사이트의 전체 HTML 소스를 저장할 변수
		Document doc = null;
		
		//사이트 접속(http 프로토콜만 가능합니다, https 프로토콜은 보안상 안 됩니다.)
		doc = Jsoup.connect(url).get();
		
		//네이버 뉴스 본문 내용에 대한 div 소스를 가져옵니다.
		// <div id="articleBodyContents" class="_article_body_contents">
		Elements newsContent = doc.select("div._article_body_contents");
		
		//태그 내 텍스트 문구만 가져옵니다.
		String res = newsContent.text();
		
		log.info(res);
		
		doc = null;
		
		return res;
	}


}

NewsCollectService


package poly.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;

import kr.co.shineware.nlp.komoran.constant.DEFAULT_MODEL;
import kr.co.shineware.nlp.komoran.core.Komoran;
import kr.co.shineware.nlp.komoran.model.KomoranResult;
import poly.service.INewsCollectService;
import poly.service.IWordAnalysisService;
import poly.util.CmmUtil;

@Service("WordAnalysisService")
public class WordAnalysisService implements IWordAnalysisService {

	private Logger log = Logger.getLogger(this.getClass());
	
	@Resource(name = "NewsCollectService")
	private INewsCollectService newsCollectService; //뉴스 기사를 수집하기 위한 객체입니다.
	
	//자연어 처리 - 형태소 분석기인 Komoran를 메모리에 올리기 위해 WordAnalysisService 클래스 내 전역 변수로 설정합니다.
	Komoran nlp = null;
	
	//생성자 사용함 - 톰켓에서 부팅할 때 @Service를 모두 메모리에 올립니다.
	//톰켓이 메모리에 올릴 때, 생성자에 선언한 Komoran도 같이 메모리에 올라가도록 생성자에 코딩합니다.
	//생성자에서 Komoran을 메모리에 올리면, 매번 메모리에 올려서 호출하는 것이 아니라, 
	// 메모리에 올리간 객체만 불러와서 사용할 수 있기 때문에 처리 속도가 빠릅니다.
	public WordAnalysisService() {
		
		log.info(this.getClass().getName() + ".WordAnalysisService creator Start !");
		
		//NLP 분석 객체 메모리 로딩합니다.
		this.nlp = new Komoran(DEFAULT_MODEL.LIGHT); // 학습데이터 경량화 버전( 웹 서비스에 적합합니다. )
		//this.nlp = new Komoran(DEFAULT_MODEL.FULL); // 학습데이터 전체 버전(일괄처리 : 배치 서비스에 적합합니다.)
	
		log.info("난 톰켓이 부팅되면서 스프링 프렝미워크가 자동 실행되었고, 스프링 실행될 때 nlp 변수에 Komoran 객체를 생성하여 저장하였다.");
		
		log.info(this.getClass().getName() + ".WordAnalysisService creator End !");
	
	
	}
	
	@Override
	public List<String> doWordNouns(String text) throws Exception {

		log.info(this.getClass().getName() + ".doWordAnalysis Start !");
		
		log.info("분석할 문장 : " + text);
		
		//분석할 문장에 대해 정제(쓸데없는 특수문자 제거)
		String replace_text = text.replace("[^가-힣a-zA-Z0-9", " ");
		
		log.info("한국어, 영어, 숫자 제외 단어 모두 한 칸으로 변환시킨 문장 : " + replace_text);
		
		//분석할 문장의 앞, 뒤에 존재할 수 있는 필요없는 공백 제거
		String trim_text = replace_text.trim();
		
		log.info("분석할 문장 앞, 뒤에 존재할 수 있는 필요 없는 공백 제거 : " + trim_text);
		
		//형태소 분석 시작
		KomoranResult analyzeResultList = this.nlp.analyze(trim_text);
		
		//형태소 분석 결과 중 명삼나 가져오기
		List<String> rList = analyzeResultList.getNouns();
		
		if (rList == null) {
			rList = new ArrayList<String>();
		}
		
		//분석 결과 확인을 위한 로그 찍기
		Iterator<String> it = rList.iterator();
		
		while (it.hasNext()) {
			//추출된 명서
			String word = CmmUtil.nvl(it.next());
			
			log.info("word : " + word);
		}
		
		
		log.info(this.getClass().getName() + ".doWordAnalysis End !");
		
		return rList;
	}

	@Override
	public Map<String, Integer> doWordCount(List<String> pList) throws Exception {

		log.info(this.getClass().getName() + ".doWordCount Start !");
		
		if (pList ==null) {
			pList = new ArrayList<String>();
		}
		
		//단어 빈도수(사과, 3) 결과를 저장하기 위해 Map객체 생성합니다.
		Map<String, Integer> rMap = new HashMap<>();
		
		//List에 존재하는 중복되는 단어들의 중복제거를 위해 set 데이터타입에 데이터를 저장합니다.
		//rSet 변수는 중복된 데이터가 저장되지 않기 떄문에 중복되지 않은 단어만 저장하고 나머지는 자동 삭제합니다.
		Set<String> rSet = new HashSet<String>(pList);
		
		//중복이 제거된 단어 모음에 빈도수를 구하기 위해 반복문을 사용합니다.
		Iterator<String> it = rSet.iterator();
		
		while(it.hasNext()) {
			//중복 제거된 단어
			String word = CmmUtil.nvl(it.next());
			
			//단어가 중복 저장되어 있는 pList로부터 단어의 빈도수 가져오기
			int frequency = Collections.frequency(pList, word);
			
			log.info("word :" + word);
			log.info("frequency : " + frequency);
			
			rMap.put(word, frequency);
		}
		
		log.info(this.getClass().getName() + ".doWordCount End !");
		
		return rMap;
	}

	@Override
	public Map<String, Integer> doWordAnalysis(String text) throws Exception {

		//네이버 뉴스 내용을 가져옵니다.
		String newContext = newsCollectService.doNaverNewsContents(
				"https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=105&oid=366&aid=0000774467"
				);
		
	//문장의 명사를 추출하기 위한 형태소 분석 실행
		List<String> rList = this.doWordNouns(newContext);
		
		if(rList == null) {
			rList = new ArrayList<String>();
		}
		
		//추출된 명사 모음(리스트)의 명사 단어별 빈도수 계산
		Map<String, Integer> rMap = this.doWordCount(rList);
		
		if(rMap == null) {
			rMap = new HashMap<String, Integer>();
		}
		
		return rMap;
	}

}

WordAnalysisService

 

이전 글에서 수정과 추가를 했습니다.

어노테이션 리소스 부분을 추가해주고 

 

네이버 뉴스 내용을 가져오는 부분을 추가하고

 

형태소 분석 하는 부분을 수정해 줬습니다.

 


이렇게 정상적으로 네이버 기사를 웹 크롤링 해와서 형태소 분석하는 자연어 처리를 한 내용입니다.

 

정상적으로 완료했습니다.

반응형

댓글