데이터 수집 파이프라인 구축
졸업 프로젝트로 AI기반 평생교육 커리큘럼 추천 서비스 큐릭(Kuriq)을 개발하고 있다.
간단하게 서비스를 소개하자면, 국내 주요 공공 교육 플랫폼에 흩어져 있는 강좌를 하나의 인터페이스에서 통합 탐색할 수 있게 하고, 사용자가 자연어로 입력한 관심사/학습 목표/현재 수준등을 바탕으로 RAG 기반으로 개인에게 최적화된 주간 학습 로드맵을 자동으로 생성해주는게 주 기능이다.
그렇기에 서비스 구현을 위해서는 강좌 데이터를 벡터 DB에 적재하는 파이프라인이 필요하다!! 그래서 내가 구현한 공공 교육 데이터를 수집하고 전처리한 뒤 DB에 적재하기까지의 과정을 정리해봤다.
1. 환경 설정
의존성 충돌 방지, 환경의 동일성 유지, 뭉서보다 시스템의 깔끔한 유지등을 위해… 가상환경을 세팅하고 그 위에서 작업했다.
- 가상환경이란? 컴퓨터 전체 환경에 라이브러리를 설치하는 게 아니라, 특정 프로젝트만을 위한 독립적인 실행 환경을 구축하는 것이다.
가상환경 설치 및 의존성 설치
1
2
3
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
requirements.txt
1
2
3
4
requests==2.31.0
chromadb>=0.5.0
sentence-transformers==2.6.1
python-dotenv==1.0.0
requirements.txt란, 프로젝트를 실행하기 위해서 필요한 패키지의 리스트이다. (의존성 관리를 위한 문서)
사용 라이브러리는 네 가지로 최소화했다. 임베딩 모델로 현재는 OpenAI API 대신 sentence-transformers의 로컬 모델을 사용하기로 했는데, 후에 제공신청을 한 API에 대해서 제공여부가 결정된 후 여러 모델(OpenAI API, Qwen 등…)을 사용해본 후 가장 성능이 좋은 모델을 사용할 예정이다.
환경변수 (.env)
1
2
3
4
5
6
7
DATA_GO_KR_API_KEY=공공데이터포털_API_키
KOCW_API_KEY=KOCW_API_키
CHROMA_MODE=local # local | server
CHROMA_PATH=./chroma_db # 로컬 모드일 때 저장 경로
CHROMA_HOST=localhost # 서버 모드일 때
CHROMA_PORT=8000
공공데이터 API를 크롤링하여 정보를 가져오므로 공공데이터 포털 API키와 KOCW API키를 넣어야한다.
아직 서버를 배포하지 않아서 로컬에 저장해야하는데, 나중에 서버를 띄운 후 코드의 일부분을 수정해야하는 것을 막기 위해 CHROMA_MODE 변수로 local모드와 server모드를 나눴다. 나중에 서버 배포 후에는 CHROMA_MODE를 server로 바꾸고 서버 주소만 입력하면 DB에 연결할 수 있다.
2. 데이터 모델 설정
우리 서비스는 우선적으로 강좌 정보 API를 제공하는 국가평생교육 K-MOOC, 서울시 평생학습포털, KOCW의 강좌 정보만을 가져온다. (온국민평생배움터 강좌 정보 API 제공신청을 한 상태.) 이후, 서비스를 확장하게 되면 Gseek등의 좀 더 다양한 국가 평생교육 사이트의 강좌를 추가할 예정이다.
따라서 API 크롤링을 진행해야한다. 이에 앞서 모든 수집기가 공통으로 사용하는 Course 데이터클래스를 정의했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@dataclass
class Course:
id: str # 플랫폼 내 고유 ID
title: str # 강의명
institution: str # 운영 기관
platform: str # 플랫폼명 (K-MOOC, KOCW 등)
category: str # 원본 카테고리
std_category: str = "기타" # 큐릭 표준 카테고리 (전처리 후 채워짐)
description: str = "" # 강의 소개
duration: str = "" # 학습 기간 / 시간
url: str = "" # 수강 신청 URL
is_free: bool = True # 무료 여부
level: str = "" # 난이도 (있는 경우)
def embed_text(self) -> str:
"""임베딩용 텍스트 생성: 강의명 + 표준카테고리 + 기관 + 소개 200자"""
snippet = self.description[:200] if self.description else ""
return f"{self.title} [{self.std_category}] {self.institution} {snippet}".strip()
def to_metadata(self) -> dict:
"""ChromaDB metadata 딕셔너리"""
return {
"title": self.title,
"institution": self.institution,
"platform": self.platform,
"category": self.std_category,
"duration": self.duration,
"url": self.url,
"is_free": self.is_free,
"level": self.level,
}
def chroma_id(self) -> str:
"""ChromaDB document ID — 플랫폼_ID"""
return f"{self.platform}_{self.id}"
- 데이터클래스(@dataclass)란, 데이터를 저장하는 클래스를 만들 때 보일러플레이트 코드를 자동으로 생성해주는 데코레이터이다. 데이터 구조를 정의하는 간단한 경우에는 일반 클래스보다 가독성이 높고 유지보수가 편리하다.
embed_text(self)메서드로 벡터 DB에 저장하기 위해 임베딩용 텍스트를 생성한다. (강의명, 카테고리, 기관, 소개글을 합쳐서 하나의 문장으로 만듦) 강의 전체 내용을 전부 넣으면 검색 품질이 저하될 수 있기에 소개 앞 200자만 사용하여 토큰 수를 줄인다.- ChromaDB는 실제 텍스트 외에 메타데이터를 딕셔너리 형태로 저장하기에,
to_metadata(self)메서드로 메타데이터를 가공한다. - 강의를 구별하기 위해 플랫폼 이름과 ID를 조합하여 DB에서 각 강의를 식별할 고유 키를 만든다.