Skip to content

Commit ebb060a

Browse files
committed
init
0 parents  commit ebb060a

12 files changed

+345
-0
lines changed

Interpark/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# interpark_ticket_macro
2+
인터파크 뮤지컬 티켓 자동 예매 프로그램
3+
4+
## 기본 설정
5+
6+
1. chromedriver 다운로드
7+
- Chrome 브라우저 version 확인(크롬 브라우저 검색창에 chrome://settings/help 입력)
8+
- version에 맞는 chrome driver 설치(https://chromedriver.chromium.org/downloads)
9+
- 다운받은 chromedriver를 main.py와 같은 directory로 옮기기
10+
11+
2. package 설치
12+
```
13+
$ pip install -r requirements.txt
14+
```
15+
16+
3. settings 파일 설정
17+
```
18+
{
19+
"userid": 인터파크 아이디,
20+
"password": 인터파크 비밀번호,
21+
"reservation_time": 티켓 예매를 수행할 시각 설정. 해당 시간에 예매 동작이 수행됨,
22+
"ticket_code": 뮤지컬 예매 페이지 갔을때 해당 페이지 URL의 ?GoodsCode={code} 혹은 goods/{code} 의 code값,
23+
"ticket_date": 티켓 날짜,
24+
"ticket_type": 좌석 종류(S, R, VIP)
25+
}
26+
```
27+
28+
4. 실행
29+
```
30+
$ python main.py
31+
```
32+
- 정상적으로 수행되면 예약완료 정보가 담긴 scrennshot.png라는 파일이 생성됨
33+
34+
## 옵션
35+
36+
* chrome 브라우저 동작하는거 보려면 코드에서 options.headless = False
37+
38+
## 기타
39+
40+
* CAPTCHA가 있는 뮤지컬의 경우 headless를 True로 하고 직접 입력해주기
926 Bytes
Binary file not shown.
966 Bytes
Binary file not shown.
2.25 KB
Binary file not shown.
3.66 KB
Binary file not shown.

Interpark/captcha.png

24.4 KB
Loading

Interpark/chromedriver.exe

13.6 MB
Binary file not shown.

Interpark/constants.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
URL_MAIN = 'https://accounts.interpark.com/login/form'
2+
URL_TICKET_PAGE = 'https://mobileticket.interpark.com/goods/{}?app_tapbar_state=hide&'
3+
4+
ID_USERID_INPUT = 'userId'
5+
ID_PASSWORD_INPUT = 'userPwd'
6+
ID_LOGIN_BUTTON = 'btn_login'
7+
ID_BUY_BUTTON = 'buyButton'
8+
ID_SEATS_BUTTON = 'step_noti_txt'
9+
10+
CLASS_DATE_CHOICE = 'selectBtn'
11+
CLASS_NEXT_STEP = 'goNextStep'
12+
CLASS_OTHERPAY = 'otherpayWrap'
13+
CLASS_RESERVATION_NUMBER = 'reservationNumber'
14+
CLASS_INPUT_COUNT = 'numberInput'
15+
CLASS_COUNTUP = 'countUp'
16+
CLASS_MONTH = 'monthTxt'
17+
CLASS_CALENDAR_ACTIVE = 'swiper-slide-active'
18+
CLASS_DATE_RIGHT_BUTTON = 'rightBtn'
19+
20+
XPATH_BTN_BANK = '//*[@id="passbook"]/ul/li[6]/label/span' # 신한은행

Interpark/main.py

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import json
2+
import logging
3+
import time
4+
from datetime import datetime, timezone
5+
import urllib.request
6+
import urllib.error
7+
from selenium.webdriver.common.by import By
8+
from selenium.webdriver.common.keys import Keys
9+
from utils import driver_start, input_field, button_click, get_element, confirm_alert
10+
import constants
11+
import os
12+
import cv2 as cv
13+
import numpy
14+
import pytesseract
15+
16+
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
17+
18+
logger = logging.getLogger('log')
19+
logger.setLevel(logging.INFO)
20+
streamHandler = logging.StreamHandler()
21+
logger.addHandler(streamHandler)
22+
23+
def main():
24+
try:
25+
with open("settings.json") as f:
26+
settings = json.load(f)
27+
28+
driver = driver_start()
29+
30+
logger.info('로그인')
31+
input_field(driver, constants.ID_USERID_INPUT, settings['userid'])
32+
input_field(driver, constants.ID_PASSWORD_INPUT, settings['password'])
33+
button_click(driver, By.ID, constants.ID_LOGIN_BUTTON)
34+
driver.get(constants.URL_TICKET_PAGE.format(settings['ticket_code']))
35+
36+
waiting_reservation_time(settings)
37+
38+
# 날짜 선택창으로 이동
39+
button_click(driver, By.ID, constants.ID_BUY_BUTTON)
40+
# confirm_alert(driver)
41+
driver.switch_to.window(driver.window_handles[1])
42+
43+
select_date(driver, settings['ticket_date'], settings['ticket_time'])
44+
45+
if(settings['captcha'] == 'O'):
46+
captcha(driver)
47+
48+
49+
find_seat(driver, settings['ticket_type'], settings['ticket_num'])
50+
logger.info('좌석 가격 선택')
51+
for i in range(int(settings['ticket_num'])):
52+
button_click(driver, By.CLASS_NAME, constants.CLASS_COUNTUP)
53+
btn = get_element(driver, By.CLASS_NAME, constants.CLASS_NEXT_STEP)
54+
if not btn.text == '결제방식 선택':
55+
# 버튼 클릭이 제대로 안된 경우 재시도
56+
time.sleep(1)
57+
button_click(driver, By.CLASS_NAME, constants.CLASS_COUNTUP)
58+
click_next_button(driver)
59+
60+
logger.info('걸제 수단 무통장입금으로 선택')
61+
btn_other_wrapper = get_element(driver, By.CLASS_NAME, constants.CLASS_OTHERPAY)
62+
btn_other = btn_other_wrapper.find_element(By.XPATH, './/div[@role="button"]')
63+
btn_other.send_keys(Keys.ENTER)
64+
65+
logger.info('무통장 입금 은행 신한은행으로 선택')
66+
btn_bank = get_element(driver, By.XPATH, constants.XPATH_BTN_BANK)
67+
driver.execute_script("arguments[0].click();", btn_bank)
68+
# 동의하기
69+
allcheck_wrapper = get_element(driver, By.CLASS_NAME, 'allChk')
70+
allcheck = allcheck_wrapper.find_element(By.XPATH, './/i')
71+
driver.execute_script("arguments[0].click();", allcheck)
72+
73+
logger.info('최종 결제 버튼 클릭')
74+
try:
75+
click_next_button(driver)
76+
get_element(driver, By.CLASS_NAME, constants.CLASS_RESERVATION_NUMBER)
77+
except Exception as error:
78+
# 에러 발생하면 alert 끄고 다시 한번 시도
79+
logger.info("결제 오류로 인한 재시도: {}".format(error))
80+
confirm_alert(driver)
81+
click_next_button(driver)
82+
get_element(driver, By.CLASS_NAME, constants.CLASS_RESERVATION_NUMBER)
83+
84+
logger.info('결과 페이지 다운로드')
85+
driver.save_screenshot("screenshot.png")
86+
87+
except Exception as error:
88+
logger.error("ERROR: {}".format(error))
89+
90+
91+
def waiting_reservation_time(settings):
92+
"""
93+
예약 시간까지 기다리다가 예약시간이 되면 예약 버튼 클릭
94+
"""
95+
# 예약 시간에 맞춰서 동작
96+
reservation_time = settings["reservation_time"]
97+
if reservation_time:
98+
reservation_timestamp = datetime.strptime(reservation_time, '%Y.%m.%d %H:%M:%S').timestamp()
99+
logger.info('예약 대기')
100+
while reservation_timestamp > get_server_time():
101+
time.sleep(1)
102+
logger.info('예약 시작')
103+
104+
105+
def select_date(driver, ticket_date, ticket_time):
106+
"""
107+
티켓 날짜 선택
108+
"""
109+
logger.info('날짜 선택')
110+
try:
111+
month = get_element(driver, By.CLASS_NAME, constants.CLASS_MONTH)
112+
if int(month.text.split('.')[1]) < int(ticket_date.split('.')[1]):
113+
for i in range(int(ticket_date.split('.')[1]) - int(month.text.split('.')[1])):
114+
button_click(driver, By.CLASS_NAME, constants.CLASS_DATE_RIGHT_BUTTON)
115+
time.sleep(0.5)
116+
117+
calendar = get_element(driver, By.CLASS_NAME, constants.CLASS_CALENDAR_ACTIVE)
118+
119+
date_list = calendar.find_elements(By.TAG_NAME, "button")
120+
for date in date_list:
121+
if int(date.text) == int(ticket_date.split('.')[2]):
122+
date.send_keys(Keys.ENTER)
123+
break
124+
125+
get_element(driver, By.CLASS_NAME, constants.CLASS_DATE_CHOICE)
126+
ticket_times = driver.find_elements(By.CLASS_NAME, constants.CLASS_DATE_CHOICE)
127+
ticket_times[int(ticket_time) - 1].send_keys(Keys.ENTER)
128+
# button_click(driver, By.CLASS_NAME, constants.CLASS_DATE_CHOICE)
129+
130+
except Exception as error:
131+
logger.error('Error(select_date): {}'.format(error))
132+
raise Exception("원하는 날짜 선택을 실패했습니다")
133+
134+
135+
def find_seat(driver, seat_type, ticket_num):
136+
"""
137+
seat_type: VIP, R, S
138+
"""
139+
logger.info('좌석 선택')
140+
try:
141+
get_element(driver, By.XPATH, '//a[contains(@title,"{}석")]'.format(seat_type))
142+
seats = driver.find_elements(By.XPATH, '//a[contains(@title,"{}석")]'.format(seat_type))
143+
cnt = 0
144+
for seat in seats:
145+
try:
146+
seat.click()
147+
print('잡음')
148+
cnt += 1
149+
except:
150+
print('놓침')
151+
if(cnt == int(ticket_num)):
152+
break
153+
# button_click(driver, By.XPATH, '//a[contains(@title,"{}석")]'.format(seat_type))
154+
except Exception as error:
155+
logger.error('Error(find_seat): {}'.format(error))
156+
raise Exception("좌석을 찾지 못했습니다")
157+
158+
button_click(driver, By.ID, constants.ID_SEATS_BUTTON)
159+
160+
161+
def click_next_button(driver):
162+
btn_wrapper = get_element(driver, By.CLASS_NAME, constants.CLASS_NEXT_STEP)
163+
btn = btn_wrapper.find_element(By.XPATH, './/a')
164+
driver.execute_script("arguments[0].click();", btn)
165+
166+
167+
def get_server_time():
168+
"""
169+
인터파크 서버 시간 불러오기
170+
"""
171+
date = urllib.request.urlopen(constants.URL_MAIN).headers['Date']
172+
date_time = datetime.strptime(date, '%a, %d %b %Y %H:%M:%S %Z').replace(tzinfo=timezone.utc)
173+
174+
return date_time.timestamp()
175+
176+
177+
def captcha(driver):
178+
"""
179+
캡차 처리
180+
"""
181+
image = get_element(driver, By.ID, 'imgCaptcha')
182+
image = image.screenshot_as_png
183+
with open(os.getcwd() + "\\captcha.png", "wb") as file:
184+
file.write(image)
185+
image = cv.imread(os.getcwd() + "\\captcha.png")
186+
image = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
187+
image = cv.adaptiveThreshold(image, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 85, 1)
188+
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
189+
image = cv.morphologyEx(image, cv.MORPH_OPEN, kernel, iterations=1)
190+
cnts = cv.findContours(image, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
191+
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
192+
for c in cnts:
193+
area = cv.contourArea(c)
194+
if area < 30:
195+
cv.drawContours(image, [c], -1, (0, 0, 0), -1)
196+
kernel2 = numpy.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
197+
image = cv.filter2D(image, -1, kernel2)
198+
result = 255 - image
199+
captcha_text = pytesseract.image_to_string(result)
200+
driver.find_element(By.CLASS_NAME, 'validationTxt').click()
201+
time.sleep(0.5)
202+
driver.find_element(By.ID, 'txtCaptcha').send_keys(captcha_text)
203+
# time.sleep(2)
204+
# if driver.find_element(By.ID, 'imgCaptcha'):
205+
# button_click(driver, By.XPATH, '//*[@id="oneStop-step2"]/div/div[2]/div/div[1]/div/button[1]')
206+
# captcha(driver)
207+
208+
209+
if __name__ == "__main__":
210+
main()

Interpark/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
PyAutoGUI==0.9.48
2+
selenium==3.141.0
3+
psutil==5.6.3
4+
beautifulsoup4==4.9.3

Interpark/settings.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"userid": "jeusda5771",
3+
"password": "data6788!",
4+
"reservation_time": "2023.05.15 17:35:00",
5+
"ticket_code": "23006740",
6+
"ticket_date": "2023.09.23",
7+
"ticket_time" : "2",
8+
"ticket_num" : "2",
9+
"ticket_type": "R",
10+
"captcha" : "O"
11+
}

Interpark/utils.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import time
2+
3+
from selenium import webdriver
4+
from selenium.webdriver.support import expected_conditions as EC
5+
from selenium.webdriver.support.ui import WebDriverWait
6+
from selenium.webdriver.common.keys import Keys
7+
from selenium.webdriver.common.by import By
8+
from selenium.common.exceptions import NoAlertPresentException
9+
10+
import constants
11+
12+
13+
DOM_WAIT_TIMEOUT = 10
14+
15+
def driver_start():
16+
"""
17+
selenium driver 실행
18+
"""
19+
mobile_emulation = {
20+
"deviceMetrics": { "width": 375, "height": 812, "pixelRatio": 3.0 },
21+
"userAgent": "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19"
22+
}
23+
24+
options = webdriver.ChromeOptions()
25+
options.add_experimental_option("mobileEmulation", mobile_emulation)
26+
# 사람처럼 보이게 하는 옵션들
27+
options.add_argument("disable-gpu") # 가속 사용 x
28+
options.add_argument("lang=ko_KR") # 가짜 플러그인 탑재
29+
options.headless = False
30+
31+
driver = webdriver.Chrome(options=options)
32+
driver.get(constants.URL_MAIN)
33+
34+
return driver
35+
36+
37+
def get_element(driver, by, value, timeout=int(DOM_WAIT_TIMEOUT)):
38+
return WebDriverWait(driver, timeout).until(EC.presence_of_element_located((by, value)), 'Timed out waiting for getting element')
39+
40+
41+
def button_click(driver, by, value, timeout=int(DOM_WAIT_TIMEOUT)):
42+
WebDriverWait(driver, timeout).until(EC.element_to_be_clickable((by, value)), 'Timed out waiting for button {}'.format(value)).send_keys(Keys.ENTER)
43+
44+
45+
def input_field(driver, id, input_value):
46+
"""
47+
id의 input Element에 input_value 입력
48+
"""
49+
elem = get_element(driver, By.ID, id)
50+
elem.send_keys(input_value)
51+
if input_value != elem.get_attribute('value'):
52+
time.sleep(2)
53+
54+
55+
def confirm_alert(driver):
56+
time.sleep(2)
57+
try:
58+
driver.switch_to.alert.accept()
59+
except NoAlertPresentException:
60+
pass

0 commit comments

Comments
 (0)