Skip to content

ace19-dev/Localing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

362 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

로컬링 (Localing)

한국어 | English

Local + Ring(알림) = 우리 동네 문화생활을 한눈에!

로컬링은 대한민국 전국의 문화행사, 스포츠 강좌, 평생학습 프로그램 정보를 자동으로 수집하고, AI가 맞춤 추천해주는 문화생활 플랫폼입니다.

🚀 직접 실행해보고 싶다면처음부터 실행하기 가이드 (docs/GETTING_STARTED.md)


시스템 아키텍처

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                    LOCALING                                          │
│                        AI 기반 문화행사 검색 플랫폼                                   │
└─────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────┐
│  FRONTEND (static/)                                                                  │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────────────────────────────────┐   │
│  │ index.html  │  │  style.css   │  │  app.js                                   │   │
│  │ - 채팅 UI   │  │ - 반응형     │  │ - 위치감지 (GPS/IP)                        │   │
│  │ - 모달 창   │  │ - 모바일퍼스트│  │ - 자연어 검색                              │   │
│  │ - 로딩 오버레이│ │ - 다크모드   │  │ - 페이지네이션                            │   │
│  └─────────────┘  └──────────────┘  └───────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                          │
                                          ▼ HTTP/JSON
┌─────────────────────────────────────────────────────────────────────────────────────┐
│  BACKEND (app/)                                          FastAPI + Uvicorn/Gunicorn │
│                                                                                      │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  API Routes (api/routes/)                                                     │  │
│  │  ┌──────────────┐  ┌───────────────────┐  ┌─────────────┐  ┌──────────────┐  │  │
│  │  │ POST /api/   │  │ GET /api/detect-  │  │ GET /api/   │  │ POST /kakao/ │  │  │
│  │  │     chat     │  │     location      │  │   locate    │  │ skills/events│  │  │
│  │  └──────┬───────┘  └───────────────────┘  └─────────────┘  └──────────────┘  │  │
│  └─────────┼─────────────────────────────────────────────────────────────────────┘  │
│            │                                                                         │
│            ▼                                                                         │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Services (services/)                                                         │  │
│  │                                                                               │  │
│  │  ┌─────────────────┐    ┌───────────────────┐    ┌─────────────────────────┐ │  │
│  │  │  llm.py         │    │ semantic_search.py │    │  admin_region.py       │ │  │
│  │  │                 │    │                    │    │                        │ │  │
│  │  │ • Gemini 2.5    │    │ • Hybrid Search    │    │ • 행정구역 정규화       │ │  │
│  │  │ • Rule-based    │    │   (벡터+키워드)    │    │ • 별칭 매핑             │ │  │
│  │  │   fallback      │    │ • pgvector 유사도  │    │ • 캐시 기반 조회        │ │  │
│  │  └─────────────────┘    └───────────────────┘    └─────────────────────────┘ │  │
│  │                                                                               │  │
│  │  ┌─────────────────┐    ┌───────────────────┐    ┌─────────────────────────┐ │  │
│  │  │  embedding.py   │    │  events.py        │    │  region_parser.py      │ │  │
│  │  │                 │    │                   │    │                        │ │  │
│  │  │ • E5-small-ko   │    │ • 이벤트 스코어링 │    │ • 전국 지역 추출       │ │  │
│  │  │ • 384차원 벡터  │    │ • 키워드 확장     │    │ • 동명이인 처리        │ │  │
│  │  │ • 배치 생성     │    │ • 추천 알고리즘   │    │                        │ │  │
│  │  └─────────────────┘    └───────────────────┘    └─────────────────────────┘ │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
│            │                                                                         │
│            ▼                                                                         │
│  ┌───────────────────────────────────────────────────────────────────────────────┐  │
│  │  Models & ORM (models/)                                        SQLAlchemy 2.0 │  │
│  │  ┌─────────────────────────────────────────────────────────────────────────┐ │  │
│  │  │  Event Model                                                            │ │  │
│  │  │  - id, source, title, sido, sigungu, dong, place, address              │ │  │
│  │  │  - start_date, end_date, codename, use_fee, homepage...                │ │  │
│  │  │  - embedding (Vector 384) ← pgvector                                   │ │  │
│  │  └─────────────────────────────────────────────────────────────────────────┘ │  │
│  └───────────────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────┘
                                          │
                                          ▼ psycopg2
┌─────────────────────────────────────────────────────────────────────────────────────┐
│  DATABASE                                              PostgreSQL (Neon) + pgvector │
│                                                                                      │
│  ┌───────────────────────────────────┐    ┌─────────────────────────────────────┐   │
│  │  events 테이블                    │    │  admin_regions 테이블               │   │
│  │  - 25,000+ 행사                   │    │  - 시도/시군구/읍면동               │   │
│  │  - HNSW 벡터 인덱스               │    │  - 별칭 매핑                        │   │
│  │  - 복합 인덱스                    │    │                                     │   │
│  └───────────────────────────────────┘    └─────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────┐
│  CRAWLERS (crawlers/)                                          278개 지역별 크롤러   │
│                                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────────────┐    │
│  │  Base Classes                                                               │    │
│  │  ┌─────────────────────────┐    ┌──────────────────────────────────────┐   │    │
│  │  │  BaseCrawler            │    │  BaseSeleniumCrawler                 │   │    │
│  │  │  - HTTP 기반            │    │  - JavaScript 렌더링 필요 사이트     │   │    │
│  │  │  - BeautifulSoup        │    │  - Headless Chrome                   │   │    │
│  │  └─────────────────────────┘    └──────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐   │
│  │ seoul/ (43) │ │gyeonggi(106)│ │ incheon(17) │ │ busan/ (5)  │ │national(26) │   │
│  │ 25개 자치구 │ │ 31개 시/군  │ │ 10개 구/군  │ │ 16개 구/군  │ │KOPIS,Yes24  │   │
│  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘   │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐   │
│  │ daegu/ (5)  │ │daejeon/(6)  │ │ gwangju/(5) │ │ ulsan/ (5)  │ │ gangwon(15) │   │
│  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘   │
└─────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────┐
│  EXTERNAL SERVICES                                                                   │
│                                                                                      │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  ┌────────────────┐  │
│  │ Google Gemini   │  │  Kakao Maps     │  │   Nominatim     │  │ 지자체/플랫폼  │  │
│  │ 2.5-flash LLM   │  │  Local API      │  │ Reverse Geocode │  │ KOPIS/관광공사 │  │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘  └────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────┐
│  INFRASTRUCTURE                                                  Google Cloud Run   │
│                                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────────────┐    │
│  │  배포 파이프라인                                                            │    │
│  │                                                                             │    │
│  │   cloudbuild.yaml → Docker Build → Container Registry → Cloud Run          │    │
│  │                                                                             │    │
│  │   Cloud Scheduler (매주 월요일 2AM KST) → 크롤러 실행                        │    │
│  │   Cloud Scheduler (5분마다, 24시간) → /warmup 워밍업                        │    │
│  └─────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                      │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────────────┐  │
│  │ Secret Manager  │  │ asia-northeast3 │  │ max-instances: 1, min-instances: 0  │  │
│  │ API Keys 보관   │  │ (서울 리전)     │  │ 2 CPU, 4Gi Memory, Concurrency 80   │  │
│  └─────────────────┘  └─────────────────┘  └─────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────────────┘

검색 시퀀스 다이어그램

  사용자                 app.js              chat.py             region_parser.py        DB (PostgreSQL)
    │                     │                    │                       │                       │
    │  "시청 맛집"        │                    │                       │                       │
    │────────────────────▶│                    │                       │                       │
    │                     │  POST /api/chat    │                       │                       │
    │                     │───────────────────▶│                       │                       │
    │                     │                    │                       │                       │
    │                     │                    │  ① Rule-based 파싱    │                       │
    │                     │                    │──────────────────────▶│                       │
    │                     │                    │                       │                       │
    │                     │                    │                       │  RegionCache 조회     │
    │                     │                    │                       │  (alias → sido/sigungu)│
    │                     │                    │                       │──────────────────────▶│
    │                     │                    │                       │  시청 → 서울특별시 중구│
    │                     │                    │                       │◀──────────────────────│
    │                     │                    │                       │                       │
    │                     │                    │  ParsedQuery 반환     │                       │
    │                     │                    │  region=중구          │                       │
    │                     │                    │  sido=서울특별시       │                       │
    │                     │                    │  keywords=[맛집]      │                       │
    │                     │                    │◀──────────────────────│                       │
    │                     │                    │                       │                       │
    │                     │                    │  ② normalized_region 설정                     │
    │                     │                    │  sigungu=중구, sido=서울특별시                 │
    │                     │                    │  (parsed 정보 직접 사용, 재조회 없음)           │
    │                     │                    │                       │                       │
    │                     │                    │  ③ 계층적 지역 매칭   │                       │
    │                     │                    │  sigungu=중구 AND sido=서울특별시              │
    │                     │                    │──────────────────────────────────────────────▶│
    │                     │                    │  서울 중구 이벤트만 반환 (동명이구 방지)       │
    │                     │                    │◀──────────────────────────────────────────────│
    │                     │                    │                       │                       │
    │                     │                    │  ④ 하이브리드 검색    │                       │
    │                     │                    │  벡터 유사도(0.6) + 키워드 LIKE(0.4)          │
    │                     │                    │──────────────────────────────────────────────▶│
    │                     │                    │◀──────────────────────────────────────────────│
    │                     │                    │                       │                       │
    │                     │                    │  ⑤ 스코어링 + 정렬   │                       │
    │                     │                    │  키워드 매칭·무료·날짜 근접·지역 가중치        │
    │                     │                    │                       │                       │
    │                     │  JSON 응답         │                       │                       │
    │                     │  (AI 요약 + 9개)   │                       │                       │
    │                     │◀──────────────────│                       │                       │
    │  결과 카드 렌더링    │                    │                       │                       │
    │◀────────────────────│                    │                       │                       │
    │                     │                    │                       │                       │

파싱 Fallback 체인

사용자 입력
    │
    ▼
┌─────────────────────────┐     성공     ┌─────────────────────┐
│  Rule-based 파서        │────────────▶│  ParsedQuery 반환   │
│  (region_parser.py)     │             │  region, sido,      │
│  • 지역 alias 매칭      │             │  keywords, 날짜     │
│  • 조사/접미사 처리     │             └─────────────────────┘
│  • 날짜 파싱            │
└─────────┬───────────────┘
          │ 지역 추출 실패
          ▼
┌─────────────────────────┐     성공     ┌─────────────────────┐
│  Gemini LLM Fallback    │────────────▶│  ParsedQuery 반환   │
│  (llm.py)               │             └─────────────────────┘
│  • JSON 구조화 추출     │
└─────────┬───────────────┘
          │ API 오류/실패
          ▼
┌─────────────────────────┐
│  키워드 전용 검색       │
│  (지역 필터 없이 전체)  │
└─────────────────────────┘

지역 매칭 Fallback (계층적)

  ① 동(dong) 매칭 시도 ──── 있으면 ──▶ 결과 반환
          │
        없으면
          ▼
  ② 시군구(sigungu) 매칭 ── 있으면 ──▶ 결과 반환  (sido 함께 필터링 → 동명이구 방지)
          │
        없으면
          ▼
  ③ 시도(sido) 매칭 ─────── 있으면 ──▶ 결과 반환
          │
        없으면
          ▼
  ④ 전체 이벤트 Fallback ────────────▶ 결과 반환 + "해당 지역 서비스 준비 중" 안내

기술 스택 요약

레이어 기술
프론트엔드 Vanilla JS, CSS3, Pretendard 폰트
백엔드 FastAPI 0.104, Uvicorn, Gunicorn
데이터베이스 PostgreSQL (Neon) + pgvector
ORM SQLAlchemy 2.0
AI/NLP Gemini 2.5-flash, E5-small-ko (384차원), kiwipiepy (Korean NLP)
크롤링 BeautifulSoup4, Selenium, httpx
인프라 Docker, Google Cloud Run, Cloud Build
스케줄링 Cloud Scheduler (크롤링: 매주 월요일 02:00 KST, 워밍업: 5분마다 24시간)

주요 기능

1. 데이터 수집 (278개 크롤러)

지역 크롤러 수 주요 데이터 소스
서울 43개 25개 구 문화센터, 체육시설, 평생학습관, 도서관
경기 106개 31개 시군 문화센터, 도서관, 청소년시설, 박물관
인천 17개 10개 구 문화시설, 도서관, 체육시설
부산 5개 문화센터, 도서관, 체육시설, 청소년시설
대구 5개 문화센터, 도서관, 체육시설
대전 6개 문화센터, 도서관, 구별 센터
광주 5개 문화센터, 도서관, 체육시설
울산 5개 문화센터, 도서관, 체육시설
강원 15개 속초, 강릉, 춘천, 원주 등 문화/축제
충북 4개 청주 문화/도서관/평생학습
충남 4개 천안 문화/도서관/평생학습
전북 9개 전주, 군산, 익산 문화/축제
전남 15개 목포, 순천, 여수 문화/체육/청소년
경북 4개 경주(gyeongju_culture.py), 구미(gumi_culture.py), 안동(andong_culture.py), 포항(pohang_culture.py)
경남 4개 창원, 김해, 거제, 진주 문화
제주 5개 문화센터, 도서관, 체육시설
전국/플랫폼 26개 KOPIS, 인터파크, 예스24, Meetup, 한국관광공사, 국립박물관 등

2. AI 자연어 검색

  • Google Gemini 2.5-flash: 자연어 질문 이해 및 파싱
  • 한국어 최적화 임베딩: dragonkue/multilingual-e5-small-ko 모델 (384차원)
  • 하이브리드 검색: 시맨틱(벡터 유사도) + 키워드(LIKE) 검색 결합
  • 행정구역 정규화: admin_regions 테이블 + 카카오 로컬 API fallback
    • DB 캐시 기반 빠른 매칭 (시도/시군구/동 계층)
    • alias 컬럼으로 별칭 지원 (여의도→영등포구, 홍대→마포구)
    • 매칭 실패 시 카카오 로컬 API로 자동 정규화
  • 스마트 Fallback: API 오류 시 규칙 기반 파서로 자동 전환
  • 위치 기반: GPS/IP로 현재 위치 자동 감지
  • 키워드 추출: 검증된 불용어/조사 사전 기반

3. 웹 인터페이스

  • 채팅 UI: 대화형 검색 인터페이스
  • 추천 이벤트: AI 기반 맞춤 추천
  • 반응형 디자인: 모바일/데스크톱 지원

4. 성능 최적화

  • DB 연결 풀 튜닝: pool_size=20, max_overflow=40 (최대 60 동시 연결)
  • 멀티 지역 단일 쿼리: 복수 지역 검색 시 N개 쿼리 → 1개 OR 쿼리로 통합
  • 행정구역 캐시: RegionCache 싱글턴에 시군구→시도 매핑 딕셔너리 캐시, UNION ALL 단일 쿼리
  • 임베딩 모델 백그라운드 Preload + Lock: 서버 시작 시 백그라운드 스레드로 로드, threading.Lock 이중 잠금으로 중복 로딩 방지, float16 직접 로드로 메모리 피크 절감
  • /warmup 엔드포인트: 임베딩 모델만 사전 로드 (LLM·DB 없음), warmup 스케줄러가 5분마다 호출해 첫 검색 지연 방지
  • Cloud Scheduler 워밍업: 5분 간격 /warmup 호출로 인스턴스·모델 warm 유지 (24시간)
  • Rule-based 파싱 우선: LLM 호출 전 규칙 기반 파서 먼저 적용하여 API 비용 최소화

프로젝트 구조

Localing/
├── app/                          # FastAPI 애플리케이션
│   ├── main.py                   # 앱 진입점
│   ├── api/routes/               # API 라우트
│   │   ├── chat.py               # 채팅 API (자연어 검색)
│   │   ├── location.py           # 위치 감지 API (IP/GPS/타임존)
│   │   └── kakao.py              # 카카오 챗봇 스킬 서버
│   ├── services/                 # 비즈니스 로직
│   │   ├── llm.py                # Gemini AI 연동
│   │   ├── semantic_search.py    # 시맨틱 검색 (하이브리드 검색)
│   │   ├── embedding.py          # 텍스트 임베딩 (E5-small-ko)
│   │   ├── events.py             # 이벤트 점수/추천
│   │   ├── admin_region.py       # 행정구역 정규화 (DB 캐시)
│   │   ├── region_parser.py      # 지역 파싱
│   │   ├── kakao.py              # 카카오 챗봇 통합
│   │   └── kakao_local.py        # 카카오 로컬 API (fallback)
│   ├── models/                   # SQLAlchemy 모델
│   ├── schemas/                  # Pydantic 스키마
│   └── constants/                # 상수 정의
│
├── crawlers/                     # 크롤러 모듈 (278개)
│   ├── base.py                   # 기본 크롤러 클래스
│   ├── base_selenium.py          # Selenium 크롤러 클래스
│   ├── utils.py                  # 유틸리티 함수
│   │
│   ├── seoul/                    # 서울 (43개)
│   ├── gyeonggi/                 # 경기 (106개)
│   ├── incheon/                  # 인천 (17개)
│   ├── busan/                    # 부산 (5개)
│   ├── daegu/                    # 대구 (5개)
│   ├── daejeon/                  # 대전 (6개)
│   ├── gwangju/                  # 광주 (5개)
│   ├── ulsan/                    # 울산 (5개)
│   ├── gangwon/                  # 강원 (15개)
│   ├── chungbuk/                 # 충북 (4개)
│   ├── chungnam/                 # 충남 (4개)
│   ├── jeonbuk/                  # 전북 (9개)
│   ├── jeonnam/                  # 전남 (15개)
│   ├── gyeongbuk/                # 경북 (4개)
│   ├── gyeongnam/                # 경남 (4개)
│   ├── jeju/                     # 제주 (5개)
│   └── national/                 # 전국/플랫폼 (26개)
│
├── core/                         # 핵심 모듈
│   ├── database.py               # DB 연결 (커넥션 풀 최적화)
│   ├── config.py                 # 설정 관리
│   └── prompts.py                # AI 프롬프트
│
├── static/                       # 프론트엔드
│   ├── index.html                # 메인 페이지
│   ├── about.html                # 서비스 소개
│   ├── faq.html                  # FAQ
│   ├── guide.html                # 사용 가이드
│   ├── privacy.html              # 개인정보처리방침
│   ├── terms.html                # 이용약관
│   ├── app.js                    # JavaScript
│   ├── style.css                 # 스타일시트
│   ├── og-image.png              # OG 이미지
│   ├── robots.txt                # 크롤러 설정
│   ├── sitemap.xml               # 사이트맵
│   ├── ads.txt                   # AdSense 인증
│   ├── blog/                     # 블로그 (58개 SEO 글 + index)
│   └── events/                   # SEO 이벤트 랜딩 페이지 (63개)
│
├── tests/                        # 테스트
│   ├── ai_message_and_llm.py    # AI 메시지 및 LLM 파싱 테스트 (400+ 케이스)
│   └── comprehensive_parsing.py  # 종합 파싱 테스트 (1,583+ 케이스)
│
├── scripts/                      # 실행 스크립트
│   ├── run_all_crawlers.py       # 전체 크롤러 실행
│   ├── generate_embeddings.py    # 임베딩 일괄 생성
│   ├── download_model.py         # 임베딩 모델 다운로드 (GCS 캐시)
│   ├── update_embedding_model.py # 임베딩 모델 업데이트
│   ├── fix_region_data.py        # 지역 데이터 정규화
│   ├── normalize_fees.py         # 이용료 데이터 정규화
│   ├── remove_duplicate_events.py # 중복 이벤트 제거
│   ├── analyze_crawlers.py       # 크롤러 분석
│   ├── convert_english_to_korean.py # 데이터 언어 변환
│   ├── remove_fallback.py        # Fallback 데이터 정리
│   ├── check_memory_usage.py     # 메모리 사용량 측정
│   ├── fix_invalid_urls.py       # 잘못된 URL 데이터 수정
│   ├── run_tests.sh              # 테스트 일괄 실행
│   └── setup_secrets.sh          # GCP Secret Manager 설정
│
├── db/                           # 데이터베이스
│   ├── schema.sql                # 테이블 스키마 (events, admin_regions)
│   ├── seed_admin_regions.sql    # 행정구역 시드 데이터 (7,460행, 별칭 포함)
│   ├── neon.md                   # Neon PostgreSQL 연결 가이드
│   └── migrations/               # DB 마이그레이션
│       ├── 001_add_embedding.sql # pgvector 임베딩 컬럼 추가
│       ├── 002_drop_gu_name_column.sql
│       ├── 003_remove_event_key.sql # 복합 유니크 제약조건
│       ├── 004_add_landmark_aliases.sql # 랜드마크 별칭 추가
│       ├── 005_add_nationwide_aliases.sql # 전국 지역 별칭 추가
│       ├── 006_add_hongdae_alias.sql  # 홍대 별칭 추가
│       └── 007_fix_aliases_ambiguous.sql # 동명이구 별칭 수정
│
├── models/                       # 임베딩 모델 캐시
│
├── docs/                         # 문서
│   ├── GETTING_STARTED.md        # 처음부터 실행하기 가이드
│   ├── OPERATIONS.md             # 운영 명령어 가이드
│   ├── CRAWLERS_README.md        # 크롤러 상세 문서
│   └── claude_testcase_guide.txt # 테스트 케이스 작성 가이드
│
├── cloudbuild.yaml               # Google Cloud Build 설정
├── Dockerfile                    # Docker 이미지
└── requirements.txt              # Python 의존성

지원 지역 상세

서울특별시 (43개 크롤러)

권역 크롤러
강남권 강남구 gangnam.py, gangnam_culture.py
서초구 seocho.py, seocho_sports.py, seocho_sports_center.py, seocho_citizen_center.py
송파구 songpa_culture.py, songpa_learning.py, songpa_sports.py
강서권 강서구 gangseo_culture.py
양천구 yangcheon_culture.py
영등포구 yeongdeungpo_culture.py, yeongdeungpo_learning.py
강북권 노원구 nowon_culture.py
도봉구 dobong_culture.py
강북구 gangbuk_culture.py
동북권 동대문구 dongdaemun_culture.py
성동구 seongdong_culture.py
광진구 gwangjin_culture.py
중랑구 jungnang_culture.py
성북구 seongbuk_culture.py
서북권 마포구 mapo_culture.py
서대문구 seodaemun_culture.py
은평구 eunpyeong_culture.py
도심권 종로구 jongno_culture.py
중구 junggu_culture.py
용산구 yongsan_culture.py
서남권 관악구 gwanak.py, gwanak_culture.py
동작구 dongjak.py
금천구 geumcheon_culture.py
구로구 guro_culture.py
동남권 강동구 gangdong_culture.py
공통 서울시 seoul_opendata_events.py, seoul_libraries.py, seoul_library.py, seoul_museums.py, seoul_youth.py, seoul_youth_centers.py, seoul_festival.py, seoul_sfac.py, seoul_sema.py, seoul_sports_multi.py

경기도 (106개 크롤러)

수원, 성남, 고양, 용인, 부천, 안산, 안양, 남양주, 화성, 평택, 의정부, 시흥, 파주, 김포, 광명, 광주, 군포, 하남, 오산, 이천, 안성, 의왕, 양평, 여주, 과천, 가평, 양주, 연천, 포천, 동두천, 구리 등 31개 시군

전국/플랫폼 (26개 크롤러)

크롤러 설명
kopis.py KOPIS 공연예술통합전산망 API
interpark_ticket.py 인터파크 티켓
yes24_ticket.py 예스24 티켓
showala.py 쇼왈라 공연 정보 (AJAX)
meetup.py Meetup.com 이벤트
onoffmix.py 온오프믹스 행사
eventbrite.py Eventbrite 이벤트
thinkcontest.py 공모전/대회 정보
visitkorea_festival.py 한국관광공사 축제 API
visitkorea_experience.py 한국관광공사 체험 API
coex.py, kintex.py, bexco.py, exco.py 전시컨벤션센터
sac.py 서울 예술의전당
museum_korea.py 국립중앙박물관
mmca.py 국립현대미술관
seoul_history_museum.py 서울역사박물관
national_library_api.py 국립도서관 API
national_museum_api.py 국립박물관 API
national_youth_api.py 청소년활동정보서비스 API
culture_portal.py 문화포털
culture_facility.py 문화시설 정보
kcisa_culture.py 한국문화정보원
culture_unified.py 서울시 + 전국 문화행사 통합 수집
seoul.py 서울시 열린데이터광장

빠른 시작

🧭 처음 실행하시나요? 준비물부터 트러블슈팅까지 전 과정을 담은 처음부터 실행하기 가이드 (docs/GETTING_STARTED.md) 를 따라 하세요. 아래는 요약본입니다.

임베딩 모델 준비

검색에 사용하는 임베딩 모델(dragonkue/multilingual-e5-small-ko, 약 450MB)은 저장소에 포함되어 있지 않습니다 (models/는 gitignore 대상).

  • 로컬 개발: 별도 준비 불필요. 서버 첫 실행 시 Hugging Face에서 자동 다운로드됩니다 (인터넷 연결 필요).
  • Docker 빌드: Dockerfilemodels/ 디렉토리를 이미지에 복사하므로, 빌드 전에 모델을 미리 받아둬야 합니다:
# models/ 디렉토리에 모델 다운로드 (Docker 빌드 전 1회)
SENTENCE_TRANSFORMERS_HOME=./models python -c \
  "from sentence_transformers import SentenceTransformer; SentenceTransformer('dragonkue/multilingual-e5-small-ko')"
  • Cloud Build 배포: scripts/download_model.py가 GCS 버킷 캐시에서 모델을 가져옵니다 (GCS_BUCKET 환경변수로 버킷 지정). GCS에 없으면 Hugging Face에서 받아 캐시합니다.

로컬 개발

# 1. 의존성 설치
pip install -r requirements.txt

# 2. 환경 변수 설정
cp .env.example .env
# .env 파일 편집하여 API 키 설정

# 3. DB 초기화 (pgvector 지원 PostgreSQL 필요, 예: Neon)
psql $DATABASE_URL -f db/schema.sql
psql $DATABASE_URL -f db/seed_admin_regions.sql   # 행정구역 시드 (지역 파싱 필수)

# 4. 크롤러 실행 (행사 데이터 수집)
python scripts/run_all_crawlers.py

# 5. 임베딩 생성
python scripts/generate_embeddings.py

# 6. 서버 실행 (첫 실행 시 임베딩 모델 자동 다운로드)
uvicorn app.main:app --reload

db/seed_admin_regions.sql은 반드시 실행해야 합니다. 전국 행정구역(시도/시군구/동)과 별칭(홍대→마포구 등) 7,460행이 들어 있으며, 이 데이터가 없으면 지역 파싱·크롤러의 지역 정규화가 동작하지 않습니다. 별칭 마이그레이션(004~007)은 이미 반영되어 있습니다.

Docker 실행

# 사전에 위 "임베딩 모델 준비"의 models/ 다운로드 필요
docker-compose up -d

Google Cloud 배포

gcloud builds submit --config cloudbuild.yaml

환경 변수

.env 파일에 다음 변수들을 설정하세요:

# 데이터베이스
DATABASE_URL=postgresql://user:password@host/dbname

# AI
GEMINI_API_KEY=your-gemini-api-key

# 지자체/플랫폼 API
SEOUL_API_KEY=your-seoul-opendata-api-key
NATIONAL_API_KEY=your-data-go-kr-api-key
KOPIS_API_KEY=your-kopis-api-key

# 위치 서비스
KAKAO_REST_API_KEY=your-kakao-key  # 지도 표시 + 지역 정규화 fallback

API 엔드포인트

엔드포인트 메서드 설명
/ GET 메인 페이지
/about GET 서비스 소개
/faq GET FAQ
/guide GET 사용 가이드
/blog GET 블로그 목록
/blog/{slug} GET 블로그 포스트
/events/{slug} GET SEO 이벤트 랜딩 페이지 (도시+카테고리 조합)
/privacy GET 개인정보처리방침
/terms GET 이용약관
/api/chat POST AI 채팅 (자연어 검색)
/api/detect-location GET IP 기반 위치 감지
/api/detect-location-timezone GET 타임존 기반 위치 감지 (fallback)
/api/locate GET 좌표→지역명 변환
/kakao/skills/events POST 카카오 챗봇 스킬 서버
/warmup GET 임베딩 모델 워밍업 (스케줄러 5분 간격 호출)
/health GET 헬스체크

기술 스택

  • Backend: FastAPI, SQLAlchemy, PostgreSQL (Neon) + pgvector
  • Frontend: Vanilla JS, CSS
  • AI/NLP: Google Gemini 2.5-flash, Sentence Transformers (dragonkue/multilingual-e5-small-ko), kiwipiepy (Korean NLP)
  • Crawling: BeautifulSoup4, Selenium, Requests, httpx
  • Infra: Google Cloud Run, Cloud Build, Cloud Scheduler
  • Container: Docker

테스트

# AI 메시지 및 LLM 파싱 테스트 (400+ 케이스)
python tests/ai_message_and_llm.py

# 종합 파싱 테스트 (1,583+ 케이스)
python tests/comprehensive_parsing.py

현재 2,000+ 테스트 케이스 (AI 메시지/LLM 400+ + 종합 파싱 1,583+)

  • 전국 시도/시군구/랜드마크/지하철역 커버리지 (단일 + 2개 지역 복합)
  • 단일 지역+조사 637, 복합 지역 296, 키워드 150, 노이즈 워드 134, 날짜 파싱 94, 복합 조합 133, 엣지 케이스 139
  • 테스트 케이스의 약 70%가 조사 포함 자연어 형태 (~에서, ~에 있는, 알려줘, 추천해줘 등)

라이선스

MIT License


Made with care for better local culture exploration

About

Local culture platform that auto-collects cultural events across all 17 provinces of Korea and serves them via AI-powered natural language search. FastAPI + pgvector hybrid search (E5-small-ko embeddings + Gemini), 278 crawlers, Kakao chatbot integration.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages