νμ΄μ¬μΌλ‘ KTX νλ₯Ό μ‘°ννκ³ μλ§€ν©λλ€
μ½λ μΌ μ€λ§νΈ μλ§€(μ½λ μΌν‘) API λ₯Ό κ°μΌ λΉκ³΅μ ν΄λΌμ΄μΈνΈ
λΉ λ₯Έ μμ Β· ν μ μλ κ² Β· API λ νΌλ°μ€ Β· κΈ°μ¬νκΈ°
pip install pykorailfrom datetime import datetime
from pykorail import Korail
with Korail.logged_in("me@example.com", "password") as korail:
trains = korail.trains.search("μμΈ", "λΆμ°", depart_after=datetime(2026, 4, 1, 9, 0))
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λΆ)
Warning
μ½λ μΌκ³Ό μ무 κ΄λ ¨ μλ λΉκ³΅μ λΌμ΄λΈλ¬λ¦¬μ λλ€. λ¬Έμνλμ§ μμ μ± API λ₯Ό μ°κΈ° λλ¬Έμ μ½λ μΌμ΄ μ±μ λ°κΎΈλ©΄ μκ³ μμ΄ λ©μΆ μ μμ΅λλ€. μ΄μ© μ½κ΄κ³Ό κ΄λ ¨ λ²λ Ήμ μ§ν€λ κ²μ μ¬μ©μ μ± μμ΄λ©°, κ³Όλν μλ μμ²μ κ³μ μ μ¬λ‘ μ΄μ΄μ§ μ μμ΅λλ€.
from pykorail import AdultPassenger, Card, ChildPassenger
trains = korail.trains.search(
"μμΈ",
"λΆμ°",
depart_after=datetime(2026, 4, 1, 9, 0),
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(2026, 4, 1, 9, 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λ§€μ§μΌ λ μμ½λκΈ° κ±ΈκΈ°
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) # νλΆκΈ°κΈ° νλ‘νμΌ κ³ μ νκΈ° β μ¬λ¬ λ² μ€ννλ€λ©΄
νλ‘νμΌλ§λ€ ν©μ± 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 | datetime(2026, 4, 1, 9) λ μλ²κ° μ΄λ νμμ‘΄μ΄λ νκ΅μκ° μ€μ 9μ. |
| μ§λ μκ°μ κ±°λΆ | μλ²κ° κ³Όκ±°μλ λΉ κ²°κ³Όλ§ μ€μ "λ λ μ΄μ°¨"μ "μ΄μ°¨ μμ"μ΄ κ΅¬λΆλμ§ μμ΅λλ€. |
| μ€ν¨λ μμΈλ‘ | 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 λͺ¨λ μ
λ ₯ν μ μμ΅λλ€.
ν΄λΌμ΄μΈνΈκ° ν΄λν° λ‘κ·ΈμΈμΌλ‘ λΆλ₯νκ³ νμ΄νμ μ κ±°ν΄ μ μ‘ν©λλ€.
Windows μμ μ€μΉκ° μ λ©λλ€
curl_cffi μ libcurl DLL λ‘λκ° μ€ν¨νλ νκ²½μ΄ μμ΅λλ€.
pip install "pykorail[fallback]" λ‘ requests ν΄λ°±μ ν¨κ» μ€μΉνμΈμ.
λ€λ§ μμ μ μ μ΄μ λ‘ λ‘κ·ΈμΈμ΄ λ§ν μ μμ΅λλ€.
SRT λ λλμ?
μλμ. SRT(μμκ³ μμ² λ)λ λ€λ₯Έ νμ¬μ λ€λ₯Έ μμ€ν μ λλ€.
μ΄μ κΉμ§ λλ κ² μ€λ μ λ©λλ€
μ½λ μΌμ΄ μλ²λ₯Ό λ°κΏ¨μ μ μμ΅λλ€. API λ³κ²½ μ΄μ λ‘ μλ €μ£Όμλ©΄ λμνκ² μ΅λλ€. κ°μ₯ λμμ΄ λλ κΈ°μ¬μ λλ€.
μλ§€κ° νμ€ν λλμ?
μ΄ λΌμ΄λΈλ¬λ¦¬λ μ±κ³Ό κ°μ μμ²μ λ³΄λΌ λΏμ΄κ³ , μ’μ λ°°μ μ μ½λ μΌ μλ²κ° ν©λλ€. λͺ μ μλ§€μ²λΌ κ²½μμ΄ μ¬ν μν©μμ μ±κ³΅μ 보μ₯νμ§ μμ΅λλ€.
νμ΄μ¬ 3.10 μ΄μ (3.10 β 3.14 μμ ν μ€νΈν©λλ€).
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 Β· μ½λ μΌκ³Ό 무κ΄ν λΉκ³΅μ νλ‘μ νΈ