Skip to main content
pykorail — 파이썬으로 KTX 표를 조회하고 예매합니다

코레일 스마트 예매(코레일톡) API 를 감싼 비공식 클라이언트

CI PyPI Python License

문서 사이트 · 빠른 시작 · 할 수 있는 것 · API 레퍼런스 · 기여하기


빠른 시작

pip install pykorail
from datetime import datetime, timedelta, timezone
from pykorail import Korail

kst = timezone(timedelta(hours=9))
# 내일 오전 9시(KST) 출발을 찾습니다.
departure = (datetime.now(kst) + timedelta(days=1)).replace(hour=9, minute=0, second=0, microsecond=0)

with Korail.logged_in("me@example.com", "password") as korail:
    trains = korail.trains.search("서울", "부산", depart_after=departure)
    for train in trains:
        print(train)
[KTX 101]  04/01 09:00~12:30  서울~부산  특실 가능, 일반실 가능 (3시간 30분)
[KTX 103]  04/01 10:00~13:30  서울~부산  특실 매진, 일반실 가능 (3시간 30분)
[KTX 105]  04/01 11:00~14:35  서울~부산  특실 가능, 일반실 매진 (3시간 35분)

할 수 있는 것

표 예매하고 결제하기

from pykorail import AdultPassenger, Card, ChildPassenger

trains = korail.trains.search(
    "서울",
    "부산",
    depart_after=departure,
    passengers=[AdultPassenger(2), ChildPassenger(1)],
)

reservation = korail.reservations.create(trains[0])
print(reservation)
# [KTX 101] … 119600원(3석), 구입기한 4월 1일 09:20

korail.reservations.pay(
    reservation,
    Card(number="1234567812345678", password="12", verify_number="900101", expire="2812"),
)
취소표 기다리기
import time
from pykorail import NoResultsError, PastDepartureError

departure = (datetime.now(kst) + timedelta(days=1)).replace(hour=9, minute=0, second=0, microsecond=0)

while True:
    try:
        trains = korail.trains.search("서울", "부산", depart_after=departure)
    except NoResultsError:
        time.sleep(30)  # 서버에 부담 주지 않게 넉넉히 쉬세요
        continue
    except PastDepartureError:
        break  # 열차가 이미 떠났습니다 — 무한정 돌지 않도록

    korail.reservations.create(trains[0])
    break
뒤쪽 열차까지 이어서 보기

서버는 조회 한 번에 한 쪽(보통 열 편)만 줍니다. max_pages 를 올리면 마지막 열차의 출발 시각부터 다시 조회해 이어 붙입니다.

trains = korail.trains.search("서울", "부산", include_no_seats=True, max_pages=5)
print(len(trains), trains[-1].dep_time)

쪽마다 요청이 한 번씩 더 나갑니다. 새 열차가 더 없으면 그 전에 멈춥니다.

매진일 때 예약대기 걸기
trains = korail.trains.search("서울", "부산", include_waiting_list=True)
waitable = [t for t in trains if not t.has_seat() and t.has_waiting_list()]

reservation = korail.reservations.create(waitable[0])
assert reservation.is_waiting
인근역에서 출발·도착하는 열차까지 보기

앱의 "인접역" 옵션입니다. 용산 → 대전을 찾으면 서울 → 대전 · 용산 → 서대전 편도 함께 나옵니다. 직통이 없는 구간은 이걸 켜지 않으면 조회할 방법이 없습니다.

trains = korail.trains.search("용산", "대전", include_nearby_stations=True)

for train in trains:
    print(train.dep_name, "→", train.arr_name)
# 서울 → 대전 / 용산 → 서대전 / 서울 → 서대전 …

# 출발은 용산에서만 하고 싶다면 결과에서 고르면 됩니다
from_yongsan = [t for t in trains if t.dep_name == "용산"]

결과가 더해지는 것이 아니라 후보가 넓어지는 것이라, 켜면 인근역 편이 시간순으로 끼어들며 뒤쪽 직통편을 밀어낼 수 있습니다. 그래서 기본값은 꺼짐입니다.

내 예약·승차권 보기
for reservation in korail.reservations.all():
    print(reservation.train.dep_name, "→", reservation.train.arr_name, reservation.price)

for ticket in korail.tickets.all():
    print(ticket.ticket_no, ticket.car_no, ticket.seat_no)
    korail.tickets.refund(ticket)  # 환불
공항버스 시간표 · 좌석 보기

로그인 없이 됩니다. 코레일이 파는 것은 광명역 → 인천공항(T1 · T2) 방향뿐입니다.

buses = korail.airport_bus.search("광명역", "인천공항T1")
bus = next(bus for bus in buses if bus.has_seat())

print(bus, bus.price)
print([seat.seat_no for seat in korail.airport_bus.seats(bus) if seat.is_available])
마일리지 · 할인쿠폰 보기
mileage = korail.membership.mileage()
print(mileage.total, "마일리지,", mileage.expiring, "이번 달 소멸 예정")

for entry in mileage.entries:  # 최근 30일의 적립 · 사용 내역
    print(entry.date, entry.movement, entry.amount)  # 20261003 적립 2990

for coupon in korail.membership.coupons():
    print(coupon.title, coupon.valid_until)
기기 프로파일 고정하기 — 여러 번 실행한다면

프로파일마다 합성 Android ID가 하나 생성되며, 같은 프로파일을 쓰는 클라이언트는 그 ID를 재사용합니다. 재시작 후에도 유지하려면 id와 android_id를 함께 저장하세요. pykorail은 파일을 자동으로 읽거나 쓰지 않습니다.

from pykorail.device import profile_by_id, random_profile

profile = profile_by_id(saved_id, android_id=saved_android_id) or random_profile()
# 호출 앱의 설정에 profile.id와 profile.android_id를 함께 저장합니다.
korail = Korail(device_profile=profile)

전체 프로파일을 저장하려면 dataclasses.asdict(profile)을 직렬화한 뒤 DeviceProfile(**saved_data)로 복원할 수도 있습니다. ID는 Android 14의 SSAID 계산 방식과 코레일 공개 서명 인증서로 생성한 합성 값이며, 실제 기기에서 추출한 값은 아닙니다. 프로파일을 생략하면 클라이언트마다 새 ID가 생성됩니다. 이때도 korail.android_id를 저장하고 Korail(android_id=saved_android_id)로 복원할 수 있습니다. 프로파일 유무와 무관하게 User-Agent는 korailtalk입니다.


알아두면 좋은 것

역은 이름으로 "서울역" 이 아니라 "서울". 오타면 요청 전에 막고 비슷한 역을 알려줍니다.
시각은 항상 KST tz 없는 datetime 은 실행 머신의 타임존과 무관하게 한국시간으로 해석됩니다.
지난 시각은 거부 서버가 과거에도 빈 결과만 줘서 "떠난 열차"와 "열차 없음"이 구분되지 않습니다.
실패는 예외로 cancel() · pay() 는 성공 시 아무것도 반환하지 않습니다.
from pykorail import KorailError, SoldOutError

try:
    korail.reservations.create(train)
except SoldOutError:
    ...  # 매진 — 다음 열차로
except KorailError as exc:
    print(exc.msg, exc.code)  # 코레일이 준 메시지와 코드

문서

🌐 문서 사이트 이 문서들을 한곳에서 — 검색 · 다크 모드
📖 API 레퍼런스 전체 메서드 · 모델 · 옵션 · 예외
🤝 기여 가이드 개발 환경, 테스트 규칙, PR 절차
🏛 아키텍처 규약 계층 구조, 외부 API 불변식
🤖 에이전트 설정 Codex · Claude Code · Pi · Cursor · OpenCode
🔐 보안 정책 · 행동 강령

자주 묻는 것

로그인이 안 됩니다

설치할 때 경고가 떴다면 curl_cffi 대신 requests 로 돌고 있을 수 있습니다. 코레일은 TLS 지문을 보기 때문에 이때 로그인이 거부될 수 있습니다. pip install curl_cffi 로 해결되는 경우가 대부분입니다.

휴대폰 번호는 010-1234-5678과 01012345678 모두 입력할 수 있습니다. 클라이언트가 휴대폰 로그인으로 분류하고 하이픈을 제거해 전송합니다.

PasswordChangeRequiredError 가 난다면 아이디 · 비밀번호는 맞습니다. 같은 비밀번호를 6개월 넘게 써서 코레일이 변경 안내를 띄운 상태이고, 그동안은 로그인이 끝나지 않습니다. 코레일톡 앱이나 홈페이지에서 안내를 처리한 뒤 다시 실행하세요.

Windows 에서 설치가 안 됩니다

curl_cffi 의 libcurl DLL 로드가 실패하는 환경이 있습니다. pip install "pykorail[fallback]" 로 requests 폴백을 함께 설치하세요. 다만 위에 적은 이유로 로그인이 막힐 수 있습니다.

SRT 도 되나요?

아니요. SRT(수서고속철도)는 다른 회사의 다른 시스템입니다.

어제까지 되던 게 오늘 안 됩니다

코레일이 서버를 바꿨을 수 있습니다. API 변경 이슈 로 알려주시면 대응하겠습니다. 가장 도움이 되는 기여입니다.

예매가 확실히 되나요?

이 라이브러리는 앱과 같은 요청을 보낼 뿐이고, 좌석 배정은 코레일 서버가 합니다. 명절 예매처럼 경쟁이 심한 상황에서 성공을 보장하지 않습니다.


개발

파이썬 3.11 이상 (3.11 – 3.15 에서 테스트합니다).

uv sync --all-extras
uv run ruff format && uv run ruff check && uv run ty check && uv run pytest

네 개를 전부 통과해야 합니다. 커버리지 하한 90%, 현재 96%.

기여를 환영합니다 — CONTRIBUTING.md 부터 보세요. 새 엔드포인트는 공식 코레일톡+ APK 를 디컴파일해 경로와 폼 필드를 확인한 뒤에만 추가합니다.

릴리스

버전의 유일한 출처는 git 태그입니다. 파일을 손으로 고칠 필요가 없습니다.

git tag v0.2.0 && git push origin v0.2.0

태그를 밀면 전 버전 게이트 → 보안 스캔 → 빌드 → PyPI 업로드 → 릴리스 노트 생성이 자동으로 돕니다.


MIT License · 코레일과 무관한 비공식 프로젝트

Metadata

Release files for pykorail 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pykorail 0.3.0
File Size Uploaded
pykorail-0.3.0.tar.gz 184.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pykorail 0.3.0
File Interpreter ABI Platform
pykorail-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 283.4 kB

Release files / pykorail-0.3.0.tar.gz

Download URL pykorail-0.3.0.tar.gz
Size 184.4 kB
Tags Source
SHA-256 checksum
How to use checksums
856dfbdd39c7d9cd2bcb366cc70b0793bc7dc0e7bcf82cf2c87a4c4a2c779988
BLAKE2b-256 checksum
How to use checksums
89b607490fea92aa141f2c54513509dfbc19c1f7a6d9e88fa24021c11dacd413
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / pykorail-0.3.0-py3-none-any.whl

Download URL pykorail-0.3.0-py3-none-any.whl
Size 99.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
20594698b9ae563b4166a280e211db4ba5f50d5e3d4257f6de1481f376a0370f
BLAKE2b-256 checksum
How to use checksums
187f29bcab37241d54f0672ceb10bd28cef750b63ecc3b2c827ae5e2e5d0587e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

0.3.1

2 release files

This release

0.3.0 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page