MBTI 검색엔진 데이터를 API 형태로 전달합니다.
API 목록
- 전체 문서에서 각 MBTI 타입별 상위 100개 키워드 출력 - 문서 수 기준
- MBTI 유형 중 E 또는 I 유형에 따라 상위 100개 키워드 출력
- MBTI 유형 중 E 또는 I 유형에 따라 검색어를 입력하여 검색된 상위 100개 키워드 출력
구현한 API는 다음과 같습니다.
전체 문서에서 각 MBTI 타입별 상위 100개 키워드를 출력
@app.get('/top/keywords/{mbti_type}')
def get_top_keywords(mbti_type: str, q:Optional[str]=None):
es_query = {
"size": 0,
"query": {"match": {"keyword": mbti_type}},
"aggs": {
"term_cnt": {
"terms": {
"field": "contents.nori_noun",
"size": 1000
}
}
}
}
res = es.search(index="mbti_term", body=es_query)
bkt_list = res.body["aggregations"]["term_cnt"]["buckets"]
refine_bkt_list = [b for b in bkt_list if b['key'] not in stopwords]
refine_bkt_list = [r for r in refine_bkt_list if not r['key'].isdigit()]
return {"data": refine_bkt_list}
es_query를 보면 “aggs”에서 합계를 내고 있는 필드는 contents.nori_noun 입니다. 이전 ES 포스트에서 말했듯이, nori_noun 은 명사(Mecab 형태소 분석기 분류상 명사)만 들어있습니다. 보통 의미있는 단어는 명사인 경우가 많아 명사만 특정 필드에 저장했습니다. 그리고 여기에 있는 terms 를 카운트 했습니다.
MBTI 유형 중 E 또는 I 유형에 따라 상위 100개 키워드 출력
@app.get('/top/keywords/regex/{mbti_type}')
def get_top_keywords_regex(mbti_type: str, q: Optional[str]=None):
if mbti_type not in ["I", "E"]:
raise
regex_q = f"{mbti_type}.*"
es_query = {
"size": 0,
"query": {"regexp": {"keyword": regex_q}},
"aggs": {
"term_cnt": {
"terms": {
"field": "contents.nori_noun",
"size": 1000
}
}
}
}
res = es.search(index="mbti_term", body=es_query)
bkt_list = res.body["aggregations"]["term_cnt"]["buckets"]
refine_bkt_list = [b for b in bkt_list if b['key'] not in stopwords]
refine_bkt_list = [r for r in refine_bkt_list if not r['key'].isdigit()]
return {"data": refine_bkt_list}
여기서는 ES 내에 regex를 사용했습니다(regexp). 제가 분석하고 싶은것은 E(외향형) 또는 I(내향형)에 따라 어떤 키워드를 더 많이 언급할까 또는 어떤 비중으로 특정 단어들을 언급할까 궁금했기 때문입니다.
MBTI 유형 중 E 또는 I 유형에 따라 검색어를 입력하여 검색된 상위 100개 키워드 출력
이번에는 Regex 와 키워드 검색을 결합하여 봅시다.
@app.get('/top/keywords/search/{mbti_type}/{keyword}')
def get_search_keyword(mbti_type: str, keyword: str, q: Optional[str]=None):
if mbti_type not in ["I", "E"]:
raise
regex_q = f"{mbti_type}.*"
es_query = {
"size": 0,
"query": {
"bool": {
"must": [
{
"match": {
"contents": keyword
}
},
{
"regexp": {
"keyword": regex_q
}
}
]
}
},
"aggs": {
"term_cnt": {
"terms": {
"field": "contents.nori_noun",
"size": 1000
}
}
}
}
res = es.search(index="mbti_term", body=es_query)
bkt_list = res.body["aggregations"]["term_cnt"]["buckets"]
refine_bkt_list = [b for b in bkt_list if b['key'] not in stopwords]
refine_bkt_list = [r for r in refine_bkt_list if not r['key'].isdigit()]
return {"data": refine_bkt_list}
regex와 키워드 검색을 결합하는 것은 어렵지 않습니다. 간단하게 bool 쿼리를 사용하여 match와 regexp를 사용하여 검색하였습니다. 이 쿼리는 특정 키워드가 포함된 문서들이면서 E 또는 I 유형에 대해 언급한 문서들을 가져온 뒤 Term Vector를 분석하여 그 수를 세는 쿼리입니다.
위 코드들 중 공통적인 부분이 있습니다.
# 불용어 제거
refine_bkt_list = [b for b in bkt_list if b['key'] not in stopwords]
# 숫자 제거
refine_bkt_list = [r for r in refine_bkt_list if not r['key'].isdigit()]
ES에서 명사들만 필드에 넣다보니 Mecab 에서는 숫자도 명사로 인식하기 때문에 API에서는 최종적으로 숫자 형태의 문자는 제거하고 Response 합니다.
또한 불용어(이, 것 등) 역시 제거합니다. Mecab 에서는 수사, 단위명사, 대명사 등도 명사로 인식되어 포함되기 때문에 불용어 사전을 이용하여 최대한 제거합니다.
API가 완성되었으니 앱 또는 웹 어플리케이션으로 결과를 보여주는 화면까지 연결해보겠습니다.
'Elastic Search' 카테고리의 다른 글
[Elastic Search] MBTI 검색 프로젝트 - 2. Emoji 검색 및 Aggregation(3편) (0) | 2022.04.24 |
---|---|
[Elastic Search] MBTI 검색 프로젝트 - 2. Emoji 검색 및 Aggregation(2편) (0) | 2022.04.21 |
[Elastic Search] MBTI 검색 프로젝트 - 2. Emoji 검색 및 Aggregation (0) | 2022.04.13 |
[Elastic Search] MBTI 검색 프로젝트 - 1. 검색 Score 튜닝 (0) | 2022.04.12 |
[Elastic Search] 검색 구현하기(with Fast API) (0) | 2022.02.15 |