프로젝트 배경
글또에서 '4_게임해또'라는 채널을 운영한지 벌써 한 달이 넘었다.
그 동안 채널에서 104명이라는 많은 사람이 생겼고 아직도 사람들이 간간이 들어오고 있다.
채널에서는 주로 특정 게임을 같이할 사람을 찾거나, 본인이 좋아하는 게임을 추천하는 활동이 이루어진다.
위와 같이 사람들은 본인이 주로 플레이하는 게임을 간단히 소개 하는데,
메시지가 많아지다보니 사람들이 같은 게임을 하는 사람을 쉽게 파악하기 어려워 보여 우선 데이터를 수집해 정리하기로 결정했다.
간단한 작업을 통해, 사람들이 보낸 메시지를 기반으로 좋아하는 게임과 자주 플레이 하는 게임을 정리하고 간단하게 시각화할 수 있는 대시보드를 만들어보려고 한다. 오늘은 간단하게 데이터를 적재하는 작업까지만 진행할 예정이다.
데이터 수집
우선, Slack 메시지에서 데이터를 가져올 수 있는 방법은 다음의 방법들이 있다.
1. 데이터 내보내기
워크스페이스 데이터 내보내기
Slack을 사용하면 워크스페이스 또는 Enterprise Grid 조직에서 데이터를 내보낼 수 있습니다. 플랜에 따라 데이터 내보내기는 몇 가지 옵션이 있을 수 있BAD+C...
slack.com
간단하게 슬랙 내의 채널 메시지 등 다양한 데이터를 파일로 내보내기 할 수 있는 기능.
사실 데이터를 수집할 수 있는 가장 간단한 방법이지만, 워크스페이스 관리자가 아니기 때문에 사실상 나에게는 불가능한 방법이다😭
일반적으로, 주기적으로 데이터가 사라지는 무료 버전에서 과거 데이터를 보존하려는 경우 많이 사용한다.
어쨌든 해당 방법은 Pass!
2. API를 통한 데이터 가져오기
다음과 같이 APP을 생성해서 권한을 추가 후 데이터를 수집하는 방법도 있다.
사실 이 방법을 가장 사용하고 싶었다.
실시간이나 배치 단위로 채널에 새로운 멤버가 메시지를 남기면 해당 메시지로부터 데이터를 추출해서 정제 및 적재하고 싶었고,
추후 멤버들의 데이터를 분석하는데도 도움이 될 것이라고 판단했기 때문이다.
문제는 이 경우 역시, 워크스페이스 관리자의 승인이 필수였기 때문에 사용할 수 없었다...😭
추후 운영진분들과 대화를 통해 자동화에 사용할 수도 있기 때문에 간단하게나마 동작할 만한 코드를 아래와 같이 작성해봤다.
import os
import requests
from dotenv import load_dotenv
# .env 파일 로드
load_dotenv()
# 사용자 토큰 (User Token)
TOKEN = os.getenv("SLACK_BOT_TOKEN") # .env에서 가져오기
CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID") # 채널 ID
# API URL
url = "https://slack.com/api/conversations.history"
headers = {"Authorization": f"Bearer {TOKEN}"}
params = {"channel": CHANNEL_ID, "limit": 100}
# API 호출
response = requests.get(url, headers=headers, params=params)
data = response.json()
# 결과 확인
if data["ok"]:
messages = data["messages"]
# 사용자별 메시지 저장을 위한 딕셔너리 생성
user_messages = {}
# 메시지 데이터를 사용자별로 그룹화
for message in messages:
user = message.get("user", "unknown_user") # 메시지 작성자 ID (없으면 unknown_user)
text = message.get("text", "") # 메시지 내용
if user not in user_messages:
user_messages[user] = []
user_messages[user].append(text)
# 사용자별 메시지를 파일로 저장
output_dir = "user_messages" # 저장 폴더 이름
os.makedirs(output_dir, exist_ok=True) # 폴더 생성 (존재하지 않으면 생성)
for user, msgs in user_messages.items():
# 사용자별 파일 생성
filename = os.path.join(output_dir, f"{user}.txt")
with open(filename, "w", encoding="utf-8") as file:
file.write("\n".join(msgs)) # 메시지를 줄 단위로 저장
print(f"사용자별 메시지가 '{output_dir}' 폴더에 저장되었습니다.")
else:
print(f"Error: {data['error']}")
사용자별 메시지를 각각의 파일로 저장하여 추후에 해당 사용자가 주로 플레이하는 데이터도 분석하기에 용이하다.
3. 수동으로 수집하기
말 그대로 데이터를 직접 수집으로 수집하는 방법.
앞서 말했던 것처럼 나는 일개 '채널 매니저'이기 때문에 1번과 2번의 방법은 사용할 수 없었다.
약 100명 그 중에서도 채널에 선호 게임에 대한 '메시지를 남겨 준 사람은 더 적으니 직접 수집하지 뭐..' 라는 마음으로 우선은 수동으로 데이터를 끌어 모았다.
뭐 어떻게 해? 자동으로 할 수 없으면 우선 수동으로라도 돌아가게 해야지..(라고 데이터 엔지니어 같지 않은 말을 해댄다.)
데이터 전처리
대충 대략적으로 데이터를 다 수집하고 나면, 각 메시지로부터 좋아하는 게임명을 추출해내야 한다.
문제는 여기서 각자 게임을 '리그 오브 레전드', '롤' 'LOL', '칼바람' 등 같은 게임을 다양한 방식으로 부르기 때문에 정확한 게임 이름만 추출해낼 방법이 없을까 고민하다, 날먹을 위해 GPT를 선택했다. 역시 돈이 최고
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage
# LangChain LLM 설정
llm = ChatOpenAI(model="gpt-4o-mini", openai_api_key=openai_api_key)
# 메시지 분석 함수
def analyze_message(message):
prompt_template = """
주어진 메시지에서 아래 항목들을 추출하여 분류하세요:
- 이름: 사용자의 이름
- 게임 이름: 주로 플레이하는 게임, 좋아하는 게임, 추천한 게임을 한국어 표기명으로 작성해주세요.(ex. 리그 오브 레전드)
- 장르: 해당 게임의 장르 (장르가 작성돼있지 않으면, 게임 이름을 검색해서 해당 게임의 장르를 정리해주세요)
- 시간대: 주로 게임을 플레이하는 시간대 (아침, 오후, 저녁, 밤) (정보가 없으면 비워두세요)
- 요일: 주로 플레이하는 요일 (주중, 주말 등) (정보가 없으면 비워두세요)
- 특이 사항: 추가적인 정보 (특정 선호도, 장르나 스타일 관련 언급 등)
"""
response = llm([HumanMessage(content=prompt_template.format(message=message))])
return response.content
처음에는 값싼 gpt-3.5-turbo 모델을 사용했었다. (1000 tokens 당 $0.0015)
이전에 회사에서 LLM을 사용한 프로젝트를 했었을 때도 느꼈지만, GPT3.5와 GPT4o의 가격이 약 10배 가까이 차이나는데 그만큼 성능 차이도 매우 심하다. 3.5는 프롬프트를 아무리 뜯어고쳐도 이상한 소리를 할 때가 많고, 결국 이번에도 자꾸 헛소리를 해대길래 4o를 사용했다.
약 50명의 데이터를 처리하는데 $0.029904 정도의 돈이 들었다. 음 이 정도면 굉장히 싸게 먹혔다고 생각한다🧐
실제로 데이터를 확인해봤을 때도 정확도도 비교적 정확한 편이었기 때문에!
사실, 게임 이름을 공식 명칭으로 할까? 한국어로 할까, 영어로 할까? 고민이 많았다..(데이터 일관성은 있어야지..)
근데 약 90%가 넘는 메시지에서 사람들이 게임명을 지칭할 때 대부분 한국어 표기명을 사용하는 경우가 많았다.
예를 들어, 오버워치를 OverWatch라고 적는 것보다는 오버워치, Battle Ground를 배틀그라운드로 적는 것처럼..!
이게 좀 더 사람들에게 직관적인 느낌일 거라고 판단해 우선 게임명을 위의 프롬프트처럼 한국어로 추출할 수 있도록 했다.
데이터 저장
def process_and_save(messages, output_file):
results = []
total_cost = 0
for message in tqdm(messages):
result_text, token_usage = analyze_message(message.strip()) # 메시지 분석 및 토큰 사용량 반환
cost = calculate_cost(token_usage) # 각 요청에 대한 비용 계산
total_cost += cost # 총 비용에 추가
print(f"Request Cost: ${cost:.6f}, Total Tokens: {token_usage['total_tokens']}")
# 각 결과를 딕셔너리로 변환
result_dict = {}
for line in result_text.split("\n"):
if ": " in line:
key, value = line.split(": ", 1)
result_dict[key] = value.strip()
else:
result_dict[key] = ""
results.append(result_dict)
time.sleep(1) # 1초 대기
# 총 비용 출력
print(f"Total Cost for all requests: ${total_cost:.6f}")
가장 중요한 가격도 확인하면서, 처리된 데이터는 CSV 파일로 저장 후 Notion으로 옮겼다.
(당장은 대시보드를 만들동안 Notion에서 사람들이 취향을 확인할 수 있도록 하기 위해서)
이렇게 추출된 데이터를 통해 앞으로 대시보드 제작과 시각화 작업을 진행할 계획이다.
특정 장르의 게임, 특정 게임명을 클릭하면 해당 게임을 플레이하는 사용자들을 쉽게 확인할 수 있는 형태의 기능을 생각중이다.
(대시보드 없이 노션만 사용할 수도 있긴 한데, 개인적으로는 그다지 직관적이라는 판단이 들지 않아 좀 더 커스텀할 수 있는 대시보드를 만들어보려고 한다.)
또, 당장으로서는 새로운 유입 글보다는 '특정 게임 하실 분'이라는 같이 게임할 사람을 찾는 글이 주로 올라오기 때문에 배치 및 실시간 수집과 전처리 과정이 필요없는 상태여서 아카이빙 용도로 노션에 데이터를 적재해뒀다. (아마 나중에 데이터 분석을 할 때는 필요할 것 같긴하지만)
앞으로 할 것들
이후에는 노션보다 직관적이고 실시간 데이터 반영이 가능한 간단한 대시보드를 만들어볼 계획이다.
대시보드는 사람들이 최근에 어떤 게임을 가장 많이 플레이하고 있는지 파악할 수 있도록 구성할 예정이다. (물론 운영측의 허가 후..)
이를 위해 Airflow 같은 워크플로 도구를 활용하여 Slack 데이터를 주기적으로 수집하고 업데이트하는 파이프라인을 구축하려고 한다.
구체적으로는 아래 정도의 계획?
- 데이터 수집 자동화
- Airflow로 Slack API를 호출하여 주기적으로 새 메시지를 수집
- 메시지 데이터는 데이터베이스에 적재하여 관리
- 데이터 전처리
- 수집된 메시지에서 게임 이름, 장르 등 주요 정보를 추출
- GPT 모델을 활용해 데이터 품질과 일관성을 유지
- 데이터 시각화 및 대시보드 개발
- Streamlit 또는 Dash 같은 간단한 도구를 사용해 대시보드 구현
- 채널 내 게임별 인기 순위, 플레이어 수, 장르 분포 등을 한눈에 확인할 수 있도록 시각화
- 결과 반영 및 활용
- 대시보드를 Slack 채널 구성원이 실시간으로 참고할 수 있도록 공유
- 구성원 간 더 나은 소통과 게임 매칭 기회를 제공