From 4a850b3ae96c1bbeaf8dee5dfb1569e5cd7a25c8 Mon Sep 17 00:00:00 2001 From: You-Hyuk Date: Thu, 6 Aug 2026 18:05:45 +0900 Subject: [PATCH] =?UTF-8?q?[docs]=20README.md=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- .github/logo.png | Bin 0 -> 24707 bytes LICENSE | 21 ++++++ README.md | 163 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 .github/logo.png create mode 100644 LICENSE create mode 100644 README.md diff --git a/.github/logo.png b/.github/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..94ec3917b967431a52340eb66529f21d6e96bf3d GIT binary patch literal 24707 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 + Coming + +
+
+ + **Jpop 아티스트 내한 공연 정보 통합 플랫폼** + + KOPIS·MusicBrainz·setlist.fm·Spotify 데이터를 수집·매칭해 적재하는 데이터 파이프라인 — Claude Code 서브에이전트·스킬 워크플로우로 개발 전 과정을 진행한 1인 프로젝트입니다. + +
+ + [![Python](https://img.shields.io/badge/Python-3.9-3776AB?style=flat-square&logo=python&logoColor=white)](pyproject.toml) + [![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=flat-square&logo=fastapi&logoColor=white)](api.py) + [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?style=flat-square&logo=postgresql&logoColor=white)](https://www.postgresql.org/) + [![Docker](https://img.shields.io/badge/Docker-2496ED?style=flat-square&logo=docker&logoColor=white)](Dockerfile) + [![CI](https://img.shields.io/github/actions/workflow/status/Cominggg/Data/ci.yml?style=flat-square&logo=githubactions&logoColor=white&label=CI)](https://github.com/Cominggg/Data/actions/workflows/ci.yml) + [![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE) + +
+ + **[→ comingg.com](https://comingg.com)** + + + +## 소개 + +`coming-data`는 Coming의 데이터 파트로, 다음 역할을 담당합니다. + +- KOPIS(공연예술통합전산망)에서 국내 공연 정보를 수집하고, MusicBrainz 아티스트 데이터와 매칭 +- MusicBrainz에서 아티스트·멤버·릴리즈(앨범/싱글/EP) 정보를 수집 +- setlist.fm에서 공연 완료 후 셋리스트를 수집 +- Spotify에서 아티스트 프로필 이미지를 수집 +- 위 파이프라인을 APScheduler로 정기 실행하고, 백엔드(Spring)의 트리거 요청을 내부 API로 수신 + +## 기술 스택 + +| 분류 | 사용 기술 | +|---|---| +| 언어 | Python 3.9 | +| 스케줄러 | APScheduler | +| 내부 API 서버 | FastAPI + Uvicorn | +| DB 연동 | SQLAlchemy + psycopg2 (DML 전용, DDL은 백엔드 Flyway가 관리) | +| 테스트/린트 | pytest, pytest-cov, ruff | +| 배포 | Docker, GitHub Actions (CI/CD), GHCR | + +## 수집 파이프라인 + +| 단계 | 수집 주기 | 진입점 | +|------|---------|--------| +| ① MusicBrainz 아티스트 | 초기 1회 | `run_initial_collect()` | +| ② 공연 상태 갱신 | 매일 04:00 | `run_concert_status_update()` | +| ③ 신규 공연 수집·매칭 | 매일 04:30 | `run_new_concert_collect()` | +| ④ 릴리즈 (앨범·트랙·커버) | 초기 + 매일 05:00 | `run_release_update()` | +| ⑤ 아티스트 이미지 | 매주 목 02:00 | `run_artist_image_update()` | +| ⑥ 로마자→한글 alias 변환 | 매주 목 03:00 | `run_ja_romanize_collect()` | +| ⑦ setlist.fm | 매일 06:00 (공연완료 대상) | `run_setlist_collect()` | + +공연-아티스트 매칭은 ③ 단계 안에서 함께 수행됩니다. 전체 크론 목록은 [`scheduler.py`](scheduler.py)의 `_build_scheduler()`를 참고하세요. + +## 외부 API 연동 + +| API | 엔드포인트 | Rate Limit | 비고 | +|-----|-----------|------------|------| +| KOPIS | `GET /openApi/restful/pblprfr` | 없음 | 주 1회 이상 권장 | +| MusicBrainz | `GET /ws/2/artist/`, `/ws/2/release-group/`, `/ws/2/release/` | 1 req/sec | | +| Cover Art Archive | `GET /release-group/{mbid}/front` | 1 req/sec | 404 시 null 허용 | +| setlist.fm | `GET /rest/1.0/search/setlists` | - | `x-api-key` 헤더 필요 | +| Spotify Web API | `POST /api/token`, `GET /v1/artists/{id}`, `GET /v1/search` | rolling 30초 윈도우 | Client Credentials Flow | +| Discord Webhook | `POST {webhook_url}` | 웹훅당 분당 약 30건 | 신규 공연 알림, 미설정 시 생략 | + +## 레포지토리 구조 + +``` +coming-data/ +├── collectors/ +│ ├── artist_image.py # Spotify Web API 아티스트 프로필 이미지 수집 +│ ├── kopis.py # KOPIS API 수집 +│ ├── ja_romanize.py # sort_name 로마자 표기 → 한글 alias 규칙 변환 +│ ├── musicbrainz.py # MusicBrainz 아티스트·멤버 수집 +│ ├── release.py # MusicBrainz 릴리즈(앨범·싱글·EP) + 트랙·커버 수집 +│ ├── setlist.py # setlist.fm 셋리스트 수집 +│ └── spotify_client.py # Spotify Client Credentials 토큰 발급·공통 요청 +├── matchers/ +│ └── artist_matcher.py # alias 기반 phrase-matching 로직 +├── notifier/ +│ └── discord.py # Discord Webhook 알림 (신규 공연 수집 결과) +├── db/ +│ ├── connection.py # SQLAlchemy 엔진·세션 설정 +│ └── repository.py # DB 저장 함수 (DML) +├── tests/ # pytest 단위 테스트 +├── scheduler.py # APScheduler 진입점 (내부 API 서버도 함께 기동) +├── api.py # 내부 FastAPI 서버 — BE→Data 수집 트리거 (X-Internal-Secret 인증) +└── pyproject.toml +``` + +## 시작하기 + +### 설치 + +```bash +git clone https://github.com/Cominggg/Data.git coming-data +cd coming-data +python -m venv .venv && source .venv/bin/activate +pip install -e ".[dev]" +``` + +### 환경변수 + +`.env` 파일에 아래 값을 설정합니다. + +``` +DB_HOST= +DB_PORT= +DB_NAME= +DB_USER= +DB_PASSWORD= +KOPIS_API_KEY= +MUSICBRAINZ_USER_AGENT= +SETLISTFM_API_KEY= +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= +INTERNAL_SECRET= +DISCORD_WEBHOOK_URL= # 선택 — 미설정 시 알림 생략 +``` + +### 실행 + +```bash +# 초기 1회 수집 (아티스트 → KOPIS 매칭 → 릴리즈) +python scheduler.py init + +# 상시 데몬 (APScheduler 크론 + 내부 API 서버, 기본 포트 8000) +python scheduler.py + +# 단일 잡 즉시 실행 +python scheduler.py run-job --job {concert-status-update|new-concert-collect|release-update|ja-romanize|artist-image|setlist} + +# 누락 이미지·릴리즈 재수집 +python scheduler.py recover +``` + +## 테스트 & 린트 + +```bash +pytest # 단위 테스트 (통합 테스트 제외) +ruff check . # 린트 +``` + +## CI/CD + +- **CI** ([`ci.yml`](.github/workflows/ci.yml)): 모든 브랜치 push 및 main 대상 PR에서 `ruff check .` + `pytest` 실행 +- **CD** ([`cd.yml`](.github/workflows/cd.yml)): main 브랜치 push 시 Docker 이미지를 빌드해 GHCR에 푸시하고, SSH로 운영 서버에 접속해 `docker compose`로 배포 + +## 관련 레포지토리 + +- [Backend](https://github.com/Cominggg/Backend) — Spring Boot API 서버 +- [Frontend](https://github.com/Cominggg/Frontend) — 웹 클라이언트 +- [Specification](https://github.com/Cominggg/Specification) — ERD·API 명세 + +## License + +MIT © [You-Hyuk](https://github.com/You-Hyuk)