EMD Blog

[Tip] 멜론 콘서트 취소표 알림 받기 (with Python) 본문

기타

[Tip] 멜론 콘서트 취소표 알림 받기 (with Python)

EmaDam 2024. 7. 13. 14:08
반응형

안녕하세요. Emadam입니다.
이번에는 Python을 사용해 멜론 콘서트 취소표 알림받는 법을 포스팅해보려 합니다.
2월 말 즈음 문득 와이프와 아이유 콘서트에 가보고 싶어 네이버에 검색해보니 3월 2일부터 주말마다 콘서트가 있다는 걸 알게 되었습니다.

물론 전부 매진이었습니다. 결국 취소표를 노려야하는 상황인데 멜론은 취소표를 노리는게 다른 플랫폼보다 많이 까다로운 편입니다.
인터파크와 같은 플랫폼은 취소표를 특정 시점에 한번에 풀어서 일명 '취켓팅'이 가능하지만 멜론은 취소표를 랜덤한 시간에 풀어 미리 대기하는 방식이 불가능합니다.
때문에 멜론으로 취소표를 노리려면 새로고침을 열심히 누르는 방법밖에는 없는데, 아이유 콘서트는 인기가 굉장히 많기 때문에 취소표가 잘 나지 않습니다. 사실상 새로고침으로 취소표를 노리는 건 불가능에 가깝습니다.
그럼 어떻게 해야 티켓을 구할 수 있을까요?
사실 전체적인 흐름은 간단합니다. 1. 멜론 홈페이지에서 자리현황을 확인하고 취소표가 발생하면 2. 메신저로 알림을 보내면 됩니다.

자리현황 API 분석

위 흐름을 구현하려면 가장 먼저 자리현황을 확인해야 합니다. 멜론 홈페이지에서는 아래와 같이 새로고침 버튼을 통해 자리현황을 갱신할 수 있습니다.

위 새로고침 버튼은 비동기로 동작하고 있습니다. API를 비동기로 호출해 데이터를 가져와서 리스트를 갱신합니다. 그렇다면 어떤 API로 데이터를 가져오고 있는지 확인해보겠습니다.

총 3가지 Json 데이터를 호출하는 것을 볼 수 있었습니다. 각 데이터의 리소스명과 응답값을 봤을 때 각 데이터의 용도를 아래와 같이 예상할 수 있었습니다.

  • getProdSellState.json -> 잘 모르겠는데.. 확실한 건 좌석정보는 아니였습니다.
  • getAreaMap.json -> 좌석도를 그리는데 사용되는 데이터로 보입니다.
  • summary.json -> callback 함수에 따라 표햔 데이터가 달라지는 것으로 보입니다. getBlockGradeSeatCountCallBack를 callback 함수로 사용할 경우 좌석 요약 데이터를, getBlockSummaryCallBack callback 함수를 사용할 경우 실제 좌석 현황을 보여주는 것으로 보입니다.
    이렇게 봤을 때 활용할 수 있는 데이터는 summary.json으로 보입니다. 또한 getBlockGradeSeatCountCallBack callback 함수에서는 잔여 좌석으로 예상되는 값을 찾을 수 없었기 때문에 getBlockSummaryCallBack을 자세하게 확인해보는 것이 좋아 보입니다.
getBlockSummaryCallBack({
    "summary": [{
        "prodId": 209540,
        "scheduleNo": 100001,
        "blockId": 187,
        "seatGradeNo": 10015,
        "totSeatCntlk": 257,
        "sellSeatCntlk": 199,
        "rendrSeatCntlk": 58,
        "lockSeatCntlk": 4,
        "realSeatCntlk": 54,
        "regUserId": null,
        "regUserName": null,
        "regDate": null,
        "mdfUserId": null,
        "mdfUserName": null,
        "mdfDate": null,
        "seatGradeName": "지정석",
        "gradeColorVal": "#BEA886",
        "floorNo": null,
        "floorName": "",
        "areaNo": "[A GATE]401",
        "areaName": "구역",
        "sntvList": null,
        "sntv": ",[A GATE]401",
        "blockTypeCode": "SE0001",
        "perfStartDay": "20240414",
        "perfStartTime": "1400",
        "basePrice": 110000,
        "sejongSeatGradeCode": null
    },

    ...         

    ],
    "interlockTypeCode": "",
    "code": "0000",
    "staticDomain": null,
    "httpsDomain": null,
    "httpDomain": null
});

좌석의 영역 리스트와 summary내 배열 내용이 일치한 것을 확인했습니다. 그리고 각 배열 내 Object의 key 중 realSeatCntlk의 값이 잔여좌석의 값과 일치하는 것을 확인할 수 있었습니다. 대략 아래와 같은 값으로 예상했습니다.

  • totSeatCntlk -> 전체 좌석 수
  • sellSeatCntlk -> 판매된 좌석 수
  • rendrSeatCntlk -> 팔리지 않은 좌석 수
  • lockSeatCntlk -> 잠긴 좌석 수 (구매 진행중이라 잠긴 좌석으로 예상됩니다.)
  • realSeatCntlk -> 남은 좌석 수

남은 좌석을 확인하는 방법은 알아냈지만 남들보다 빠르게 좌석을 낚아채려면 정확한 좌석위치까지 알림으로 알려주는게 좋을 것 같습니다. 다행히 이 데이터 내에는 각 영역에 대한 설명도 적혀있습니다.

  • seatGradeName -> 좌석 등급명 (가격별로 분류된 영역 / ex R석(3회차))
  • floorNo -> 층 수 (ex 1, 2, 3, 4)
  • floorName -> 층 명 (ex 층)
  • areaNo -> 구역 번호 (ex A, B, 1, 2, 3)
  • areaName -> 구역 명 (ex 구역)
    위 값은 아래와 같이 활용해주시면 됩니다.
- {seatGradeName}, {floorNo}{floorName} {areaNo}{areaName}에 잔여좌석 {realSeatCntlk}개 발생
위 처럼 변수를 사용하면 아래와 같이 출력됩니다.
- R석(3회차), 1층 5구역에 잔여좌석 2개 발생

이제 API에서 필요한 정보는 모두 확인한 것 같습니다. 그럼 이제 이 API를 활용해서 잔여좌석을 확인하는 코드를 작성해보겠습니다.

잔여좌석 체크하기

이제 Python을 사용해 좌석 정보를 받아와 잔여좌석을 확인해보겠습니다.
좌석 데이터를 받아오는 가장 간단한 방법은 위에서 살펴본 API를 똑같이 사용하는 것입니다.
먼저 어떻게 API에 요청을 보내는지 살펴보겠습니다.

요청
요청 헤더
페이로드

뭐가 많지만 사실 모든 값이 필요하지는 않습니다. 테스트 결과 아래 정도의 값들만 필요했습니다.

  • header -> Accept, Content-Type, Cookie, Host, Referer, User-Agent
  • body -> prodId, pocCode, scheduleNo, perfDate, seatGradeNo, corpCodeNo
  • parameter -> v

header의 경우 Cookie와 User-Agent를 신경써주어야 합니다.

  • Cookie -> 처음 좌석선택 팝업을 띄웠을 때 나오는 자동 입력 방지 문자를 입력하면 생성되는 것으로 보입니다. 이 쿠키는 사용하지 않으면 만료되기 때문에 가장 주의해야 합니다. 이 값은 웹페이지 개발자 도구에서 복사해서 사용해야 합니다.
  • User-Agent -> Python 기본 User-Agent는 차단이 되어 있는 것 같았습니다. User-Agent를 다른 임의의 값으로 변경해서 해결할 수 있었습니다.

Body의 경우 위에 6가지 키를 나열했는데 콘서트별로 사용하는 키가 다릅니다. 원하는 콘서트의 Body를 꼭 확인해주세요. Parameter에는 꼭 callback을 지정해주지 않아도 됩니다. Default으로 반환하는 데이터가 getBlockSummaryCallBack 함수를 callback으로 지정했을 때와 동일했습니다.

그럼 본격적으로 Python 코드를 작성해보겠습니다. Python에서 외부 API에 요청을 보내려면 requests라고 하는 라이브러리를 사용해야 합니다.

먼저 requests 라이브러리를 Import 합니다

import requests

그 다음 위에서 확인한 정보를 바탕으로 Melon 좌석 현황 API를 호출합니다.

def get_seats_summary() -> None:
    url = "https://ticket.melon.com/tktapi/product/block/summary.json?v=1" 

    body = {
        'prodId': '',
        'pocCode': '',
        'scheduleNo': '',
        'perfDate': '',
        'seatGradeNo': '',
        'corpCodeNo': ''
    }

    header = {
        'Accept': 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'Cookie': '',
        'Host': 'ticket.melon.com',
        'Referer': 'https://ticket.melon.com/reservation/popup/stepBlock.htm',
        'User-Agent': 'X'
    }

    response = requests.post(url,headers=header,data=body)
    return response.json()

위에서 body 값과 Cookie 값만 채워주면 좌석정보를 가져오게됩니다.

좌석 데이터를 받아왔다면 이 데이터에서 realSeatCntlk 값을 확인해 잔여좌석이 남아있는지 확인해야합니다.

def check_remaining_seats(seats: list) -> list:
    result = []

    for seat in seats:
        if seat['realSeatCntlk'] > 0:
            print('남은 좌석 발생!')

# 위 함수는 아래처럼 호출합니다.
# 먼저 좌석 데이터를 가져오고
seats = get_seats_summary() 

# 좌석 데이터에서 summary(좌석 배열) 값을 잔여좌석 체크 함수의 매개변수로 넘깁니다.
check_remaining_seats(seats['summary']) 

남은 좌석을 체크하기는 했지만 어디서 몇개의 자리가 발생했는지는 알 수가 없습니다. 다행히 seats 요소에는 좌석 영역에 대한 상세한 정보가 포함되어 있어 더 구체적인 메세지 작성이 가능합니다.

# 메세지 출력 시 generate_message 함수를 호출하도록 변경합니다.
def check_remaining_seats(seats: list) -> list:
    result = []

    for seat in seats:
        if seat['realSeatCntlk'] > 0:
            print(generate_message(seat))

# 정확히 어느 위치에 몇 자리가 발생했는지 메세지를 작성해서 반환합니다.
def generate_message(seat: dict) -> str: 
    return seat['seatGradeName'] + ", " + seat['floorNo'] + seat['floorName'] + " " + seat['areaNo'] + seat['areaName'] + "에 잔여좌석 " + str(seat['realSeatCntlk']) + "개 발생! "

Slack으로 알림보내기

좌석체크를 할 수 있게 되었지만 매번 화면을 들여다보고 있을 수는 없습니다. 좌석이 생겼을 때 Slack과 같은 도구로 알림을 받을 수 있다면 좋을 것 같습니다. 좌석이 발생했을 때 Slack Webhook URL로 메세지를 보내도록 구성해보겠습니다. (Slack 계정이 있다고 가정하겠습니다.)
 
먼저 Apps 페이지로 이동 후 우측 상단에 Create New App을 클릭합니다.

그러면 App을 어떤 방식으로 생성할 것인지 선택하는 모달창이 출력됩니다. 특별한 설정이 없는 App이라 수동으로 구성해도 문제가 없을 것 같습니다. From scratch를 선택합니다.

적절하게 App Name을 선택한 뒤 Workspace를 선택해줍니다. (만약 Workspace가 없다면 새로 생성해주세요.)

App을 생성했다면 우측 메뉴에서 Incoming Webhooks를 클릭한 뒤 우측의 Activate Incoming Webhooks을 On으로 변경해줍니다.

그 다음 페이지 맨 아래로 이동해 Add New Webhook to Workspace 버튼을 클릭합니다.

그러면 Workspace 내 채널에 대한 액세스 권한을 요청합니다. 알림을 받고자 하는 채널을 선택 후 허용해줍니다. (채널이 없다면 생성해주세요.)

이제 Webhook URL이 생성되었습니다. 이제 이 Webhook URL을 사용해 잔여좌석 알림을 받을 수 있습니다.

아래와 같이 코드를 추가합니다.

# 메세지를 출력하는 대신 배열에 저장한 뒤 반환합니다.
def check_remaining_seats(seats: list) -> list:
    result = []

    for seat in seats:
        if seat['realSeatCntlk'] > 0:
            result.append(generate_message(seat))

    return result

# 메세지 목록을 받아 Slack Webhook URL로 메세지를 전달합니다.
def send_message(messages: list) -> None:
    slack_webhook_url = ""
    for message in messages:
        response = requests.post(slack_webhook_url, json={'text' : message})

# 위 함수는 아래처럼 호출합니다.
# 좌석 데이터를 받아옵니다.
seats = get_seats_summary()

# 좌석 중 잔여 좌석 정보에 대한 메세지 배열을 받아옵니다.
messages = check_remaining_seats(seats['summary'])

# 메세지 목록을 매개변수로 전달합니다.
send_message(messages)

crontab을 사용해 자동으로 확인하기

알림도 받을 수 있게 되었지만 현재 상태라면 Melon 홈페이지에서 새로고침 버튼을 누르는 것과 크게 다르지 않습니다. (스크롤하면서 빈 좌석을 확인하지 않아도 되는 점이 다르겠네요.)
crontab으로 스케줄링을 걸어 주기적으로 확인하게끔 설정한다면 편할 것 같습니다.
MacOS에서는 간단하게 crontab을 설정할 수 있습니다. 먼저 crontab 설정 파일을 오픈합니다.

$ crontab -e 

그 다음 아래와 같이 내용을 작성후 저장합니다.

* * * * * <PYTHON_PATH> <PYTHON_FILE_PATH>

PYTHON_PATH에는 python 바이너리 파일의 전체 경로를 입력합니다. PYTHON_FILE_PATH에는 실행할 파일 경로를 작성해주세요.
위 설정(* * * * *)은 매 분마다 파이선 파일을 실행하게 합니다. 이 간격은 더 늘릴 수 있는데 티켓팅 특성상 주기가 짧을수록 좋기 때문에 매분으로 설정하는 것이 좋습니다. (다른 주기로 설정하고 싶다면 이 링크에서 쉽게 표현식을 만들 수 있습니다.)

 
그런데 사실 인기가 많은 콘서트의 경우 1분으로는 부족할 수 있습니다. 제가 노렸던 아이유 콘서트의 경우 잔여좌석 발생 후 2~8초 사이에 매진이 됐기 때문에 주기를 더 짧게 설정해줘야 하지만 crontab은 최소 분단위만 지원합니다.

이런 경우 python의 time 라이브러리의 sleep 기능을 사용하면 간단하게 해결할 수 있습니다. 예시로 2초마다 실행하도록 코드를 작성해보겠습니다.

import time

def main() -> None:
    for i in range(30):
        seats = get_seats_summary()
        messages = check_remaining_seats(seats['summary'])
        send_message(messages)
        time.sleep(2)

...

crontab 주기인 1분 동안 잔여좌석 체크 함수를 30번 실행시키고 잔여좌석을 한번 체크할 때마다 2초씩 정지합니다. 이렇게 하면 1분동안 2초간격으로 함수를 실행하게 됩니다.
 
여태까지 진행한 전체코드는 Github에 배포해두었으니 참고해주세요!
- ldy9037/melon-ticket-alert

마무리

이렇게 Python, Slack, crontab을 사용해 Melon 콘서트 취소표를 체크하는 프로그램을 만들어 보았습니다. 만들기 전에는 잘될까 싶었는데 만들고 나니 생각보다 잘 작동해서 만족스러웠던 프로그램이었습니다. 이 프로그램으로 꼭 티켓을 구매해서 블로그에 인증하고 싶었는데.. 1인 1매 한정이라 중간에 포기해서 많이 아쉬웠습니다. 다른 분이라도 꼭 원하는 콘서트 티켓을 얻어가시길 바라겠습니다.
감사합니다!
 

반응형