안경잡이개발자

728x90
반응형

  이번 시간에는 만들어진 워드 클라우드(Word Cloud) API를 웹 상에서 접근할 수 있는 방법에 대해서 알아보도록 하겠습니다. 웹 상에서 특정한 이미지 파일 등에 접근하기 위해서는 정적 폴더(Static Folder)를 설정해야 합니다.


▶ word_cloud.py


# 단어구름에 필요한 라이브러리를 불러옵니다.
from wordcloud import WordCloud
# 한국어 자연어 처리 라이브러리를 불러옵니다.
from konlpy.tag import Twitter
# 명사의 출현 빈도를 세는 라이브러리를 불러옵니다.
from collections import Counter
# 그래프 생성에 필요한 라이브러리를 불러옵니다.
import matplotlib.pyplot as plt
# Flask 웹 서버 구축에 필요한 라이브러리를 불러옵니다.
from flask import Flask, request, jsonify

# 플라스크 웹 서버 객체를 생성합니다.
app = Flask(__name__, static_folder='outputs')

# 폰트 경로 설정
font_path = 'NanumGothic.ttf'


def get_tags(text, max_count, min_length):
# 명사만 추출합니다.
t = Twitter()
nouns = t.nouns(text)
processed = [n for n in nouns if len(n) >= min_length]
# 모든 명사의 출현 빈도를 계산합니다.
count = Counter(processed)
result = {}
# 출현 빈도가 높은 max_count 개의 명사만을 추출합니다.
for n, c in count.most_common(max_count):
result[n] = c
# 추출된 단어가 하나도 없는 경우 '내용이 없습니다.'를 화면에 보여줍니다.
if len(result) == 0:
result["내용이 없습니다."] = 1
return result


def make_cloud_image(tags, file_name):
# 만들고자 하는 워드 클라우드의 기본 설정을 진행합니다.
word_cloud = WordCloud(
font_path=font_path,
width=800,
height=800,
background_color="white",
)
# 추출된 단어 빈도수 목록을 이용해 워드 클라우드 객체를 초기화 합니다.
word_cloud = word_cloud.generate_from_frequencies(tags)
# 워드 클라우드를 이미지로 그립니다.
fig = plt.figure(figsize=(10, 10))
plt.imshow(word_cloud)
plt.axis("off")
# 만들어진 이미지 객체를 파일 형태로 저장합니다.
fig.savefig("outputs/{0}.png".format(file_name))


def process_from_text(text, max_count, min_length, words, file_name):
# 최대 max_count 개의 단어 및 등장 횟수를 추출합니다.
tags = get_tags(text, max_count, min_length)
# 단어 가중치를 적용합니다.
for n, c in words.items():
if n in tags:
tags[n] = tags[n] * int(words[n])
# 명사의 출현 빈도 정보를 통해 워드 클라우드 이미지를 생성합니다.
make_cloud_image(tags, file_name)


@app.route("/process", methods=['GET', 'POST'])
def process():
content = request.json
words = {}
if content['words'] is not None:
for data in content['words'].values():
words[data['word']] = data['weight']
process_from_text(content['text'], content['maxCount'], content['minLength'], words, content['textID'])
result = {'result': True}
return jsonify(result)


@app.route('/outputs', methods=['GET', 'POST'])
def output():
text_id = request.args.get('textID')
return app.send_static_file(text_id + '.png')


if __name__ == '__main__':
app.run('0.0.0.0', port=5000)


▶ 이미지 생성


  이미지 생성에는 textID라는 파라미터를 추가적으로 넣어주세요.



▶ 이미지 접근


  이미지에 접근할 때는 다음과 같이 파라미터로 접근하면 됩니다.



▶ word_cloud.py


  이러한 이미지에 실제로 클라이언트에서 효과적으로 접근할 수 있도록 이미지가 존재하는지 확인하기 위한 validate() 함수를 추가적으로 만들어 주었습니다.


# 단어구름에 필요한 라이브러리를 불러옵니다.
from wordcloud import WordCloud
# 한국어 자연어 처리 라이브러리를 불러옵니다.
from konlpy.tag import Twitter
# 명사의 출현 빈도를 세는 라이브러리를 불러옵니다.
from collections import Counter
# 그래프 생성에 필요한 라이브러리를 불러옵니다.
import matplotlib.pyplot as plt
# Flask 웹 서버 구축에 필요한 라이브러리를 불러옵니다.
from flask import Flask, request, jsonify
# 테스트를 위하여 CORS를 처리합니다.
from flask_cors import CORS
# 파일에 접근하기 위한 라이브러리를 불러옵니다.
import os

# 플라스크 웹 서버 객체를 생성합니다.
app = Flask(__name__, static_folder='outputs')
CORS(app)

# 폰트 경로 설정
font_path = 'NanumGothic.ttf'


def get_tags(text, max_count, min_length):
# 명사만 추출합니다.
t = Twitter()
nouns = t.nouns(text)
processed = [n for n in nouns if len(n) >= min_length]
# 모든 명사의 출현 빈도를 계산합니다.
count = Counter(processed)
result = {}
# 출현 빈도가 높은 max_count 개의 명사만을 추출합니다.
for n, c in count.most_common(max_count):
result[n] = c
# 추출된 단어가 하나도 없는 경우 '내용이 없습니다.'를 화면에 보여줍니다.
if len(result) == 0:
result["내용이 없습니다."] = 1
return result


def make_cloud_image(tags, file_name):
# 만들고자 하는 워드 클라우드의 기본 설정을 진행합니다.
word_cloud = WordCloud(
font_path=font_path,
width=800,
height=800,
background_color="white",
)
# 추출된 단어 빈도수 목록을 이용해 워드 클라우드 객체를 초기화 합니다.
word_cloud = word_cloud.generate_from_frequencies(tags)
# 워드 클라우드를 이미지로 그립니다.
fig = plt.figure(figsize=(10, 10))
plt.imshow(word_cloud)
plt.axis("off")
# 만들어진 이미지 객체를 파일 형태로 저장합니다.
path = "outputs/{0}.png".format(file_name)
# 이미 파일이 존재하는 경우 덮어쓰기합니다.
if os.path.isfile(path):
os.remove(path)
fig.savefig(path)


def process_from_text(text, max_count, min_length, words, file_name):
# 최대 max_count 개의 단어 및 등장 횟수를 추출합니다.
tags = get_tags(text, max_count, min_length)
# 단어 가중치를 적용합니다.
for n, c in words.items():
if n in tags:
tags[n] = tags[n] * int(words[n])
# 명사의 출현 빈도 정보를 통해 워드 클라우드 이미지를 생성합니다.
make_cloud_image(tags, file_name)


@app.route("/process", methods=['GET', 'POST'])
def process():
content = request.json
words = {}
if content['words'] is not None:
for data in content['words'].values():
words[data['word']] = data['weight']
process_from_text(content['text'], content['maxCount'], content['minLength'], words, content['textID'])
result = {'result': True}
return jsonify(result)


@app.route('/outputs', methods=['GET', 'POST'])
def output():
text_id = request.args.get('textID')
return app.send_static_file(text_id + '.png')


@app.route('/validate', methods=['GET',' POST'])
def validate():
text_id = request.args.get('textID')
path = "outputs/{0}.png".format(text_id)
result = {}
# 해당 이미지 파일이 존재하는지 확인합니다.
if os.path.isfile(path):
result['result'] = True
else:
result['result'] = False
return jsonify(result)


if __name__ == '__main__':
app.run('0.0.0.0', port=5000, threaded=True) # 처리 속도 향상을 위해 쓰레드를 적용합니다.

▶ Detail.js


  이제 Detail.js 파일을 수정합니다. 기본적으로 React에서 이미지(Image) 태그의 정보가 수정될 때에는 브라우저 캐시(Cache)가 동작하지 않도록, 별도의 랜덤 파라미터를 추가적으로 붙여 줄 수 있습니다.


import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import '../index.css';
import { withStyles } from '@material-ui/core/styles';
import Fab from '@material-ui/core/Fab';
import UpdateIcon from '@material-ui/icons/Update';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';

const databaseURL = "https://react-example-55161.firebaseio.com/";
const apiURL = "http://localhost:5000";

const styles = theme => ({
fab: {
position: 'fixed',
bottom: '20px',
right: '20px'
},
});


class Detail extends React.Component {

constructor(props) {
super(props);
this.state = {
dialog: false,
textContent: '',
words: {},
imageUrl: null
}
}

componentDidMount() {
this._getImage();
this._getText();
this._getWords();
}

_getText() {
fetch(`${databaseURL}/texts/${this.props.match.params.textID}.json`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(text => this.setState({textContent: text['textContent']}));
}

_getWords() {
fetch(`${databaseURL}/words.json`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(words => this.setState({words: (words == null) ? {} : words}));
}

_getImage() {
fetch(`${apiURL}/validate?textID=${this.props.match.params.textID}`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(data => {
if(data['result'] == true) {
this.setState({imageUrl: apiURL + "/outputs?textID=" + this.props.match.params.textID})
} else {
this.setState({imageUrl: 'NONE'});
}
});
}

handleDialogToggle = () => this.setState({
dialog: !this.state.dialog
})

handleSubmit = () => {
this.setState({imageUrl: 'READY'});
const wordCloud = {
textID: this.props.match.params.textID,
text: this.state.textContent,
maxCount: 30,
minLength: 1,
words: this.state.words
}
this.handleDialogToggle();
if (!wordCloud.textID ||
!wordCloud.text ||
!wordCloud.maxCount ||
!wordCloud.minLength ||
!wordCloud.words) {
return;
}
this._post(wordCloud);
}
_post = (wordCloud) => {
return fetch(`${apiURL}/process`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(wordCloud)
}).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(data => {
this.setState({imageUrl: apiURL + "/outputs?textID=" + this.props.match.params.textID})
});
}

render() {
const { classes } = this.props;
return (
<div>
<Card>
<CardContent>
{
(this.state.imageUrl)?
((this.state.imageUrl == 'READY')?
'워드 클라우드 이미지를 불러오고 있습니다.':
((this.state.imageUrl == 'NONE')?
'해당 텍스트에 대한 워드 클라우드를 만들어 주세요.':
<img key={Math.random()} src={this.state.imageUrl + '&random=' + Math.random()} style={{width: '100%'}}/>)):
''
}
</CardContent>
</Card>
<Fab color="primary" className={classes.fab} onClick={this.handleDialogToggle}>
<UpdateIcon />
</Fab>
<Dialog open={this.state.dialog} onClose={this.handleDialogToggle}>
<DialogTitle>워드 클라우드 생성</DialogTitle>
<DialogActions>
<Button variant="contained" color="primary" onClick={this.handleSubmit}>
{(this.state.imageUrl == 'NONE')? '만들기' : '다시 만들기'}
</Button>
<Button variant="outlined" color="primary" onClick={this.handleDialogToggle}>닫기</Button>
</DialogActions>
</Dialog>
</div>
);
}
}

export default withStyles(styles)(Detail);


※ 실행 결과 ※


  먼저 하나의 텍스트를 추가해보도록 하겠습니다.




  이어서 만들어진 텍스트를 확인해보도록 하겠습니다.



  처음에는 워드 클라우드가 존재하지 않는 상황입니다.



  워드 클라우드를 생성한 이후에는 다음과 같이 정상적으로 출력됩니다.




  또한 만들어진 이후에 [다시 만들기] 버튼을 누르면 새롭게 워드 클라우드가 생성됩니다.


728x90
반응형

728x90
반응형

  이번 시간에는 플라스크(Flask) 웹 서버를 이용하여 워드 클라우드 API 를 구현하는 시간을 가져보도록 하겠습니다. 클라이언트(Client)에 해당하는 리액트 프론트 엔드에서 워드 클라우드 이미지를 요청하기 위해서는, 클라이언트의 요청(Request)을 받아서 워드 클라우드를 그려주는 웹 서버를 작업해야 합니다.


  추가적으로 필요한 라이브러리는 다음과 같습니다.


▶ WordCloud(워드 클라우드를 그리는 라이브러리), konlpy Twitter(한국어 자연어 처리 라이브러리), Counter(명사의 출현 빈도를 세는 라이브러리), Matplotlib(워드 클라우드 이미지를 실제로 그림 파일에 표현하는 목적의 라이브러리)


pip3 install wordcloud
pip3 install konlpy
pip3 install matplotlib
pip3 install flask
pip3 install flask_cors


  일단 프로젝트를 다음과 같이 구성합니다. 파이참(PyCharm)으로 열거나 혹은 단순한 폴더 형태로 구비하시면 됩니다. 폴더에는 outputs(워드 클라우드가 저장되는 폴더), NanumGothic.ttf(나눔고딕 글씨체), word_cloud.py(전체 소스코드)가 포함됩니다.


NanumGothic.ttf





▶ word_cloud.py

# 단어구름에 필요한 라이브러리를 불러옵니다.
from wordcloud import WordCloud
# 한국어 자연어 처리 라이브러리를 불러옵니다.
from konlpy.tag import Twitter
# 명사의 출현 빈도를 세는 라이브러리를 불러옵니다.
from collections import Counter
# 그래프 생성에 필요한 라이브러리를 불러옵니다.
import matplotlib.pyplot as plt
# Flask 웹 서버 구축에 필요한 라이브러리를 불러옵니다.
from flask import Flask, request, jsonify

# 플라스크 웹 서버 객체를 생성합니다.
app = Flask(__name__)

# 폰트 경로 설정
font_path = 'NanumGothic.ttf'


def get_tags(text, max_count, min_length):
# 명사만 추출합니다.
t = Twitter()
nouns = t.nouns(text)
processed = [n for n in nouns if len(n) >= min_length]
# 모든 명사의 출현 빈도를 계산합니다.
count = Counter(processed)
result = {}
# 출현 빈도가 높은 max_count 개의 명사만을 추출합니다.
for n, c in count.most_common(max_count):
result[n] = c
# 추출된 단어가 하나도 없는 경우 '내용이 없습니다.'를 화면에 보여줍니다.
if len(result) == 0:
result["내용이 없습니다."] = 1
return result


def make_cloud_image(tags, file_name):
# 만들고자 하는 워드 클라우드의 기본 설정을 진행합니다.
word_cloud = WordCloud(
font_path=font_path,
width=800,
height=800,
background_color="white",
)
# 추출된 단어 빈도수 목록을 이용해 워드 클라우드 객체를 초기화 합니다.
word_cloud = word_cloud.generate_from_frequencies(tags)
# 워드 클라우드를 이미지로 그립니다.
fig = plt.figure(figsize=(10, 10))
plt.imshow(word_cloud)
plt.axis("off")
# 만들어진 이미지 객체를 파일 형태로 저장합니다.
fig.savefig("outputs/{0}.png".format(file_name))


def process_from_text(text, max_count, min_length, words):
# 최대 max_count 개의 단어 및 등장 횟수를 추출합니다.
tags = get_tags(text, max_count, min_length)
# 단어 가중치를 적용합니다.
for n, c in words.items():
if n in tags:
tags[n] = tags[n] * int(words[n])
# 명사의 출현 빈도 정보를 통해 워드 클라우드 이미지를 생성합니다.
make_cloud_image(tags, "output")


@app.route("/process", methods=['GET', 'POST'])
def process():
content = request.json
words = {}
if content['words'] is not None:
for data in content['words'].values():
words[data['word']] = data['weight']
process_from_text(content['text'], content['maxCount'], content['minLength'], words)
result = {'result': True}
return jsonify(result)


if __name__ == '__main__':
app.run('0.0.0.0', port=5000)

  서버를 실행하면 자동으로 5000번 포트에 웹 서버 할당이 이루어집니다.



※ API 테스트 ※


  이제 만들어진 플라스크 웹 서버에 API 테스트를 진행해보도록 하겠습니다. 크롬(Chrome) 확장 프로그램인 Restlet을 이용하면 쉽게 Rest API를 테스트할 수 있습니다.


▶ 요청(Request) JSON 예제


{
    "text": "안녕하세요? 저는 한국교원대학교 나동빈입니다. 여러분들과 함께 다양한 공부를 진행하면서 스터디에 참여하고 싶어요. 한 번 공부를 할 때 제대로 공부를 하는 것이 목표입니다. 공부는 쉽지 않지만 열심히 하다 보면 재미를 느끼고 참여할 수 있을 것 같아요.",
    "maxCount": 15,
  "minLength": 2,
    "words":
{"1":{"weight":"7","word":"스터디"},"2":{"weight":"5","word":"참여"},"3":{"weight":"5","word":"분노"},"4":{"weight":"4","word":"치킨"}}
}


  이를 /process 경로로 전달하시면 됩니다.



  실행 결과 다음과 같이 성공 메시지가 출력됩니다.




  이후에 서버 폴더로 이동하여 output.png 파일을 확인해 보시면 다음과 같이 워드 클라우드 파일이 성공적으로 생성되어 있습니다.



※ 깃 허브에 업로드하기 ※


  이후에 다음과 같이 깃 허브에 리포지터리를 업로드 하시면 됩니다.



728x90
반응형