최근 에이블러들과 공모전을 준비하면서, 무슬림들이 먹는 할랄 음식 서비스 기획을 진행하고 있다. 이번 포스팅에서는 플레이 스토어에 등록된 할랄 관련 어플의 리뷰를 가져와서, 부정적인 리뷰를 요약해보았다. 코드 진행에 앞서, 분석이 필요한 이유를 먼저 정리했다.
VOC(Voice Of Customer)
VOC(Voice Of Customer)는 고객의 목소리를 의미한다. 이는 고객이 직접적으로 혹은 간접적으로 제공하는 피드백, 의견, 제안, 불만 등을 포함하는 포괄적인 개념이다. 이는 기업이 고객의 니즈의 기대를 이해하고, 제품과 서비스를 개선하는 데 중요한 역할을 한다. 특히, VOC는 고객 경험을 최적화하고 고객 만족도를 높이기 위한 전략적 도구로 활용된다.
외국 할랄 어플 리뷰 분석의 필요성
KOSIS 외래방문객 통계에 따르면, 코로나19를 제외하고 2016년부터 2019년까지 한국을 방문하는 무슬림 관광객은 계속 증가하고 있다. 또한, 한국관광공사에서 발표한 2022년 외래관광객 조사에 따르면 관광객들이 한국을 찾는 요인 1순위가 먹거리 문화이다. 이 상황에서, 음식을 마음대로 먹을 수 없는 무슬림 관광객들이 편리하게 이용할 수 있는 할랄 푸드맵 서비스를 제공하려고 한다. 그래서, 이미 시장에 나와 있는 외국 할랄 어플의 리뷰를 분석해서 고객의 요구사항과 기대치를 파악해보았다.
Python Code
먼저, google_play_scraper를 설치해야한다. 자세한 사항은 아래의 문서를 참고하면 된다.
google-play-scraper
Google-Play-Scraper provides APIs to easily crawl the Google Play Store for Python without any external dependencies!
pypi.org
import pandas as pd
import numpy as np
import json
import time
from tqdm import tqdm
from pprint import pprint
import urllib.parse
from transformers import pipeline
from google_play_scraper import app, Sort, reviews_all, reviews
result, continuation_token = reviews(
'com.unated', # 어플 ID
lang='en', # defaults to 'en'
country='us', # defaults to 'us'
sort=Sort.NEWEST, # defaults to Sort.NEWEST
count=3, # defaults to 100
filter_score_with=5 # defaults to None(means all score)
)
# If you pass `continuation_token` as an argument to the reviews function at this point,
# it will crawl the items after 3 review items.
result, _ = reviews(
'com.unated',
continuation_token=continuation_token # defaults to None(load from the beginning)
)

플레이 스토어의 어플은 모두 고유한 ID를 가지고 있다. input 파라미터로 어플 ID를 넣고, 국가와 언어, 정렬, 개수, 점수를 지정하면 결과와 토큰을 return 받는다. 단, 이 방법은 모든 리뷰를 가져오지는 못하고, 정해진 개수만큼을 가져온다.
from google_play_scraper import reviews_all
result = reviews_all(
'com.serunai.verifyhalal',
sleep_milliseconds=100, # sleep time between requests
lang='en',
country='us'
)
for review in result:
print(review)

모든 리뷰를 가져오고 싶으면, review_all 메서드를 사용하면된다. ID를 입력하고, 동일한 조건을 제시하면 해당 어플의 모든 리뷰가 출력된다. sleep_milliseconds는 요청 사이에 약간의 틈을 줘서, 오류를 방지하는 역할을 한다.
from google_play_scraper import search, reviews_all
import pandas as pd
def get_app_ids(query, lang='en', country='us'):
try:
# Perform search on Google Play Store
results = search(
query,
lang=lang,
country=country
)
# Extract app IDs from the results
app_ids = [app['appId'] for app in results]
return app_ids
except Exception as e:
print(f"An error occurred: {e}")
return []
def crawl_review(app_id, lang='en', country='us'):
try:
result = reviews_all(
app_id,
sleep_milliseconds=100, # sleep time between requests
lang=lang,
country=country
)
return pd.DataFrame(result)
except Exception as e:
print(f"An error occurred while fetching reviews for {app_id}: {e}")
return pd.DataFrame()
def get_all_reviews(query, lang='en', country='us'):
app_ids = get_app_ids(query, lang=lang, country=country)
all_reviews = pd.DataFrame()
for app_id in app_ids:
reviews_df = crawl_review(app_id, lang=lang, country=country)
all_reviews = pd.concat([all_reviews, reviews_df], ignore_index=True)
return all_reviews
# Example usage
query = 'halal'
all_reviews_df = get_all_reviews(query)
print(all_reviews_df)

하나의 어플이 아니라, 할랄과 관련된 모든 어플의 리뷰를 확인해보고 싶다는 생각이 들었다. 따라서, 특정 키워드를 입력하면 키워드 관련 어플 ID를 return 하는 함수를 추가적으로 작성했다. 그 결과, 2.387개의 다국어 리뷰를 확인할 수 있었다.
감성 분석
기존의 서비스와 독창성을 주기위해, 사용자들의 부정적 감성을 분석했다. huggingface의 다국어 Bert 모델을 활용했으며, 코드는 아래와 같다.
import pandas as pd
from transformers import pipeline
all_reviews_df = pd.read_csv('all_reviews_df.csv')
# Load pre-trained multilingual sentiment analysis model
sentiment_analysis = pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment")
def sentiment_analysis_for_dataframe(df):
sentiments = []
for content in df['content']:
try:
sentiment = sentiment_analysis(content)[0]
sentiments.append(sentiment)
except Exception as e:
print(f"An error occurred: {e}")
sentiments.append({"label": "neutral", "score": 0.0})
df['sentiment'] = [s['label'] for s in sentiments]
df['sentiment_score'] = [s['score'] for s in sentiments]
return df
# Example usage
if __name__ == "__main__":
# Perform sentiment analysis
analyzed_df = sentiment_analysis_for_dataframe(all_reviews_df)
| Level | Count |
| 5 stars | 985 |
| 4 stars | 368 |
| 3 stars | 201 |
| 2 stars | 169 |
| 1 star | 664 |
| neutral | 1 |
결과는 별점으로 return되며, 6종의 감성으로 분류된다. 또한, 해당 감성 분류의 신뢰도(confidence score)도 존재한다. 먼저, 다국적 언어를 처리하기 위해 영어로 번역을 진행했다. 이후, 부정적 감성을 이해하기 위해 별점 2점 이하인 데이터 중 신뢰도가 0.5 이상인 데이터를 추출했다.
import pandas as pd
from googletrans import Translator
from nltk.corpus import stopwords
import nltk
# Download stopwords
nltk.download('stopwords')
def translate_to_english(texts):
translator = Translator()
translated_texts = []
for text in texts:
try:
translated_text = translator.translate(text, dest='en').text
translated_texts.append(translated_text)
except Exception as e:
print(f"Translation error: {e}")
translated_texts.append(text) # In case of error, use original text
return translated_texts
def extract_negative_reviews(df):
# Filter negative reviews (1 star and 2 stars)
negative_reviews = df[(df['sentiment'].isin(['1 star', '2 stars']))&(analyzed_df['sentiment_score']>=0.5)]
return negative_reviews
# Example usage
if __name__ == "__main__":
# Extract negative reviews
negative_reviews_df = extract_negative_reviews(analyzed_df)
negative_reviews = negative_reviews_df['content'].tolist()
# Translate reviews to English
translated_reviews = translate_to_english(negative_reviews)

그 결과, 506개의 문장을 확인했다. 전체 결과가 너무 많아서 내용을 파악할 수가 없기 때문에, 요약을 진행했다. 또, 이모지, 특수문자도 일부 존재함을 확인했다. 불필요한 문자를 제거하고, facebook에서 발표한 bart-large-cnn 모델로 요약을 진행했다.
import re
from bs4 import BeautifulSoup
import emoji
from transformers import pipeline
# 리뷰 정리 함수
def clean_reviews_list(reviews):
return [clean_text(review) for review in reviews]
# 리뷰 분할 함수
def split_reviews(reviews, chunk_size):
for i in range(0, len(reviews), chunk_size):
yield reviews[i:i + chunk_size]
# 요약 함수
def summarize_reviews(reviews, summarizer, chunk_size=50):
summarized_sections = []
for chunk in split_reviews(reviews, chunk_size):
combined_text = " ".join(chunk)
summary = summarizer(combined_text, max_length=130, min_length=30, do_sample=False)[0]['summary_text']
summarized_sections.append(summary)
return summarized_sections
# 예제 사용법
if __name__ == "__main__":
# 텍스트 정리
cleaned_reviews = clean_reviews_list(reviews_list)
# Summarization pipeline 초기화
summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
# 리뷰 요약
summarized_sections = summarize_reviews(cleaned_reviews, summarizer, chunk_size=50)
final_review = summarize_reviews(summarized_sections, summarizer, chunk_size = 3)
전체 리뷰를 한 번에 요약하면 정보 손실이 너무 크다고 판단했다. 따라서, 임의의 chunk_size를 정해서 리뷰를 분할한 후, 요약된 결과를 다시 한 번 더 요약했다. 결과는 아래와 같다.
리뷰 주요 내용 및 공통된 문제점
1. 앱 기능 문제
- 많은 사용자들이 스캔 기능이 제대로 작동하지 않는다고 언급
- 제품의 바코드를 스캔할 때 제품이 등록되어 있지 않거나, '할랄'로 인식되지 않는 경우가 많음
- 재료를 스캔할 때 '하람'으로 잘못 인식되는 경우가 있음
- 앱이 자주 충돌하며, 카메라 기능이 멈추거나 작동하지 않는 문제가 발생하는 경우가 있음
2. 사용자 경험 문제
- 광고 동의 버튼이 작동하지 않으며, 해당 화면에서 진행되지 않는 문제가 있음
- UI가 매우 느림
- 광고 없는 버전을 구매했음에도 불구하고, 다시 연간 구독을 요구
3. 정확성 문제
- 일부 제품이 '할랄'로 표시되지만, 실제로는 돼지고기를 포함하고 있는 등 정확성에 문제가 있다는 불만
- 각 나라별로 다른 기준을 반영하지 못하고 있다는 지적이 있음 (예: 말레이시아와 브루나이의 기준 차이).
4. 로그인 및 계정 문제
- 구글 계정으로만 로그인 가능하게 강제하는 점에 대한 불만
- 비밀번호 재설정 이메일이 오지 않는 문제, 게스트로 주문할 수 없는 문제 등 계정 관리 문제도 언급
5. 지역화 및 데이터베이스 문제
- 할랄 음식점 검색 기능이 제대로 작동하지 않아, 실제로 근처에 있는 음식점을 찾지 못하고 수백 마일 떨어진 곳의 결과를 보여줌
- 제품 데이터베이스가 부족하여 많은 제품이 등록되지 않음
6. 기타 기술적 문제
- OTP(일회용 비밀번호) 수신 문제 등 기술적 결함
- 앱의 아이콘이 너무 작음
- 이와 같은 리뷰들은 주로 앱의 기능적 결함, 사용자 경험의 불편함, 데이터의 정확성 문제, 그리고 계정 및 로그인 문제 등을 공통적으로 지적
[집계]
- 앱 기능 문제: 5건
- 사용자 경험 문제: 2건
- 정확성 문제: 2건
- 로그인 및 계정 문제: 2건
- 지역화 및 데이터베이스 문제: 2건
- 기타 기술적 문제: 1건
'Aivle > Project' 카테고리의 다른 글
| [빅프로젝트] Nginx 웹 배포 (0) | 2024.07.27 |
|---|---|
| [빅프로젝트] 네이버 뉴스 웹크롤링 (2) | 2024.07.11 |
| [에이블스쿨] GCP, Naver Cloud, 형태소 분석기 바른 (1) | 2024.04.20 |
| [에이블스쿨] 잡코리아 웹크롤링 (2) | 2024.03.21 |
| [에이블스쿨] RFM, Retention (0) | 2024.03.12 |