Skip to content

Repository files navigation

피파 전적관리

넥슨 오픈API로 EA SPORTS FC 온라인 전적을 조회·집계하는 PyQt6 데스크톱 앱(윈도우). 구단주 닉네임을 검색하면 감독모드 전적을 받아와 승률·선수 지표·전술 상성 등을 화면으로 보여준다.

  • 형태: 파이썬 3 + PyQt6 GUI 단일 실행 앱. 서버 없음, 설치 없이 소스로 실행하거나 exe로 빌드
  • 데이터: 넥슨 오픈API(JSON) + 공식 데이터센터 HTML 스크래핑(오픈API에 없는 순위·구단가치·선수 능력치)
  • 저장: 조회할 때마다 로컬 SQLite(fifa.db)에 누적 — API가 최근 100경기까지만 주는 한계를 넘기 위해
  • API 키: 개인 발급 키를 .env에 두고 읽는다(저장소에 포함되지 않음)

주요 기능

탭 / 기능 내용
전적 최근 경기 목록 — 결과·스코어·상대·점유율·슛 등
선수 지표 선수별 출전·평점·공격력·수비력·기대득점률·선방력(공식 미제공 값 역산)
전술·경기 결과 상대 전술(포메이션)별 승률, 결과 분포
상대 전적 구단주별 상성, 포지션별 최다 상대
승률 추이 기간별 승률·등급(디비전) 계단 그래프
기간별 추이 1일/2일/1주/1개월 단위 전적 + 연승·연패 기록
승부처 분석 선제골 승률, 시간대별 득실 분포, 시각대별 승률
성적 진단 상대 등급(디비전)별 승률·평균 득실, 점유율 구간별 승률
선수별 결정력 슛·유효슛·골·전환율·xG·어시스트 랭킹
선수 조합 함께 선발로 나온 선수쌍별 승률 — 잘 맞는/안 맞는 조합
슛 맵 슛 좌표를 하프 피치에 시각화(내 슛 / 상대 슛 실점 위치)
선수 카드 능력치·특성·시세·클럽경력 + 강화단계·적응도·팀컬러 시뮬레이터
랭커 카드 감독모드 순위·구단가치·랭킹점수(ELO)

화면

닉네임을 검색하면 랭커 카드로 요약을 보여주고, "감독모드 분석"에서 탭별 상세로 들어간다.

랭커 카드 — 순위·점수·전적·구단가치 요약
랭커 카드
선수 지표 — 포지션별 출전·평점·공수 지표(공식 미제공 값 역산)
선수 지표
전술·경기 결과 — 상대 포메이션별 승률, 득·실점 유형
전술·경기 결과
승률 그래프 — 기간별 승률·등급(디비전) 계단 그래프
승률 그래프
승부처 분석 — 선제골 승률, 시간 구간별 득실, 시각대별 승률
승부처 분석
슛 맵 — 슛 좌표를 하프 피치에 시각화(골/유효슛/빗나감)
슛 맵

시작하기

git clone https://github.com/yim2412/fifa-match-tracker.git
cd fifa-match-tracker

# 1) 패키지 설치
pip install -r requirements.txt

# 2) API 키 설정 — .env.example 을 복사해 .env 로 만들고 키를 넣는다
copy .env.example .env
notepad .env

# 3) API 연결 점검 (GUI 전에 먼저)
python check_api.py 내닉네임

# 4) 앱 실행
python app_main.py

# 5) 파싱·집계 회귀 테스트 (네트워크 없이, 실제 응답 픽스처로)
python tests/test_parsing.py
python tests/test_bot.py
node tests/test_adapter.js

# 6) (선택) 카카오톡 오픈채팅 봇 — 서버는 bot/README.md,
#    카톡에 붙이는 방법은 adapters/README.md
python -m bot.server

파일 구조

파일 역할
app_main.py PyQt6 UI — 검색 바, 랭커 카드, 분석 탭 15종(선수 지표·전술·승부처 분석·성적 진단·슛 맵·결정력·선수 조합·팀컬러 등), 조회 워커 스레드
widgets.py 화면 부품(위젯) — 랭커 카드·표·승률/등급 그래프·축구장 스쿼드 배치(PitchWidget)·슛 맵·NoScrollComboBox
theme.py 색상 상수·전역 스타일시트(QSS) — 배경·승/패·수비 등 색과 버튼·글꼴 스타일을 한 곳에
nexon_api.py 넥슨 오픈API 클라이언트. 엔드포인트 경로·에러코드가 전부 여기 상수에 모여 있다
models.py 매치 상세 JSON → MatchSummary 파싱, Stats·상대 전적·승률 추이 집계
stats.py 여러 경기 집계 — 선수 지표·전술·경기 결과. 역산해서 알아낸 상수가 여기 모여 있다
store.py 조회한 경기를 SQLite(fifa.db)에 누적 — API 100경기 한계 극복
ranker.py 넥슨 데이터센터에서 감독모드 순위·구단가치·ELO 를 긁어온다(오픈API 엔 없음)
images.py 넥슨 CDN에서 선수 얼굴 이미지를 받아 디스크에 캐시(오픈API 아닌 정적 CDN, 비공식)
playerinfo.py 선수 카드 상세(능력치·특성·시세·클럽경력) — 넥슨 모바일 데이터센터 HTML 스크래핑
config.py API 키 로드(.env), DB 경로, 매치 종류·조회 개수 기본값
check_api.py 터미널에서 키·엔드포인트·집계·DB 동작 확인용
bot/ 카카오톡 오픈채팅 봇 서버 — 같은 API·DB를 쓰는 별도 진입점(bot/README.md)
adapters/ 봇을 카카오톡에 붙이는 어댑터 — 메신저봇R 스크립트(adapters/README.md)
tests/test_parsing.py 파싱·집계 회귀 테스트 — 실제 응답 픽스처로 골든값 고정(네트워크 없음)
tests/test_bot.py 봇 명령 처리 회귀 테스트 — 채팅 한 줄 → 답장까지(네트워크 없음)
tests/test_adapter.js 어댑터 회귀 테스트 — 가짜 java.* 로 메신저봇R 진입점을 그대로 실행(node)

기본 매치 종류는 감독모드(52) 다 — 이 앱은 감독모드 전적을 본다.

누적 저장 (fifa.db)

API는 최근 100경기까지만 준다. 이 계정은 21시간에 100경기가 쌓여서(시간당 약 5경기), 하루만 조회를 걸러도 그 사이 경기는 영영 못 가져온다 — 과거를 조회할 수단이 API에 없다.

그래서 조회할 때마다 fifa.db에 쌓고, 화면은 API가 아니라 DB를 그린다. 검색하는 순간이 곧 수집이라, 자주 보는 계정일수록 데이터가 길어진다.

  • 저장 기준은 ouid — 구단주명은 바뀌어도 ouid는 안 바뀐다
  • 경기 원본은 한 번만 — 한 경기에 두 명이 나오므로 matches / match_players로 분리. 상대를 검색하면 이미 받아 둔 경기가 재활용된다
  • 이미 있는 경기는 API를 다시 부르지 않는다 (store.existing_ids)
  • 등록 계정 — 조회하면 자동 등록. 목록에서 고르면 바로 조회. 등록 해제해도 경기 기록은 남는다

한계: 과거는 못 되살린다. 처음 조회 시점의 최근 100경기가 시작점이고, 그 이전은 없다. 조회 간격이 100경기(≈20시간)보다 길면 그 사이가 뚫린다.

감독모드 랭킹 (ranker.py)

순위·구단가치·랭킹점수(ELO)는 오픈API(JSON)에 없다. 넥슨 공식 데이터센터 웹페이지에는 있어서 거기서 긁어온다.

https://fconline.nexon.com/datacenter/rank_inner?rt=manager&strCharacterName=<닉네임>
  • JSON API 가 아니라 HTML 스크래핑이다. 넥슨이 페이지 구조(class 이름)를 바꾸면 깨진다. 그래서 URL·class·정규식을 전부 ranker.py 한 곳에만 둔다 — 깨지면 실제 응답과 대조해 거기만 고친다.
  • 여기 전적은 감독모드 통산(오픈API 의 최근 3천 경기보다 많다)이라 랭커 카드는 이 값을 우선 쓴다. 단 요약 숫자일 뿐 경기별 상세는 아니다.
  • 랭킹은 상위 1만 위까지만. 밖이면 순위·구단가치·점수를 "랭킹 밖"으로 두고 전적은 앱이 받은 경기로 집계한다.

API 메모

  • 호스트: https://open.api.nexon.com
  • 인증: 요청 헤더 x-nxopen-api-key
  • 키 발급: NEXON Open API 로그인 → 마이페이지

사용 중인 엔드포인트 (nexon_api.py 상단 상수):

상수 경로 용도
EP_ID /fconline/v1/id 닉네임 → ouid
EP_USER_BASIC /fconline/v1/user/basic 계정 기본 정보
EP_MAX_DIVISION /fconline/v1/user/maxdivision 역대 최고 등급
EP_USER_MATCH /fconline/v1/user/match 매치 id 목록
EP_MATCH_DETAIL /fconline/v1/match-detail 매치 상세

메타데이터(매치 종류·선수·시즌·등급)는 인증 없이 https://open.api.nexon.com/static/fconline/meta/{name}.json.

공식 문서가 자바스크립트로 렌더링돼 경로를 자동 대조하지 못했다. check_api.py가 FAIL이면 공식 문서와 대조해 nexon_api.py 상단 상수만 고치면 된다.

응답에서 역산해 알아낸 것 (공식 문서에 없음)

실제 응답 100경기로 확인한 내용. 상수는 stats.py 상단에 있다.

항목 내용 어떻게 확인했나
shoot.goalTotal 등이 null 상대 탈주 등 기록 없는 경기는 키를 빼지 않고 null로 준다. .get(k, 0)은 기본값이 안 먹으니 타입까지 확인해야 한다(models._i) 30경기 조회 시 TypeError로 재현
shootDetail[].result 3 = 골 result==3 개수가 goalTotal 합계와 정확히 일치(126/126)
shootDetail[].goalTime 비트 패킹. >>24 = 구간(0 전반/1 후반/2 연장전반/3 연장후반), & 0xFFFFFF = 경과 초 구간 0·1은 최대 49.8분(45+추가), 2·3은 19.1분(15+추가)으로 딱 맞음
shootDetail[].type 슛 유형. 1 일반(D) · 2 감아차기(ZD) · 3 헤더 · 6 땅볼(DD) · 7 발리 · 8 프리킥 · 9 페널티킥 8·9는 goalFreekick/goalPenaltyKick 집계와 200개 선수-경기 행에서 경기별 정확히 일치. 나머지는 외부 전적 사이트의 유형별 골 수와 합계 일치
shootDetail[].result 1 · 2 1 = 유효슛(막힘) · 2 = 빗나감(3은 골) effectiveShootTotal == 골+result1이 1189/1190, shootTotal == shootDetail 총개수가 1190/1190
shootDetail[].x · .y 슛 좌표(0~1 정규화). x=1.0이 상대 골문 쪽(슛이 x>0.48에 몰림), y=0.5가 폭 중앙. assistX/Y·hitPost·inPenalty도 있음 실제 캐시 1,190 선수-경기로 범위·방향 확인
type 4 · 10 · 13 · 14 미상. "알 수 없음"으로 표시 근거 없음. 참고한 사이트도 못 읽는다
shoot.shootOutScore 승부차기 점수. 승부차기가 없었으면 양쪽 0 2:2 무승부인데 승/패가 갈린 경기에서만 0이 아님
spPosition 28 = SUB(교체 명단). 전술은 GK·SUB 제외하고 수비(1-8)-수미(9-11)-미드(12-16)-공미(17-19)-공격(20-27) 인원 상대 전술별 승률이 외부 사이트와 일치(4-1-2-3-0 → 51.7%, 31승3무26패)

매치 상세는 경기별 실제 스쿼드를 준다(현재 스쿼드를 돌려주는 게 아니다). 볼타 66경기에서 서로 다른 스쿼드 조합 10개가 나오는 것으로 확인.

앞으로

  • 선수별 통계 — stats.aggregate_players
  • 상대 전술별 승률 — stats.formation_stats
  • 공격력·수비력·기대득점률·선방력 — API 미제공이라 fc-info.com 프론트엔드 JS 번들 (pages/analysis/coach/[id]-*.js)에서 계산식을 그대로 역산해 옮겼다(PlayerStat). 실제 100경기로 재현해 값이 거의 일치함을 확인(GK 선방력 151.5 vs 참고 151.6 등). 선방력은 defending 합계가 아니라 경기당 평균×100이다 — 100경기 표본에서 우연히 합계처럼 보였을 뿐, 경기 수가 다르면 값이 완전히 달라진다
  • 상대 구단주별 상성 분석 — models.opponent_stats, "상대 전적" 탭
  • 기간별 승률 추이 차트 — models.win_rate_trend(월별), "승률 추이" 탭
  • 조회 결과 로컬 DB(SQLite) 누적 — store.py
  • 선수 이미지 (넥슨 CDN) — images.py. 오픈API가 아니라 비공식 CDN 직링크 (fco.dn.nexoncdn.co.kr)라 넥슨이 바꾸면 깨질 수 있다
  • exe 빌드 (PyInstaller)
  • 선수 카드 상세(능력치·특성·시세·클럽경력) — playerinfo.py, 상대 스쿼드·선수 지표 표·포지션별 최다 상대 표에서 선수 클릭(더블클릭)
  • 선수 카드 능력치 시뮬레이터 — 강화단계(1~13강, 기본 1강)·적응도(+1/+5, 기본 +1 — 홈페이지 첫 화면과 동일 기준)·팀컬러(소속/강화/관계 3종)를 바꿔가며 실제 능력치 확인. 로컬 근사 계산이 아니라 PC 데이터센터(fconline.nexon.com)가 실제로 쓰는 /datacenter/PlayerAbility 엔드포인트를 그대로 호출해서 넥슨 서버가 계산한 값을 받는다(playerinfo.fetch_player_ability) — 팀컬러 종류(600여개)별 보너스 조합표를 넥슨이 공개하지 않아 근사가 불가능하므로, 정확성을 위해 매 조작마다 재조회하는 방식을 택함. 팀컬러 드롭다운은 응답 HTML에 같이 오는 그 선수 전용 선택지로 채운다(전체 목록 페이지 불필요). 레벨 UI는 없음 — 소속 팀컬러는 항상 그 팀컬러의 최대 레벨(대부분 4)로 자동 조회하고, 강화 팀컬러는 항목 자체에 레벨이 있으며("Lv.2 금빛 물결"), 관계 팀컬러는 레벨이 없다. 능력치 값 색상도 데이터센터와 같은 10점 구간 기준(playerinfo.stat_color). "포지션별 오버롤" 탭 — 데이터센터 축구장 그림의 16개 포지션 값(1강·적응도 +1 기준)
  • 구단주 비교 탭에 각 계정의 최근 경기 스쿼드 표시 — stats.own_squad
  • 등급(디비전) 추이 그래프 — 승률 그래프 탭 아래, 매 경기 당시 division을 계단 그래프로(저장된 데이터만 사용, 추가 조회 없음)
  • 기간별 추이 탭 — 누적 전체를 1일/2일/1주/1개월 단위로 묶은 전적 표 (models.period_stats) + 현재/최장 연승·연패(models.longest_streaks)
  • 팀컬러 랭킹 표에 팀컬러별 평균/최저/최고 팀가치 — 팀컬러를 긁을 때 같은 페이지에 있는 구단가치를 같이 저장(추가 요청 없음). 캐시 TTL은 팀컬러·팀가치 모두 7일. 넥슨 웹 점검 중 응답은 "랭킹 밖"으로 오인해 캐시하지 않도록 감지
  • 승부처 분석 탭 — 이미 저장된 shootDetail만으로 계산(추가 조회 없음). 선제골 승률(stats.clutch_summary: 내가 선제골/선제 실점 시 승률, 역전승·역전패), 정규시간 15분 6구간+연장의 득실 분포(stats.goal_minute_buckets), 시각대별 승률(stats.time_of_day_rates: 심야/오전/오후/저녁밤 4구간)
  • 선수별 결정력 랭킹 — shootDetail을 슈터(spId)·도움(assistSpId)별로 모아 선수별 슛·유효슛·골·전환율·xG·어시스트(stats.finishing_ranking). "선수별 결정력" 탭, 골−xG에 색(+초록/−빨강), 선수 더블클릭 시 선수 카드. 표시 구간 기준
  • 기대득점(xG) 근사 — stats.shot_xg. 넥슨이 정답값을 안 줘서 순수 근사(비공식). 슛 좌표의 골까지 거리·골대 각도 + 박스 여부·슛 유형(페널티킥 0.76, 헤더 감산)으로 득점 확률을 로지스틱 추정. 계수는 실제 캐시에서 xG 합≈실제 골이 되도록 맞춤 (자기 계정 캘리브레이션 — 200경기서 xG 105.4 vs 실측 113). 슛 맵 요약·결정력 표에 표시
  • 슛 맵 — shootDetail의 x/y 좌표를 공격 진영 하프 피치 위에 점으로 (stats.shot_map, widgets.ShotMapWidget). 골=초록·유효슛=노랑·빗나감=회색, 골대맞음은 흰 테두리. "슛 맵" 탭에서 내 슛/상대 슛(실점 위치) 토글, 골·유효슛· 빗나감 종류별 표시 on/off 체크박스, 표시 구간 기준. 요약(슛·골·유효슛률·전환율)은 필터와 무관하게 전체 기준으로 표시. 상대 슛은 좌표가 상대 공격 기준이라 y(좌우)를 뒤집어 내 시점으로 통일
  • 성적 진단 탭 — 저장된 데이터만으로(추가 조회 없음) 상대 등급(division)별 승률· 평균 득실(stats.division_stats)과 내 점유율 구간별(열세<40·균형40~60·우세>60) 승률(stats.possession_stats). 누적 전체 기준
  • 선수 조합(케미) 탭 — 함께 선발(SUB·GK 제외)로 나온 선수쌍별 승률 (stats.pair_synergy). 최소 경기수 필터, 승률 50% 기준 색칠(초록/빨강), 선수 더블클릭 시 카드. 누적 전체 기준

라이선스

MIT

넥슨 오픈API·데이터센터를 이용하는 비공식 개인 프로젝트다. EA SPORTS FC 온라인과 관련 데이터의 권리는 NEXON / EA 에 있으며, 이 프로젝트는 넥슨·EA 와 무관하다.

About

넥슨 오픈API로 EA SPORTS FC 온라인 감독모드 전적을 조회·집계하는 PyQt6 데스크톱 앱 (승률·선수 지표·전술 상성·슛 맵, SQLite 누적 저장)

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages