
발트해 그림자 선단 추적기 오픈소스. 1200+ 척 선박, 실시간 AIS, 해저 케이블 근접 알림. 클라우드 없음, 구독 없음, 로컬 실행.
발트해 섀도우 플리트 모니터링을 라이브 AIS 데이터로 수행합니다. 우크라이나 GUR 전쟁 및 제재 카탈로그의 1200척 이상 선박을 AISStream 웹소켓 피드와 대조하여 추적하고, 자동 업데이트 맵에 위치를 표시하며, 해저 케이블 근접 여부를 플래그하고, 러시아↔서방 환적 패턴을 탐지합니다.
무료, 오픈 소스, 로컬에서 실행됩니다. 클라우드 및 무료 AISStream API 키 외의 구독이 필요하지 않습니다.
Former Lab 주권 인텔리전스 툴체인의 일부입니다.
Shadow Fleet Tracker Light는 Former Lab 팀이 구축하고 유지 관리합니다 — 주권 컴퓨팅, 벤처 캐피털 없이 오래된 하드웨어에서 개방형 프라이버시 우선 도구를 만듭니다.
이 도구가 유용하다면 Patreon을 통한 후원을 고려해 주세요. 무료 티어가 제공되며, 유료 티어는 7일 무료 체험이 가능합니다. 항상 우크라이나를 지지합니다!
git clone https://github.com/FormerLab/shadow-fleet-tracker-light.git
cd shadow-fleet-tracker-light
pip install -r requirements.txt
export AISSTREAM_API_KEY=your_key_here # aisstream.io에서 무료
python shadow_tracker.py &
uvicorn webserver:app --host 0.0.0.0 --port 8000
http://localhost:8000을 엽니다 — 맵은 /map에 있습니다.
Windows 사용자: 환경 변수 구문은 Setup 섹션을 참조하세요 (PowerShell/cmd).
Linux / macOS 사용자는 start.sh를 사용할 수 있습니다. Windows 사용자는 start.bat 또는 start.ps1을 사용할 수 있습니다 — 두 스크립트 모두 트래커와 대시보드를 별도 창에서 열고 브라우저를 자동으로 실행합니다.
Vessels1.db)에서 가져온 1200+ 선박 MMSI 목록에 대해 스트림을 필터링합니다filtered_cables.kml의 케이블 세그먼트에서 10km 이내에 접근하면 경고합니다케이블 경로가 녹색으로 오버레이된 어두운 CartoDB 배경 지도. 선박 마커는 3분마다 업데이트되며 다음 새로 고침까지의 실시간 카운트다운이 표시됩니다 — 페이지가 자동으로 다시 로드됩니다. 팝업에는 MMSI, IMO, 국기, 속도, 방향, 목적지, MarineTraffic, VesselFinder 및 War&Sanctions에 대한 딥 링크가 포함됩니다. OpenSanctions API 키가 구성된 경우 제재 상태도 표시됩니다.
Vessels1.db에 알려진 선박 이름은 AIS 정적 메시지를 기다리지 않고 첫 번째 출현 시 즉시 표시됩니다.
시작 시 맵은 지난 24시간 동안의 위치 로그로 미리 채워집니다. 현재 세션에서 아직 보지 못한 선박은 "마지막으로 본 X시간 전" 레이블과 함께 회색 마커로 표시되며, 새 핑이 도착하면 실시간 색상으로 전환됩니다.
shadow_tracker.py 메인 프로세스 — WS 소비자, 맵 렌더러, DB 쓰기
loitering_module.py 선회 탐지 및 맵 주석
transshipment_module.py 항구 호출 감지 및 러시아↔서방 환적 플래깅
webserver.py FastAPI 대시보드
gur_scrape.py 원샷 스크래퍼 — IMO→GUR-ID 매핑 + 전체 선박 카탈로그 구축
check.py 사전 점검 — Python 버전, 의존성, API 키, 네트워크
start.sh Linux / macOS 실행기
start.bat Windows 실행기 (명령 프롬프트)
start.ps1 Windows 실행기 (PowerShell)
requirements.txt 의존성
Vessels1.db 감시 목록 — 1200+ 선박 (알려진 경우 MMSI + IMO + 이름)
vessel_data_log.db 위치 로그 — 런타임, 추가 전용
vessel_static.db AIS 정적 데이터 캐시 (이름, 목적지, 국기 기록)
transshipment.db 항구 호출 로그 및 환적 이벤트
loitering_events.db 선회 이벤트
filtered_cables.kml 발트해 해저 케이블 지오메트리
cables.kml 전체 케이블 데이터셋 (사전 필터 소스)
gur_mapping.json IMO→GUR-ID 매핑 (gur_scrape.py에서 생성, 선택 사항)
gur_vessels_full.json 전체 GUR 카탈로그 — GUR-ID→{imo, mmsi, name, flag} (gur_scrape.py에서 생성)
Vessels1.db는 추적 선박의 표준 목록입니다. 우크라이나 GUR 전쟁 및 제재 카탈로그에서 가져온 1200개 이상의 항목으로, 러시아 섀도우 플리트의 일부로 식별되거나 발트해 하이브리드 활동에 연루된 유조선 및 화물선을 포함합니다.
목록에는 발트해 당국이 최근 압수 또는 차단한 선박이 포함됩니다:
MMSI는 현재 AIS 데이터에 맞게 수정됩니다 — 섀도우 플리트 선박은 자주 국기를 변경합니다. update_vessels_2026_03.sql 파일에는 출처와 함께 모든 변경 사항이 문서화되어 있습니다.
감시 목록은 의도적으로 열려 있습니다. 트래커가 실행되는 동안 Vessels1.db에 직접 선박을 추가할 수 있습니다. 재시작 없이 5분 이내에 적용됩니다.
Linux / macOS:
sqlite3 Vessels1.db "INSERT OR IGNORE INTO vessels (mmsi, imo, name) VALUES ('123456789', '9999999', 'VESSEL NAME');"
Windows (sqlite3 CLI 불필요):
import sqlite3
conn = sqlite3.connect("Vessels1.db")
conn.execute("INSERT OR IGNORE INTO vessels (mmsi, imo, name) VALUES ('123456789', '9999999', 'VESSEL NAME')")
conn.commit()
conn.close()
Vessels1.db — 감시 목록
vessels(mmsi TEXT PK, imo TEXT, name TEXT, destination TEXT)
vessel_data_log.db — 위치 로그
vessel_data_log(timestamp, mmsi, name, imo, destination, speed, heading, latitude, longitude, cable_alert)
추가 전용. 맵 렌더링 주기마다 기록됩니다.
vessel_static.db — AIS 정적 데이터 캐시
vessel_static(mmsi TEXT PK, name TEXT, imo TEXT, destination TEXT, flag TEXT)
flag_history(id, mmsi, flag, timestamp)
실시간 ShipStaticData AIS 메시지에서 채워집니다. 수신되면 Vessels1.db 이름보다 우선합니다. flag_history는 모든 국기 변경을 타임스탬프와 함께 기록합니다 — 재국기 사건이 로그에 기록되고 선박별 페이지에 표시됩니다.
loitering_events.db — 선회 로그 (런타임에 생성)
loitering_events(id, mmsi, timestamp, latitude, longitude, near_cable)
transshipment.db — 항구 호출 및 환적 로그 (런타임에 생성)
port_calls(id, mmsi, name, port, port_type, entry_ts, exit_ts, min_speed)
transshipment_events(id, mmsi, name, direction, from_port, from_exit_ts,
to_port, to_entry_ts, days_between, detected_ts)
requirements.txt의 의존성트래커를 실행하는 가장 쉬운 방법입니다. 각 스크립트는 의존성을 설치하고 사전 점검을 실행하며 두 프로세스를 모두 시작합니다.
Linux / macOS:
chmod +x start.sh
export AISSTREAM_API_KEY=your_key_here
./start.sh
Windows (PowerShell):
$env:AISSTREAM_API_KEY = "your_key_here"
.\start.ps1
Windows (명령 프롬프트):
set AISSTREAM_API_KEY=your_key_here
start.bat
세 스크립트 모두 먼저 check.py를 실행합니다 — Python 버전, 의존성, 데이터 파일, API 키 및 네트워크 연결 가능성을 확인하고 누락된 항목에 대해 명확한 오류 메시지를 표시합니다.
사전 점검은 언제든지 단독으로 실행할 수도 있습니다:
python check.py
1. 클론 및 의존성 설치
git clone https://github.com/FormerLab/shadow-fleet-tracker-light.git
cd shadow-fleet-tracker-light
pip install -r requirements.txt
Ubuntu 22.04+ 및 Debian 12+에서는 pip에서 "외부 관리 환경" 오류가 발생할 수 있습니다. 시작 스크립트는 가상 환경을 자동으로 생성하여 처리합니다 — start.sh를 실행하면 알아서 처리됩니다. 수동으로 설치하는 경우 먼저 venv를 만드세요:
python3 -m venv .venv
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows
pip install -r requirements.txt
2. AISStream API 키 설정
Linux / macOS:
export AISSTREAM_API_KEY=your_key_here
export OPENSANCTIONS_API_KEY=your_key_here # 선택 사항
Windows (명령 프롬프트):
set AISSTREAM_API_KEY=your_key_here
set OPENSANCTIONS_API_KEY=your_key_here
Windows (PowerShell):
$env:AISSTREAM_API_KEY = "your_key_here"
$env:OPENSANCTIONS_API_KEY = "your_key_here"
3. 트래커와 대시보드를 두 개의 별도 터미널에서 실행
터미널 1 — AIS 피드 소비자:
python shadow_tracker.py
터미널 2 — 웹 대시보드:
uvicorn webserver:app --host 0.0.0.0 --port 8000
4. 대시보드 열기
http://localhost:8000
실시간 맵은 http://localhost:8000/map에 있으며 3분마다 업데이트됩니다. 첫 번째 실행 시 맵은 데이터베이스에 이미 있는 위치 기록에서 즉시 렌더링됩니다 — 빈 화면 대기 없음
| 서비스 | 필수 | 무료 티어 | 링크 |
|---|---|---|---|
| AISStream | 예 | 예 — 비상업적 용도 무제한 | aisstream.io |
| OpenSanctions |
OPENSANCTIONS_API_KEY 없이도 트래커는 정상 작동합니다 — 제재 배지가 맵 팝업에서 생략될 뿐입니다
모든 조정 가능한 값은 shadow_tracker.py 상단에 있습니다:
환경 변수:
| 변수 | 필수 | 설명 |
|---|---|---|
AISSTREAM_API_KEY | 예 | AISStream WebSocket API 키 |
OPENSANCTIONS_API_KEY | 아니오 | 맵 팝업에서 제재 조회 활성화 |
OpenSanctions은 비상업적 용도로 무료입니다 — opensanctions.org/api에서 등록하세요. 키가 없으면 트래커는 정상 작동하며, 제재 필드는 팝업에서 생략됩니다.
war-sanctions.gur.gov.ua는 우크라이나 GUR의 섀도우 플리트 및 제재 선박 공개 카탈로그입니다. 각 선박에는 항구 호출 기록, 지도 및 제재 세부 정보가 있는 번호가 매겨진 페이지가 있습니다.
gur_scrape.py는 카탈로그를 한 번 크롤링하여 로컬 IMO→GUR-ID 매핑을 구축합니다:
python gur_scrape.py
이렇게 하면 gur_mapping.json이 생성됩니다. 트래커는 시작 시 이를 로드하여 가능한 경우 맵 팝업에서 직접 딥 링크(/en/transport/ships/{id})를 사용하고, 아직 카탈로그에 없는 선박의 경우 Google 사이트 검색으로 폴백합니다.
크롤링은 요청당 1.5초로 약 1600개 항목을 처리합니다(약 40분). 카탈로그가 증가함에 따라 가끔 다시 실행하세요. 특정 ID에서 재개하려면 --start를 사용하세요:
python gur_scrape.py --start 1580
단일 페이지를 디버깅하려면:
python gur_scrape.py --probe 1517
gur_mapping.json은 감시 목록과 함께 5분마다 자동으로 다시 로드됩니다 — 재크롤 후 트래커 재시작이 필요하지 않습니다.
loitering_module.py의 선회 임계값:
| 상수 | 기본값 | 설명 |
|---|---|---|
SPEED_THRESHOLD_KN | 0.5 노트 | 이 이하이면 정지로 간주 |
transshipment_module.py의 환적 설정:
| 상수 | 기본값 | 설명 |
|---|---|---|
SPEED_THRESHOLD_KN | 1.5 노트 | 항구 구역 내에서 이 이하이면 호출로 간주 |
포함된 항구 구역:
| 항구 | 유형 |
|---|---|
| Ust-Luga, Primorsk, St Petersburg, Vyborg | 러시아 수출 터미널 |
| Skaw/Skagen, Gothenburg, Kiel, Copenhagen, Aarhus | 서방 환적 허브 |
각 선박 페이지(/vessel/<mmsi>)는 다음을 표시합니다:
위치 데이터에서 추론된 두 가지 패턴을 플래그 지정합니다:
항구 호출은 위치 핑에서 추론됩니다 — 외부 API가 필요하지 않습니다. 선박은 항구 구역 내에서 <1.5 노트로 최소 2번의 핑을 기록해야 호출로 등록됩니다. 이는 패턴을 보여주는 것이지 판결이 아닙니다.
filtered_cables.kml은 오픈 소스 데이터셋에서 파생되었으며 근사치입니다 — 정밀한 근접 계산에 적합하지 않습니다. 10km 경고 임계값은 이 여유를 고려합니다. 개선된 케이블 지오메트리 기여를 환영합니다.
트래커는 WebSocket 스트림에서 단일 비동기 루프를 실행합니다. 상태는 메모리(vessel_info, static_cache)에 유지되고 각 렌더링 주기에 SQLite로 플러시됩니다. 맵은 os.replace를 통해 원자적으로 작성되어 부분 파일 제공을 방지합니다.
시작 시 warm_restart()는 vessel_data_log.db에서 마지막 WARM_RESTART_HOURS 시간을 읽고 WebSocket이 연결되기 전에 vessel_info를 미리 채웁니다. 초기 맵이 즉시 렌더링됩니다. 오래된 위치는 시각적으로 구분되며(회색 마커, 희미한 흔적) 새 핑이 도착하면 실시간 색상으로 전환됩니다.
감시 목록과 gur_mapping.json은 5분마다 디스크에서 다시 로드됩니다 — 트래커가 실행되는 동안 선박을 추가하고 GUR 매핑을 다시 크롤링할 수 있으며 재시작이 필요하지 않습니다.
웹 서버는 상태 비저장이며 SQLite 파일에서 직접 읽습니다 — 트래커 프로세스와 공유 메모리가 없습니다.
| 선박 | IMO | 사건 |
|---|
| EAGLE S | 9329760 | 핀란드, 2024년 12월 압수 — Estlink-2 케이블 사보타주, 스파이 장비 발견 |
| EVENTIN | 9308065 | 독일, 2025년 1~3월 압수 — Rügen 표류, 100,000톤 러시아 원유 압수 |
| KIWALA | 9332810 | 에스토니아, 2025년 4월 억류 — 국기 없음, EU/영국 제재 |
| JAGUAR | 9293002 | 에스토니아, 2025년 5월 차단 — 러시아 Su-35 출격 호위 |
| KIRA K | 9346720 | 2025년 12월 Wagner/GRU 승무원 확인 |
| QENDIL | 9310525 | 2025년 9월 Wagner/GRU 승무원 — 2025년 12월 우크라이나 드론 공격 |
| FITBURG | 9250397 | 핀란드, 2025년 12월 31일 압수 — 헬싱키-탈린 케이블 사보타주 |
| CAFFA | 9143611 | 스웨덴, 2026년 3월 6일 압수 — 도난된 우크라이나 곡물, 위장 국기 |
| SEA OWL I | 9321172 | 스웨덴, 2026년 3월 12일 압수 — EU 제재, 위장 코모로 국기 |
| 아니오 |
| 예 — 비상업적 용도 |
| opensanctions.org/api |
| 상수 | 기본값 | 설명 |
|---|
BALTIC_BBOX | 52.65–66°N, 9–30°E | AISStream 구독 경계 상자 |
CABLE_ALERT_KM | 10 | 케이블 근접 임계값 |
MAP_RENDER_INTERVAL | 180 초 | 맵 새로 고침 주기 |
MMSI_RELOAD_INTERVAL | 300 초 | DB에서 감시 목록 다시 로드 |
WARM_RESTART_HOURS | 24 | 시작 시 로드할 위치 기록 시간 |
RECONNECT_DELAY | 10 초 | WS 재연결 백오프 |
TIME_THRESHOLD_S| 1200 초 |
| 선회 플래그 지정 전 지속 시간 |
RADIUS_THRESHOLD_KM | 0.5 km | 동일 지점으로 간주되는 최대 드리프트 |
MIN_PINGS_IN_ZONE| 2 |
| 항구 호출 기록 전 최소 핑 수 |
WINDOW_DAYS | 21 | 환적으로 플래그할 항구 호출 간 최대 일수 |
| 경로 | 설명 |
|---|
/ | 실시간 로그 뷰어, 60초마다 자동 새로 고침 |
/map | 최신 렌더링 맵 (iframe 호환) |
/analyze | 필터링 가능한 선박 기록 테이블, CSV 내보내기 포함 |
/timeline | 활동 개요 — 추적 선박당 하나의 카드, 마지막 본 순서로 정렬 |
/vessel/<mmsi> | 선박별 기록: AIS 블랙아웃, 케이블 근접, 국기 변경, GPX 내보내기 |
/vessel/<mmsi>/gpx | GPX 트랙 내보내기 — QGIS, OpenStreetMap, GPSBabel에서 열기 |
/loitering | 선회 이벤트 (케이블 근접 플래그 포함), 선박별 요약, CSV 내보내기 |
/transshipment | 러시아↔서방 항구 호출 패턴 — RU→WEST 및 WEST→RU 이벤트, CSV 내보내기 |
/log/download | 원시 로그 파일 다운로드 |
| 구성 요소 | 상태 |
|---|
| AIS 스트림 소비자 | 작동 중 |
| 감시 목록 | 1200+ 선박 (전체 GUR 카탈로그) |
| 케이블 근접 경고 | 작동 중 |
| 선회 탐지 | 작동 중 |
| 환적 탐지 | 작동 중 — 위치에서 추론된 항구 호출, 21일 창 |
| 맵 렌더링 | 작동 중 — 3분 주기, 실시간 카운트다운, 자동 새로 고침 |
| 웜 재시작 | 작동 중 — 시작 시 마지막 24시간 로그로 맵 미리 채움 |
| 선박 팝업 | MarineTraffic + VesselFinder + War&Sanctions 직접 링크 (1337척 매핑) |
| 선박 팝업 | OpenSanctions 제재 상태 (선택 사항, API 키 필요) |
| 국기 변경 탐지 | 작동 중 — 변경 기록, flag_history에 저장 |
| 대시보드 — 로그, 선박, CSV 내보내기 | 작동 중 |
| 대시보드 — 타임라인 보기 | 작동 중 |
| 대시보드 — 선박별 기록 + GPX | 작동 중 |
| 대시보드 — 경로 재생 | 작동 중 — 재생/일시정지/재설정, 속도 제어, 케이블 경고 강조 |
| 대시보드 — 선회 패널 | 작동 중 |
| 대시보드 — 환적 패널 | 작동 중 |