안경잡이개발자

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
반응형