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 ()
0 commit comments