매크로 패치 이후 안심예매에서 취켓팅에 실패했다는 사례가 많이 보여서 안내한다.

 

안심예매는 기본적으로 코드를 계속 입력해줘야 하기 때문에, 몇시간동안 돌리면서 취소표를 찾아내는 취켓팅은 거의 불가능에 가깝다.

 

안타깝지만, 안심예매에 대한 취켓팅 기능은 구현하기 힘들 것 같다. 물론 캡차 보안이 세지는 않아서, opencv와 테서랙트를 이용하면 보안문자를 알아내는 것도 가능하겠지만, 이는 엄연한 불법에 해당하기 때문에 시도하지 않는다.

 

 

드디어 블로그 방문자 수가 만명을 톨파했다. 5000명을 돌파한 지가 얼마 되지 않았는데, 벌써 만명이다. 아무래도 티켓팅 매크로의 영향이 큰 듯 하다. 앞으로도 계속 열심히 활동해야겠다.

'잡담' 카테고리의 다른 글

블로그 방문자 수 10000명 돌파  (0) 2020.08.27

2년간 해왔던 파이썬은 슬슬 그만두고 다른 언어로 가야지.... 생각하다가 드디어 Java로 넘어가는 것을 결정했다.

 

아무래도 우리나라에서 제일 중심적인 언어이고, 많이 쓰이기 때문에 자바로 결정했다.

 

자바가 끝나면 아마 C, C++ 순서대로 이어나가지 않을까 생각한다. 

 

그래서 아마 인터파크 매크로같은 경우에는 지금까지 보고된 문제만 해결하고 기능 업데이트는 힘들 수 있을 것 같다.

'프로그래밍 > Java' 카테고리의 다른 글

Java 공부 시작  (0) 2020.08.14

인터파크 티켓팅 매크로를 내 GitHub Repository에 업로드하였다.

 

라이선스는 PyQt에 따라 GPL V3.0에 따라 배포되며, 그냥 일반 사용자들은 막 가져다가 빌드해서 쓰면 된다.

 

라이선스 때문에 PySide로 변경할 생각도 하고 있긴 하지만, 귀찮음이....

 

당연하지만 상업적인 재배포는 금지다. 

 

이제부터 프로그램에 버그가 있거나 수정할 점이 있을 경우 직접 Pull Request를 생성하고 기여해 주시면 고마울 것 같다.

 

https://github.com/kustar103/Interpark-Ticketing-Macro

 

kustar103/Interpark-Ticketing-Macro

Interpark ticketing macro with selenium. Contribute to kustar103/Interpark-Ticketing-Macro development by creating an account on GitHub.

github.com

  1. 문의 2020.08.05 17:46

    exe파일로 올려주시면 안될까요 ㅜ ㅜ 부탁드립니다 ㅠㅠ

    • 페이지다운 2020.08.05 23:24 신고

      이건 원래 있던 걸 깃헙에 올린 겁니다. 빌드 파일은 https://pagedown.n-e.kr/13 이곳에서 다운받으실 수 있습니다

이번에 내 인터파크 매크로 코드를 보면 try ~ except 구문으로 떡칠되어 있는 모습을 볼 수 있다.

 

아마도 아래와 같은 형태일 것이다.

1
2
3
4
5
6
7
while True:
    try:
        pass # 실행할 구문
    except:
        continue
    else:
        break
cs

셀레니움은 웹페이지가 다 로딩되기 전에 요소를 찾으려고 하면 없다고 예외를 내뿜으면서 꺼진다. 

 

그러나 로딩 속도는 사용자 컴퓨터 성능이나 인터넷 속도 영향을 많이 받고, 때에 따라 다르기 때문에 항상 언제 정확히 몇초 후에 로딩이 될 것이라고 예측할 수가 없다. 

 

보통은 Explicit Waits을 쓰면 되지만, 그래도 불확실성이 완전히 해소된다는 보장도 없고, 내가 써 보니 잘 안되서 위와 같은 형태를 취하게 되었다.

 

해석하자면 일단 실행할 구문을 무한 루프 안에 가둬 놓고 예외가 나지 않을 때까지(셀레니움에서는 로딩이 끝나 정상적으로 요소를 찾을 수 있을 때까지) 실행한다. 그리고 예외 없이 정상적으로 실행되면 루프를 나간다.

 

그리고 몇 번 실행하고 더 이상 안되면 꺼버리고 싶을 때, 아래와 같이 for 구문을 사용하면 된다.

1
2
3
4
5
for i in range(0, time):
    try:
        pass
    except:
        continue
cs

무식한 방법처럼 보이지만, 제일 간단하고 정확하다. 오히려 될 때까지 시도한다는 점 때문에 일정 시간이 지나면 포기하는 Explicit Waits와는 다르게 어떻게든 예매창에 들어가기만 하면 좌석을 알아서 선택하고 나오게 된다.

 

사실 이 방법을 쓰기 전에 고민했는데, 이걸 쓰면 코드가 너무 지저분해지기 때문이다. 당장 구문만 빼고 들어가는 줄 수만 봐도 6줄이나 된다. 이게 몇개씩이나 있어서 코드 가독성을 해친다.

 

그냥 생각하기 싫을 때나 어떤 일이 있어도 절대로 프로그램이 종료되지 말아야 하는 경우에 쓸 수는 있어도, 가급적이면 다른 방법을 사용하는 것을 권한다.

지난번에 매크로를 올리고 정말 많은 오류가 보고되었다.

 

이렇게 복잡한 프로그램을 만들고 배포하긴 처음이라, 많이 당황했다.

 

거의 4개월만에 오류 패치를 했다. 물론 중간고사 끝나고 바로 기말고사라 급하게 해서 몇개 부탁주신 기능 구현은 하지 못했다.

 

그래서 고쳐진 부분과 추가된 부분에 대해 설명을 하고자 한다.

 

패치된 오류

안심예매 단계에서 꺼짐

회차가 두 개 이상일 경우 직접 선택해야 함

구역이 나눠져 있을 경우 작동 안됨

 

새로 바뀐 매크로

바뀐 매크로는 위와 같다.

 

여기서 추가된 부분은 회차와 구역 분리 항목이다.

 

회차의 경우에는 두 개 이상이 있는 경우가 있다.

 

이럴 경우 회차마다 순서대로 001, 002, 003... 이렇게 올라간다.

 

만약 두번째 회차를 원한다면 002라 적으면 된다(반드시 002여야 한다, 2 안됨, 02 안됨).

 

구역 분리같은 경우에 티켓팅할 공연장의 규모가 커서 구역을 선택하는 곳이 나올 때 이용할 수 있다.

 

구역 선택은 직접 해야 하며, 선택만 하면 그 다음은 알아서 한다.

 

아래는 빌드(exe) 파일과 코드이다. 코드는 직접 올리기 번거로워서 파일 형식으로 올렸다.

 

소스 코드 파일에는 pyinstaller 빌드를 위한 .spec 파일도 포함되어 있다.

 

*추가

연령 제한 알림 같은 안내가 있는 경우 수동으로 알림을 꺼야 하는 문제를 해결해 자동으로 끄게 했다.

 

*추가

JS alert가 뜨는 경우 알아서 끌 수 있게 변경했다.

 

빌드 파일

 

38.62 MB file on MEGA

 

mega.nz

소스 코드

 

6.4 KB file on MEGA

 

mega.nz

 

  1. 이전 댓글 더보기
  2. 2020.07.14 20:46

    비밀댓글입니다

  3. 2020.07.15 11:57

    비밀댓글입니다

    • 페이지다운 2020.07.15 18:35 신고

      제가 보기엔 다음날 하는 공연 때문에 뜨는 알림창 때문에 수동으로 선택한 것 같습니다. 그 부분은 수정하겠습니다.

  4. ㅇㅇ 2020.07.15 20:13

    로그인을 누르면 응용 프로그램이 종료되는데 해결방법 아시나요...?

    • 페이지다운 2020.07.15 20:15 신고

      확인하겠습니다.

    • 사람1 2020.07.16 23:08

      저도 그랬는데 본인 컴퓨터에 설치된 크롬 버전이랑 크롬 드라이버 버전이랑 안 맞으면 그렇더라구요. 본인 컴퓨터에 설치된 크롬 버전 확인하시고 거기에 맞는 크롬 드라이버 다운받으시면 잘 돼요

  5. 제크맛집 2020.07.16 09:08

    공연코드는 어디서 확인하나요?

  6. 2020.07.17 16:20

    취켓팅 모드는 정확히 어떻게 하는 건가요?

    • 페이지다운 2020.07.17 16:55 신고

      https://pagedown.n-e.kr/8 참고하세요

    • 2020.07.17 17:42

      제가 이상한건지 잘 안 되는 거 같네요 ㅠㅠ

  7. ㅇㅇ 2020.07.17 17:17

    안심예매 공연에서는 회차 설정이 풀리는 것 같습니다.

  8. 111 2020.07.18 00:32

    선생님 덕분에 티케팅 성공했습니다..감사합니다...... 적게 일하시고 많이 버세요..

  9. ㅇㅇ 2020.07.18 23:54

    패치버전 다운이 안됩니다 ㅠㅠ

  10. 멜멜 2020.07.18 23:58

    패치파일 다운이 안되는데요 ㅠㅠ

  11. 1122 2020.07.23 21:35

    우선 공유 감사합니다. 별다른 오류 없이 실행은 잘 되는 것 같은데, 프로그램이 좌석을 선택하는 기준은 어떻게 되는지 알 수 있을까요?

    • 페이지다운 2020.07.23 23:58 신고

      좌석은 HTML 엘리먼트를 기준으로 제일 위에 있는 것을 고릅니다. 이게 불규칙적이라, 지금 그 엘리먼트에 열이나 등급이 표시된 title을 기반으로 정확한 좌석을 추적하는 기능을 준비하고 있습니다.

  12. 꽁수니 2020.07.30 02:35

    제가 티켓팅이 잘되본적이 없어서
    하고 싶은데 너무 컴알못이라
    설치하고 실험용으로 다른 여유있는 공연 설정해서 해봤는데 프로그램이 꼼짝도 안하더라구요
    원인을 모르겠어서 흑흑
    도움좀 받을수있을까요?

  13. 2020.07.31 01:29

    비밀댓글입니다

  14. ㅇㅇ 2020.08.04 14:33

    자리 널널한 공연으로 시험해보면서 회차 입력해봤는데 하루에 회차가 두개 이상이라서 그런지 001 입력해도 날짜까지만 가더라고요 직접 선택해야하는게 맞나요?

  15. 2020.08.06 11:18

    비밀댓글입니다

  16. ㅇㅇ 2020.08.07 02:41

    취켓팅 모드에서 이미 선택된 좌석 팝업창이 뜨면 자동으로 창 닫은 후 새로고침이 멈추는데 이 경우 프로그램 재시작해야 하나요??? 이선좌 창 닫고 계속 새로고침이 가능하면 좋을 것 같아요!!

  17. 희희 2020.08.17 02:57

    덕분에 얼마전에 티켓팅 성공했습니다ㅠㅠㅠㅠㅠ선생님 진짜 절받으세요ㅠㅠㅠㅠㅠ
    어느 방향에 계신 지 몰라서 사방으로 절하겠습니다ㅠㅠㅠㅠㅠ감사합니다 정말ㅠㅠ

  18. 2020.08.26 14:18

    비밀댓글입니다

  19. 디노 2020.08.26 14:37

    먼저 좋은 프로그램 공유 감사드립니다.
    취겟팅시 구역 분리를 선택해도 구역 선택할 타이밍 없이 새로고침이 되버리는것 같은데요.
    (새로 고침 되기전에 구역 선택해도 다음번에 구역선택없이 새로고침됨)
    혹시 제가 잘못 사용하는 것일까요?

  20. 2020.09.04 13:04

    비밀댓글입니다

    • 페이지다운 2020.09.08 23:39 신고

      아직 좌석 등급을 구분하는 기능을 구현하지 못했습니다. 완전한 랜덤이며 이는 수정할 예정입니다.

  21. 2020.09.15 23:57

    비밀댓글입니다

오늘 인터파크 티켓팅 매크로 패치를 시작하면서 공지할 점이 있어 글을 쓰게 되었다.

 

많은 분들이 오류를 겪고 댓글로 알려 주시고 있는데, 안심예매 정도야 문제를 구현할 수 있으나 일부 내가 경험하기 힘든 문제가 있다.

 

뭐든지 문제를 고치기 위해선 문제를 내가 직접 겪어 봐야만 알 수 있다.

 

그래서 가급적 문제를 보고할 때는 어느 공연에서 문제가 발생했는지, 팝업이 떴으면 어떤 팝업이 떴는지에 대한 내용도 같이 알려 주시면 좋겠다.

 

 

지난번 글에서 인터파크 티켓팅 매크로 코드를 공개했는데, 보면 프로그램의 기능을 담당하는 .py 스크립트와 UI를 담당하는 .ui 파일이 분리되어 있는 것을 알 수 있다.

 

나는 보통 pyuic를 통해 .ui 파일에서 .py로 스크립트를 변경해 썼다. 

 

이 방법이 여러모로 편리하지만, 원치 않는 찌거기같은 코드가 마구 딸려와서 코드가 지저분해지는 경향이 있었고, UI의 변경이 어려웠다. 난 코드로 UI 디자인을 거의 못하기 때문에 Qt Designer을 쓰는데, 그런 나에겐 최악의 상황이다.

 

따라서 uic.loadUiType을 통해 ui를 따로 로드해서 프로그램을 만들어 보기로 했고 이 티켓팅 매크로가 그 첫 시도의 결과물이다.

 

ui를 따로 로드했을 때의 단점은 텍스트 에디터에서 보조를 받기 쉽지 않다는 점과, pyinstaller로 빌드 시 파일을 포함해서 빌드하지 않는다는 점이다. 따라서 .py 스크립트만 빌드하면 반드시 ui 파일이 따라다녀야 한다. 코드 공개를 원치 않는 사람에게는 별로 좋지 못하다. 그리고 파일을 한꺼번에 합치고 싶은 사람에게도 별로 좋지 못하다.

 

그래서 방법을 알아보다가 찾아서 공유한다.

 

일단 기본적으로 아래 코드를 최상단에 추가한다. 

1
2
3
4
5
6
7
import sys
import os
 
def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)
cs

stack overflow에서 구한 코드다. 정확히 무슨 역할을 하는지는 모르겠지만 아마도 바이너리 파일 내의 파일을 일반적인 파일처럼 열 수 있도록 해주는 코드 같다.

 

그러고 나서는 이제 pyinstaller에게 ui파일을 포함하도록 알려 줘야 하는데, spec 파일이 그것이다. spec 파일은 프로그램 빌드 시 다양한 옵션을 줄 수 있도록 하는 파일이다. 먼저 .py 스크립트만 빌드를 하면 spec 파일이 자동으로 생성된다. 그러면 그걸 수정하고 다시 빌드하면 된다.

 

우리는 ui 파일만 포함시키면 되므로, 아래와 같이 해주면 된다

1
2
3
4
a = Analysis(
    datas=[ ('TicketingMacro.ui', '.') ],
    ...
    )
cs

그러고 난 후 다시 원래 .py 스크립트로 돌아가서

1
2
3
form = resource_path('TicketingMacro.ui')
 
form_class = uic.loadUiType(form)[0
cs

위와 같이 resource_path 함수 안에 .ui파일을 적어 주고 반환값을 uic.loadUiType에다가 넘겨 주면 된다.

  1. helpi 2020.08.14 14:58

    안녕하세요 좋은글 감사드립니다.

    이전(후) 티켓팅 관련 배포파일 잘 보았습니다. 티케팅목적은 아니고 ui파일과 py 파일을 이용하여 exe파일을 만드는 것을 공부하다가 발견하였는데요.
    ui파일을 py파일로 만들지 않고
    pyinstaller를 이용하여 exe파일을 만드려고 하였으나 자꾸 창이 나타나지 않고 바로 꺼지니 이게 무슨이유인가 싶어서 올려주신 파일도 해보았으나 바로 꺼지길래 exe파일로 만드는 과정에서 문제가 있나 해서 질문드립니다.
    올려주신 TicketingMacro 소스파일 3개(ui, spec, py)를 이용해 pyinstaller를 사용하였는데 해당 문구는 다음과 같았습니다.
    pyinstaller --onefile TicketingMacro.py
    이동 경로를 제대로 하고 시도하였는데 exe실행 시 창이 뜨지 않고 바로 종료되었습니다. pyinstaller 시도 당시에 어떻게 해야 해당 세 소스파일을 제대로 빌드 할 수 있을까요??

    • 페이지다운 2020.08.14 16:36 신고

      .py파일 말고 .spec 파일로 빌드해야 합니다. pyinstaller --windowed -F TicketingMacro.spec 이렇게 하시면 됩니다.

  2. helpi 2020.08.19 15:49

    안녕하세요 바로 위에 댓글 올렸던 사람입니다.

    파이썬으로 파일 하나 만들어보겠다고 이곳저곳 끙끙댔는데 문제점에 대해서 알려주셔서 처음으로 프로그램 만들어 보았습니다.
    spec파일로 빌드하는 것, spec 파일에 data부분에 ui를 추가하는 것 두개가 핵심이었네요
    덕분에 PC로 실행하는 첫 프로그램 잘 실행되게 만들었습니다. 도움 주셔서 정말 감사드립니다. 항상 좋은일만 가득하시길 바라겠습니다.

    실행파일만이 아니라 소스파일(py, ui, spec)파일 같이 올려주셔서 감사합니다 덕분에 공부에 큰 도움되었습니다.

  3. lia 2020.09.10 16:45

    안녕하세요.
    저도 계속 ui 파일을 py 파일로 변경하는게 번거로워서 알아보던 중 요 글 보고 큰 도움 받아 해결하였습니다!
    너무 감사드립니다 ^^

  4. s_i 2020.09.18 02:05

    안녕하세요. 저도 올려주신 코드를 보고 이리저리 바꿔서 좀 더 공부해보려고 해당 파일을 수정해 빌드해보았는데요. 위에 질문처럼 exe파일을 만들어 실행하였으나 저는 해당 파일이 여전히 바로 종료되어서 궁금해서 질문 남깁니다. dist 폴더 안에 들어있는 파일을 실행시키는 것이 맞는지요? 처음엔 py파일로 빌드 후, spec 파일로 빌드하는 것이 맞나요? 계속 헤매다가 질문 남깁니다. 감사합니다.

    • 페이지다운 2020.09.18 17:23 신고

      처음에 py로 빌드하고 생성된 spec파일을 수정해 그걸로 빌드하면 됩니다. 만약 오류가 나면서 바로 꺼진다면 pyqt가 문제일 수 있으므로 재설치하고 시도해 보시기 바랍니다

이번엔 인터파크 티켓팅 매크로에 대한 코드를 공유하겠다. 자유롭게 수정해서 쓰고, 재배포 시 출처를 명확히 표기할 경우 허용하지만, 상업적 용도로 내 코드 전체 또는 일부를 사용하는 것은 금지다.

 

일반 버전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#인터파크 티켓팅 매크로
#만든 이 : PageDown
#https://pagedown.n-e.kr/
 
 
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from selenium import webdriver
 
form_class = uic.loadUiType('TicketingMacro.ui')[0
 
 
class MyWindow(QtWidgets.QMainWindow, form_class):
    is_data_disabled = False  # 정보 입력창 비활성화 여부
    time = None  # 스레드로부터 받아오는 현재 시간
 
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.user_password_input.setEchoMode(QtWidgets.QLineEdit.Password)
        self.seats_number_spinbox.setValue(1)
        self.date_edit.setDate(QtCore.QDate.currentDate())
        self.time_edit.setTime(QtCore.QTime.currentTime())
        self.disableElements(self.time_edit, self.label_auto_start)
 
        self.apply_btn.clicked.connect(self.initData)  # 확인 버튼 클릭
        self.login_btn.clicked.connect(self.startLogin)  # 로그인 버튼 클릭
        self.start_ticketing_btn.clicked.connect(self.startTicketing)  # 시작 버튼 클릭
        self.seats_number_spinbox.valueChanged.connect(self.checkSeatsNumber)  # 좌석 개수 변경
        self.use_auto_start.stateChanged.connect(self.useAutoStart)  # 자동 시작 여부 변경
 
    def initData(self):
        if not self.is_data_disabled:  # 정보 입력창 활성화 시
            self.user_id = self.user_id_input.text()  # 아이디
            self.user_password = self.user_password_input.text()  # 비밀번호
            self.product_code = self.product_code_input.text()  # 공연 코드
            self.seats_number = self.seats_number_spinbox.value()  # 좌석 개수
            self.use_random_seat = self.use_seat_select.isChecked()  # 중간(랜덤) 선택 여부
            self.is_canceled_ticketing = self.canceled_ticket_mode.isChecked()  # 취켓팅 여부
            if self.use_auto_start.isChecked():  # 자동 시작 시 시간 24시간 방식 -> 12시간 방식 변경
                self.time = self.time_edit.time()
                self.time = str(self.time.toPyTime())[:6+ '00'
                if int(self.time[0:2]) > 12:
                    self.new_time_h = int(self.time[0:2]) - 12
                    self.time = str(self.new_time_h) + self.time[2:]
                    if self.new_time_h < 10:
                        self.time = '0' + self.time
                elif self.time[0:2== '00':
                    self.time = '12' + self.time[2:]
            self.date = self.date_edit.date()  # 날짜
            self.date = self.date.toPyDate()
            self.date = str(self.date).replace('-''')
            self.disableElements(self.user_id_input, self.user_password_input, self.product_code_input, self.date_edit,
                                 self.seats_number_spinbox, self.use_seat_select, self.use_auto_start, self.time_edit, self.canceled_ticket_mode)  # 요소 비활성화
            self.is_data_disabled = True
            self.apply_btn.setText('수정')
            QtWidgets.QMessageBox.information(self, '완료''정보 입력이 완료되었습니다.')
        else:
            self.is_data_disabled = False
            self.apply_btn.setText('확인')
            self.enableElements(self.user_id_input, self.user_password_input, self.product_code_input, self.date_edit,
                                self.seats_number_spinbox, self.use_seat_select, self.use_auto_start, self.time_edit, self.canceled_ticket_mode)  # 요소 활성화
 
    # 좌석 개수 변경
    def checkSeatsNumber(self):
        if self.seats_number_spinbox.value() > 4:
            self.seats_number_spinbox.setValue(4)
            QtWidgets.QMessageBox.critical(self, '오류''최대 좌석 개수는 4개입니다.')
        if self.seats_number_spinbox.value() < 1:
            self.seats_number_spinbox.setValue(1)
            QtWidgets.QMessageBox.critical(self, '오류''최소 좌석 개수는 1개입니다.')
 
    # 요소들 입력받아 한꺼번에 활성화
    def enableElements(self, *elements):
        for element in elements:
            element.setEnabled(True)
 
    # 요소들 입력받아 한꺼번에 비활성화
    def disableElements(self, *elements):
        for element in elements:
            element.setEnabled(False)
 
    # 자동시작 시
    def useAutoStart(self):
        if self.use_auto_start.isChecked():
            self.enableElements(self.time_edit, self.label_auto_start)
        else:
            self.time = None
            self.disableElements(self.time_edit, self.label_auto_start)
 
    # 로그인
    def startLogin(self):
        self.time_th = TimeThread()
        if self.apply_btn.text() == '확인':  # 확인 버튼을 누르지 않았을 때
            QtWidgets.QMessageBox.critical(self, '오류''좌석 정보를 입력하세요.')
        else:
            # Dictionary 형식으로 정보 전달
            ticketing_data_to_send = {
                'user_id': self.user_id,  # 아이디
                'user_pw': self.user_password,  # 비밀번호
                'product_code': self.product_code,  # 공연 번호
                'date': self.date,  # 공연 날짜
                'time': self.time,  # 자동시작 시간
                'seats_number': self.seats_number,  # 좌석 개수
                'use_random_seat': self.use_random_seat,  # 랜덤 선택 여부
                'time_signal': self.time_th.time_signal,  # 시간 스레드
                'is_canceled': self.is_canceled_ticketing  # 취켓팅 여부
            }
            self.apply_btn.setEnabled(False)  # 확인(수정) 버튼 비활성화
            self.login_btn.setEnabled(False)  # 로그인 버튼 비활성화
            # 시간/티켓팅 스레드 시작
            self.time_th.start()
            self.time_th.time_signal.connect(self.changeTime)
            self.ticketing_th = TicketingThread(ticketing_data=ticketing_data_to_send)
            self.ticketing_th.start()
 
    # 티켓팅 시작
    def startTicketing(self):
        self.ticketing_th.start_ticketing = True
 
    # 타이머 시작
    def startTimer(self):
        self.time_th = TimeThread()
        self.time_th.start()
        self.time_th.time_signal.connect(self.changeTime)
 
    @QtCore.pyqtSlot(str)
    def changeTime(self, time):
        if time == 'error':  # 웹드라이버(시계) 종료시 알림
            QtWidgets.QMessageBox.critical(self, '오류''타이머를 끄지 마십시오.')
        else:
            self.now_time.setText(time)
 
 
class TimeThread(QtCore.QThread):
    time_signal = QtCore.pyqtSignal(str)
 
    def run(self):
        while True:
            self.driver = webdriver.Chrome('chromedriver.exe')
            #네이버 시계 접속
            self.driver.get('https://search.naver.com/search.naver?sm=tab_hty.top&where=nexearch&query=%EB%84%A4%EC%9D%B4%EB%B2%84+%EC%8B%9C%EA%B3%84&oquery=%EC%8B%9C%EA%B3%84&tqi=UEMiLdprvTossFQzFhCssssssKG-165498')
            while True:
                try:
                    text = self.driver.find_element_by_css_selector('#_cs_domestic_clock > div._timeLayer.time_bx > div > div').text  # 네이버 시계 text
                    text = text.replace('\n''').replace(' ''')[0:8]
                except:
                    self.time_signal.emit('error')  # 웹드라이버(시계) 종료 시 error emit
                    break
                self.time_signal.emit(''.join(text))
 
 
class TicketingThread(QtCore.QThread):
    def __init__(self, ticketing_data, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.user_id = ticketing_data['user_id']
        self.user_password = ticketing_data['user_pw']
        self.product_code = ticketing_data['product_code']
        self.date = ticketing_data['date']
        self.set_time = ticketing_data['time']
        self.seats_number = ticketing_data['seats_number']
        self.use_random_seat = ticketing_data['use_random_seat']
        self.time_signal = ticketing_data['time_signal']
        self.is_canceled_ticketing = ticketing_data['is_canceled']
        self.driver = self.driver = webdriver.Chrome('chromedriver.exe')
        self.time_signal.connect(self.checkTime)  # 시간 스레드와 연결(자동시작)
        self.is_logined = False  # 로그인 여부 False
        self.start_ticketing = False  # 시작 여부 False
 
    # 요소는 반드시 CSS 셀렉터로만(ID 우선)
 
    # 로그인
    def login(self):
        self.driver.get('https://ticket.interpark.com/Gate/TPLogin.asp')
        iframes = self.driver.find_elements_by_tag_name('iframe')
        self.driver.switch_to.frame(iframes[0])
        self.driver.find_element_by_id('userId').send_keys(self.user_id)
        self.driver.find_element_by_id('userPwd').send_keys(self.user_password)
        self.driver.find_element_by_id('btn_login').click()
        self.driver.get('https://ticket.interpark.com/')  # 인터파크 메인 페이지로 강제 접속
 
    def selectSeat(self):
        self.failed_to_get_ticket = False  # 취켓팅용-기본적으로 성공으로 표시
        # 직링 생성
        self.url = 'http://poticket.interpark.com/Book/BookSession.asp?GroupCode={}&Tiki=N&Point=N&PlayDate={}&PlaySeq=001&BizCode=&BizMemberCode='.format(self.product_code, self.date)
        self.driver.get(self.url)
 
        # 요소를 찾을 때까지 무한 루프(찾지 못하면 예외)
        while True:
            try:
                first_iframe = self.driver.find_element_by_id('ifrmSeat')  # 첫번째 아이프레임
            except:
                continue
            else:
                break
        self.driver.switch_to.frame(first_iframe)
        while True:
            try:
                next_iframe = self.driver.find_element_by_id('ifrmSeatDetail')  # 두번째 좌석선택 아이프레임
            except:
                continue
            else:
                break
        self.driver.switch_to.frame(next_iframe)
 
        self.loop_time = 0  #취켓팅용
        while True:
            try:
                elements = self.driver.find_elements_by_class_name('stySeat')
                self.loop_time += 1
                # 취켓팅하면서 루프를 100번 돌면 루프 탈출
                if self.is_canceled_ticketing and (self.loop_time > 100):
                    self.failed_to_get_ticket = True  # 잔여 좌석 인식 실패(개수 0)
                    break
            except:
                continue
            else:
                if len(elements) == 0:  # 예외는 나지 않았지만 좌석이 인식되지 못한 경우
                    continue
                else:
                    break
 
        while len(elements) > 0:
            # 첫번째부터 1개 선택
            if (not self.use_random_seat) and (self.seats_number == 1):
                self.driver.find_element_by_css_selector('#TmgsTable > tbody > tr > td > img:nth-child(3)').click()  # 바로 첫번째 요소 선택
 
            # 여러개 선택
            elif not self.use_random_seat:
                seat_count = 0
                for element in elements:
                    if seat_count < self.seats_number:
                        element.click()
                        seat_count += 1
 
            # 랜덤/여러개 선택
            elif self.use_random_seat:
                start_seat = random.randint(
                    int(len(elements) / 2), len(elements) - self.seats_number)
                for i in range(start_seat, start_seat + self.seats_number):
                    if start_seat < self.seats_number + start_seat:
                        elements[i].click()
            try:
                self.driver.switch_to.default_content()  # 제일 바깥으로 아이프레임 벗어남
                self.driver.switch_to.frame(first_iframe)  # 첫번째 아이프레임으로 다시 변경
                self.driver.find_element_by_id('NextStepImage').click()  # 다음 버튼 클릭
                self.driver.find_element_by_class_name('title')  # 임의의 요소 찾아서 크롬 종료 방어
            except:
                continue
            else:
                break
 
    @QtCore.pyqtSlot(str)
    def checkTime(self, data):
        self.time = data
 
    def run(self):
        # 로그인되지 않았다면 로그인
        if not self.is_logined:
            self.login()
            self.is_logined = True
        while True:
            if str(self.set_time) == str(self.time):  # 입력한 시간하고 일치한다면
                self.start_ticketing = True
            if self.start_ticketing:
                self.selectSeat()  # 티켓팅 시작
                if not self.is_canceled_ticketing:  # 취켓팅이 아니고 끝난 경우는 성공으로 처리
                    self.start_ticketing = False
                if not self.failed_to_get_ticket:  # 취켓팅인데 성공한 경우
                    self.start_ticketing = False
                if self.is_canceled_ticketing and self.failed_to_get_ticket:  # 취켓팅인데 실패한 경우
                    pass
 
 
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()
 
cs

일반 버전 ui 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>TicketingMacro</class>
 <widget class="QMainWindow" name="TicketingMacro">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>304</width>
    <height>429</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>인터파크 티켓팅 매크로</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="label_title">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>인터파크 티켓팅 매크로</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="basic_data_box">
      <property name="title">
       <string>기본 정보</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_4">
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_0">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QLabel" name="label_id">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>아이디:</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="user_id_input"/>
         </item>
         <item>
          <widget class="QLabel" name="label_pw">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>비밀번호:</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="user_password_input"/>
         </item>
        </layout>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizentalLayout_1">
         <property name="sizeConstraint">
          <enum>QLayout::SetDefaultConstraint</enum>
         </property>
         <item>
          <widget class="QLabel" name="label_pd_code">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>공연코드: </string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="product_code_input"/>
         </item>
         <item>
          <widget class="QLabel" name="label_date">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>날짜: </string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QDateEdit" name="date_edit"/>
         </item>
        </layout>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_2">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QLabel" name="label_seat_num">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>좌석매수(최대 4개): </string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QSpinBox" name="seats_number_spinbox"/>
         </item>
        </layout>
       </item>
       <item>
        <widget class="QCheckBox" name="use_seat_select">
         <property name="text">
          <string>중간부터 선택(랜덤)</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="auto_start_box">
      <property name="title">
       <string>시간 설정</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_3">
       <item>
        <widget class="QLabel" name="label_manual">
         <property name="text">
          <string>*로그인을 클릭하면 자동으로 시계가 시작합니다.
*오후 말고 항상 오전만을 선택하세요.</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QCheckBox" name="use_auto_start">
         <property name="text">
          <string>자동 시작 기능 사용</string>
         </property>
        </widget>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_3">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QTimeEdit" name="time_edit"/>
         </item>
         <item>
          <widget class="QLabel" name="label_auto_start">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>에 티켓팅을 자동 시작합니다.</string>
           </property>
          </widget>
         </item>
        </layout>
       </item>
       <item>
        <widget class="QLabel" name="now_time">
         <property name="font">
          <font>
           <pointsize>20</pointsize>
          </font>
         </property>
         <property name="text">
          <string>00 : 00 : 00</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="canceled_ticket_box">
      <property name="title">
       <string>취켓팅</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_5">
       <item>
        <widget class="QCheckBox" name="canceled_ticket_mode">
         <property name="text">
          <string>취켓팅 모드</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="apply_btn">
      <property name="text">
       <string>확인</string>
      </property>
     </widget>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_4">
      <item>
       <widget class="QPushButton" name="login_btn">
        <property name="text">
         <string>로그인</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="start_ticketing_btn">
        <property name="text">
         <string>시작</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <action name="action">
   <property name="text">
    <string>고급 설정</string>
   </property>
  </action>
  <action name="action_2">
   <property name="text">
    <string>도움말</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>
 
cs

라이트 버전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#인터파크 티켓팅 매크로 Light
#만든 이 : PageDown
#https://pagedown.n-e.kr/
 
 
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from selenium import webdriver
 
form_class = uic.loadUiType('TicketingMacro_Light.ui')[0
 
 
class MyWindow(QtWidgets.QMainWindow, form_class):
    is_data_disabled = False  # 정보 입력창 비활성화 여부
    time = None  # 스레드로부터 받아오는 현재 시간
 
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.user_password_input.setEchoMode(QtWidgets.QLineEdit.Password)
        self.seats_number_spinbox.setValue(1)
        self.date_edit.setDate(QtCore.QDate.currentDate())
        self.time_edit.setTime(QtCore.QTime.currentTime())
        self.disableElements(self.time_edit, self.label_auto_start)
 
        self.apply_btn.clicked.connect(self.initData)  # 확인 버튼 클릭
        self.login_btn.clicked.connect(self.startLogin)  # 로그인 버튼 클릭
        self.start_ticketing_btn.clicked.connect(self.startTicketing)  # 시작 버튼 클릭
 
        self.seats_number_spinbox.valueChanged.connect(self.checkSeatsNumber)  # 좌석 개수 변경
        self.use_auto_start.stateChanged.connect(self.useAutoStart)  # 자동 시작 여부 변경
 
    def initData(self):
        if not self.is_data_disabled:  # 정보 입력창 활성화 시
            self.user_id = self.user_id_input.text()  # 아이디
            self.user_password = self.user_password_input.text()  # 비밀번호
            self.product_code = self.product_code_input.text()  # 공연 코드
            self.seats_number = self.seats_number_spinbox.value()  # 좌석 개수
            self.use_random_seat = self.use_seat_select.isChecked()  # 중간(랜덤) 선택 여부
            if self.use_auto_start.isChecked():  # 자동 시작 시 시간 24시간 방식 -> 12시간 방식 변경
                self.time = self.time_edit.time()
                self.time = str(self.time.toPyTime())[:6+ '00'
                if int(self.time[0:2]) > 12:
                    self.new_time_h = int(self.time[0:2]) - 12
                    self.time = str(self.new_time_h) + self.time[2:]
                    if self.new_time_h < 10:
                        self.time = '0' + self.time
                elif self.time[0:2== '00':
                    self.time = '12' + self.time[2:]
            self.date = self.date_edit.date()  # 날짜
            self.date = self.date.toPyDate()
            self.date = str(self.date).replace('-''')
            self.disableElements(self.user_id_input, self.user_password_input, self.product_code_input, self.date_edit,
                                 self.seats_number_spinbox, self.use_seat_select, self.use_auto_start, self.time_edit)  # 요소 비활성화
            self.is_data_disabled = True
            self.apply_btn.setText('수정')
            QtWidgets.QMessageBox.information(self, '완료''정보 입력이 완료되었습니다.')
        else:
            self.is_data_disabled = False
            self.apply_btn.setText('확인')
            self.enableElements(self.user_id_input, self.user_password_input, self.product_code_input, self.date_edit,
                                self.seats_number_spinbox, self.use_seat_select, self.use_auto_start, self.time_edit)  # 요소 활성화
 
    # 좌석 개수 변경
    def checkSeatsNumber(self):
        if self.seats_number_spinbox.value() > 4:
            self.seats_number_spinbox.setValue(4)
            QtWidgets.QMessageBox.critical(self, '오류''최대 좌석 개수는 4개입니다.')
        if self.seats_number_spinbox.value() < 1:
            self.seats_number_spinbox.setValue(1)
            QtWidgets.QMessageBox.critical(self, '오류''최소 좌석 개수는 1개입니다.')
 
    # 요소들 입력받아 한꺼번에 활성화
    def enableElements(self, *elements):
        for element in elements:
            element.setEnabled(True)
 
    # 요소들 입력받아 한꺼번에 비활성화
    def disableElements(self, *elements):
        for element in elements:
            element.setEnabled(False)
 
    # 자동시작 시
    def useAutoStart(self):
        if self.use_auto_start.isChecked():
            self.enableElements(self.time_edit, self.label_auto_start)
        else:
            self.time = None
            self.disableElements(self.time_edit, self.label_auto_start)
 
    # 로그인
    def startLogin(self):
        self.time_th = TimeThread()
        if self.apply_btn.text() == '확인':  # 확인 버튼을 누르지 않았을 때
            QtWidgets.QMessageBox.critical(self, '오류''좌석 정보를 입력하세요.')
        else:
            # Dictionary 형식으로 정보 전달
            ticketing_data_to_send = {
                'user_id': self.user_id,  # 아이디
                'user_pw': self.user_password,  # 비밀번호
                'product_code': self.product_code,  # 공연 번호
                'date': self.date,  # 공연 날짜
                'time': self.time,  # 자동시작 시간
                'seats_number': self.seats_number,  # 좌석 개수
                'use_random_seat': self.use_random_seat,  # 랜덤 선택 여부
                'time_signal': self.time_th.time_signal  # 시간 스레드
            }
            self.apply_btn.setEnabled(False)  # 확인(수정) 버튼 비활성화
            self.login_btn.setEnabled(False)  # 로그인 버튼 비활성화
            # 시간/티켓팅 스레드 시작
            self.time_th.start()
            self.time_th.time_signal.connect(self.changeTime)
            self.ticketing_th = TicketingThread(ticketing_data=ticketing_data_to_send)
            self.ticketing_th.start()
 
    # 티켓팅 시작
    def startTicketing(self):
        self.ticketing_th.start_ticketing = True
 
    # 타이머 시작
    def startTimer(self):
        self.time_th = TimeThread()
        self.time_th.start()
        self.time_th.time_signal.connect(self.changeTime)
 
    @QtCore.pyqtSlot(str)
    def changeTime(self, time):
        if time == 'error':  # 웹드라이버(시계) 종료시 알림
            QtWidgets.QMessageBox.critical(self, '오류''타이머를 끄지 마십시오.')
        else:
            self.now_time.setText(time)
 
 
class TimeThread(QtCore.QThread):
    time_signal = QtCore.pyqtSignal(str)
 
    def run(self):
        while True:
            self.driver = webdriver.Chrome('chromedriver.exe')
            self.driver.get('https://search.naver.com/search.naver?sm=tab_hty.top&where=nexearch&query=%EB%84%A4%EC%9D%B4%EB%B2%84+%EC%8B%9C%EA%B3%84&oquery=%EC%8B%9C%EA%B3%84&tqi=UEMiLdprvTossFQzFhCssssssKG-165498')
            while True:
                try:
                    text = self.driver.find_element_by_css_selector('#_cs_domestic_clock > div._timeLayer.time_bx > div > div').text  # 네이버 시계 text
                    text = text.replace('\n''').replace(' ''')[0:8]
                except:
                    self.time_signal.emit('error')  # 웹드라이버(시계) 종료 시 error emit
                    break
                self.time_signal.emit(''.join(text))
 
 
class TicketingThread(QtCore.QThread):
    def __init__(self, ticketing_data, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.user_id = ticketing_data['user_id']
        self.user_password = ticketing_data['user_pw']
        self.product_code = ticketing_data['product_code']
        self.date = ticketing_data['date']
        self.set_time = ticketing_data['time']
        self.seats_number = ticketing_data['seats_number']
        self.use_random_seat = ticketing_data['use_random_seat']
        self.time_signal = ticketing_data['time_signal']
        self.driver = self.driver = webdriver.Chrome('chromedriver.exe')
        self.time_signal.connect(self.checkTime)  # 시간 스레드와 연결(자동시작)
        self.is_logined = False  # 로그인 여부 False
        self.start_ticketing = False  # 시작 여부 False
 
    # 요소는 반드시 CSS 셀렉터로만(ID 우선)
 
    # 로그인
    def login(self):
        self.driver.get('https://ticket.interpark.com/Gate/TPLogin.asp')
        iframes = self.driver.find_elements_by_tag_name('iframe')
        self.driver.switch_to.frame(iframes[0])
        self.driver.find_element_by_id('userId').send_keys(self.user_id)
        self.driver.find_element_by_id('userPwd').send_keys(self.user_password)
        self.driver.find_element_by_id('btn_login').click()
        self.driver.get('https://ticket.interpark.com/')  # 인터파크 메인 페이지로 강제 접속
 
    def selectSeat(self):
        # 직링 생성
        self.url = 'http://poticket.interpark.com/Book/BookSession.asp?GroupCode={}&Tiki=N&Point=N&PlayDate={}&PlaySeq=001&BizCode=&BizMemberCode='.format(
            self.product_code, self.date)
        self.driver.get(self.url)
 
        # 요소를 찾을 때까지 무한 루프(찾지 못하면 예외)
        while True:
            try:
                first_iframe = self.driver.find_element_by_id('ifrmSeat')  # 첫번째 아이프레임
            except:
                continue
            else:
                break
        self.driver.switch_to.frame(first_iframe)
        while True:
            try:
                next_iframe = self.driver.find_element_by_id('ifrmSeatDetail')  # 두번째 좌석선택 아이프레임
            except:
                continue
            else:
                break
        self.driver.switch_to.frame(next_iframe)
 
        while True:
            try:
                elements = self.driver.find_elements_by_class_name('stySeat')
            except:
                continue
            else:
                if len(elements) == 0:  # 예외는 나지 않았지만 좌석이 인식되지 못한 경우
                    continue
                else:
                    break
 
        while len(elements) > 0:
            # 첫번째부터 1개 선택
            if (not self.use_random_seat) and (self.seats_number == 1):
                self.driver.find_element_by_css_selector('#TmgsTable > tbody > tr > td > img:nth-child(3)').click()  # 바로 첫번째 요소 선택
 
            # 여러개 선택
            elif not self.use_random_seat:
                seat_count = 0
                for element in elements:
                    if seat_count < self.seats_number:
                        element.click()
                        seat_count += 1
 
            # 랜덤/여러개 선택
            elif self.use_random_seat:
                start_seat = random.randint(
                    int(len(elements) / 2), len(elements) - self.seats_number)
                for i in range(start_seat, start_seat + self.seats_number):
                    if start_seat < self.seats_number + start_seat:
                        elements[i].click()
            try:
                self.driver.switch_to.default_content()  # 제일 바깥으로 아이프레임 벗어남
                self.driver.switch_to.frame(first_iframe)  # 첫번째 아이프레임으로 다시 변경
                self.driver.find_element_by_id('NextStepImage').click()  # 다음 버튼 클릭
                self.driver.find_element_by_class_name('title')  # 임의의 요소 찾아서 크롬 종료 방어
            except:
                continue
            else:
                break
 
    @QtCore.pyqtSlot(str)
    def checkTime(self, data):
        self.time = data
 
    def run(self):
        # 로그인되지 않았다면 로그인
        if not self.is_logined:
            self.login()
            self.is_logined = True
        while True:
            if str(self.set_time) == str(self.time):  # 입력한 시간하고 일치한다면
                self.start_ticketing = True
            if self.start_ticketing:
                self.selectSeat()  # 티켓팅 시작
                self.start_ticketing = False  # 성공으로 처리
 
 
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()
 
cs

라이트 버전 ui 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>TicketingMacro_Light</class>
 <widget class="QMainWindow" name="TicketingMacro_Light">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>304</width>
    <height>375</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>인터파크 티켓팅 매크로 Light</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="label_title">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>인터파크 티켓팅 매크로</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="basic_data_box">
      <property name="title">
       <string>기본 정보</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_4">
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_0">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QLabel" name="label_id">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>아이디:</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="user_id_input"/>
         </item>
         <item>
          <widget class="QLabel" name="label_pw">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>비밀번호:</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="user_password_input"/>
         </item>
        </layout>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizentalLayout_1">
         <property name="sizeConstraint">
          <enum>QLayout::SetDefaultConstraint</enum>
         </property>
         <item>
          <widget class="QLabel" name="label_pd_code">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>공연코드: </string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="product_code_input"/>
         </item>
         <item>
          <widget class="QLabel" name="label_date">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>날짜: </string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QDateEdit" name="date_edit"/>
         </item>
        </layout>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_2">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QLabel" name="label_seat_num">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>좌석매수(최대 4개): </string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QSpinBox" name="seats_number_spinbox"/>
         </item>
        </layout>
       </item>
       <item>
        <widget class="QCheckBox" name="use_seat_select">
         <property name="text">
          <string>중간부터 선택(랜덤)</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="auto_start_box">
      <property name="title">
       <string>시간 설정</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_3">
       <item>
        <widget class="QLabel" name="label_manual">
         <property name="text">
          <string>*로그인을 클릭하면 자동으로 시계가 시작합니다.
*오후 말고 항상 오전만을 선택하세요.</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QCheckBox" name="use_auto_start">
         <property name="text">
          <string>자동 시작 기능 사용</string>
         </property>
        </widget>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_3">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QTimeEdit" name="time_edit"/>
         </item>
         <item>
          <widget class="QLabel" name="label_auto_start">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>에 티켓팅을 자동 시작합니다.</string>
           </property>
          </widget>
         </item>
        </layout>
       </item>
       <item>
        <widget class="QLabel" name="now_time">
         <property name="font">
          <font>
           <pointsize>20</pointsize>
          </font>
         </property>
         <property name="text">
          <string>00 : 00 : 00</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="apply_btn">
      <property name="text">
       <string>확인</string>
      </property>
     </widget>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_4">
      <item>
       <widget class="QPushButton" name="login_btn">
        <property name="text">
         <string>로그인</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="start_ticketing_btn">
        <property name="text">
         <string>시작</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <action name="action">
   <property name="text">
    <string>고급 설정</string>
   </property>
  </action>
  <action name="action_2">
   <property name="text">
    <string>도움말</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>
cs

지난편 저 글에서 인터파크 티켓팅 매크로를 제작하고 공유는 안 한다고 했는데, 개선판 작업한 후 나만 쟁여두긴 아까워서 공유해 보기로 했다. 코드는 좀 더 손보고 GitHub라든지 공유할 생각이며 아직은 실행 파일만 공유하겠다.

 

일난 지난번 버전보다 정말 많은 기능이 추가되었다.

 

기본적으로 있던 기능에 좌석 매수 설정, 중간부터 선택, 자동 시작, 취켓팅 모드가 추가되었다. 

 

원래 있던 기능부터 추가한 것까지 상세히 알아보자.

 

1. 자동 로그인 기능. 자신의 인터파크 아이디와 비밀번호를 입력하면 알아서 로그인해 준다. 

2. 직링 기능. 직링 생성 기능이 탑재되어 있어서 공연 코드와 공연 날짜를 입력하면 알아서 창을 띄워 준다.

3. 좌석 매수 선택 기능. 좌석 매수를 설정할 수 있다. 다만 사재기를 통한 암표 판매를 방지하기 위해 개수는 최대 4개로 제한했다.

4. 중간부터 선택(랜덤) 기능. 중간 이후로부터 랜덤으로 좌석을 선택해 준다. 많은 매크로들이 첫번째 자리에 몰리는 만큼 방어를 위해 넣은 기능이지만 속도가 느릴 수 있다. 엄청 느린 정도까진 아니다.

5. 자동 시작 기능. 자동 시작 기능을 넣든 말든 시간이 표시되는 기능과, 원하면 시작 시간을 설정해서 자동으로 시작하도록 할 수있다. 네이버 시계와 동기화를 한다.

6. 취켓팅 기능. 취소된 티켓을 계속 접속하면서 찾는 기능이다.

 

그럼 본격적으로 사용법을 알아보자.

 

일단 이 매크로를 쓰기 전에 필요한 준비물이 있다.

 

1. Chrome. 크롬이 필요하다. 오직 크롬만 지원한다. 이 매크로는 일반적인 클릭 매크로와 달리 브라우저를 직접 제어하기 때문에 제일 보편적인 브라우저인 크롬을 사용했다. 크롬 다운로드는 https://www.google.com/intl/ko/chrome/ 여기서 하자.

2. ChromeDriver. 크롬 드라이버가 필요하다. 이건 내 프로그램이 크롬을 제어할 수 있도록 해주는 프로그램이다. 이게 없으면 아예 매크로를 사용할 수 없다. 크롬드라이버는 아무거나 막 까는게 아니라 자기 크롬과 일치하는 버전의 드라이버를 써야 한다. 자기 크롬의 버전을 알고 싶다면 크롬 주소창에 chrome://version 을 입력하자. 그러면 아래 사진과 같은 창이 뜰 텐데 거기서 빨간색 박스 안의 숫자, 즉 제일 앞의 . 직전 숫자가 일치하는 버전을 다운받아야 한다. 다운로드는 https://chromedriver.chromium.org/downloads 이 곳에서 할 수 있다. 

 

크롬 버전

보통 최신 버전은 아래 사진처럼 친절하게 무엇을 다운로드해야 하는지 안내해 준다.

 

크롬 드라이버

만약 구형 버전이라 여기 없다면 아래로 스크롤하면 다른 버전도 나와 있다. 나는 81버전이므로 81.0.4044.69를 클릭해서 들어가면 아래와 같은 화면이 나타날 것이다.

크롬드라이버

그러면 우리는 다 윈도우 사용자이므로(내가 Mac이나 Linux 버전으로는 프로그램을 빌드하지 않았다) 아래의 chromedriver_win32.zip를 다운로드해주면 된다.

 

그리고 다운로드하면 .zip 압축 파일이 다운로드될 텐데 그곳 안에 있는 chromedriver.exe 파일을 매크로와 동일한 위치로 복사하면 끝이다.

 

나중에 매크로를 사용할 때 제대로 동작하지 않고 오류를 일으킬 수 있는데 그럴 경우에는 크롬이 업데이트되면서 크롬드라이버와 버전이 일치하지 않아 생기는 오류일 수 있으므로 확인해 보자.

 

이러면 준비는 끝이다. 이제 본격적인 사용법으로 들어가 보자.

 

아이디와 비밀번호 입력에선 딱히 설명할 게 없다. 다만 인터파크 계정이 미리 실명인증되어 있어야 한다.

 

공연 코드는 아래 사진과 같이 공연 정보 페이지 맨 위 주소창에 있는 링크에서 ?GoodsCode= 뒤에 있는 8자리 숫자를 말한다. 뒤에 #같은게 붙어 있을 수 있는데 그건 입력하면 안 된다.

공연 코드

날짜 설정은 공연 날짜를 말한다. 이건 모두 알 것이므로 넘어간다.

 

마찬가지로 좌석 개수도 넘어가겠다.

 

중간부터 선택 기능은 좀 고민을 해야 한다. 중간 이후의 랜덤한 좌석을 골라 주는 기능인데, 인터파크의 좌석 예매 특성상 색상에 따라 좌석 name에 특별한 구분을 두지 않았다(여기서 name은 HTML에서의 name이다). 따라서 모든 좌석이 다 동일하게 취급되기 때문에, 보라색 좌석을 고르지 못할 수도 있다. 또 랜덤 연산하는 시간 때문에 느려질 수 있다. 뭐 그렇다고 엄청 느려지는 정도까진 아니다.

 

시간 설정도 별로 할 말이 없다. 자기가 자동 시작을 사용할 거면 '자동 시작 기능 사용'에 체크해 놓고 시작 시간에 시간을 맞춰 놓으면 된다. 근데 나만 그런지는 모르겠지만, 이걸 하면 티켓팅 시 렉을 먹었다. 이건 컴퓨터마다 다를 수 있으므로 자기가 직접 써보고 판단해야 할 듯 하다.

 

취켓팅 모드는 체크해 놓으면 주기적으로 돌면서 취소된 티켓(남는 좌석)이 있는지 확인하고, 있으면 체크한다. 당연하게도 취켓팅 시에는 좌석 개수는 1개, 중간부터 선택에 체크하면 안 된다.

 

그러한 입력이 끝나면 확인을 눌러 정보를 저장하면 된다.

 

그러면 버튼이 수정으로 바뀌는데, 로그인 전까진 정보를 수정할 수 있다.

 

그리고 로그인을 하면 수정 버튼과 로그인 버튼이 비활성화된다. 더 이상 정보를 수정할 수 없으며, 잘못한 게 있다면, 프로그램을 끄고 다시 해야 한다. 그러니 정보 제대로 확인하자. 로그인하다가 오류나면 껐다가 다시 켜야 한다.

 

로그인 버튼을 누르면 크롬 창 두개가 나타날 것이다. 하나는 네이버 시계, 하나는 인터파크다. 실수로라도 끄지 말자. 끄면 시계의 경우 다시 나타나지만, 인터파크의 경우 꺼버리면 프로그램을 재실행해야 한다. 그리고 검은색 바탕의 프롬프트 창이 나타나는데 그것도 절대 끄면 안된다. 그게 크롬드라이버다.

 

여기까지 아무 문제가 없었다면 이제 결전의 순간을 기다리면 된다. 자동 시작을 걸어놨다면 자동으로 시작할 때까지 기다리고, 직접 시작한다면 기다리다가 시간이 됐을 때 시작 버튼을 누르면 된다.

 

이렇게 설명이 끝났다. 복잡할 수 있기 때문에 몇번 해보면서 익혀야 한다.

 

참고로, 내가 버전을 두개 만들었는데, 하나는 라이트 버전이고 하나는 일반 버전이다. 내가 취켓팅 기능을 넣으면서 일반 티켓팅에까지 불필요한 영향을 미치면서 그게 제거된 버전을 만들었다. 취켓팅하는게 아니라면 라이트 버전 이용을 추천한다.

 

그럼 티켓팅 시 몇가지 팁을 소개하겠다.

 

1. 컴퓨터 자원 정리

티켓팅 시에는 프로그램을 아무것도 켜놓고 있지 않는게 좋다. 특히 유튜브/게임같이 인터넷 트래픽을 잡아먹는 경우에는 치명적이다. 능력이 있다면 작업 관리자에서 쓸모 없는 프로세스들을 정리해도 된다.

 

2. 인터넷 속도 향상

인터넷 속도가 제일 핵심이다. 노트북을 사용중이라면 데스크톱으로 하고, 불가능하다면 유선랜을 쓰도록 하자. 랜 포트가 없다면 유선 USB 3.0 이상 랜 카드를 사서 USB 3.0 이상을 지원하는 포트에 장착하자. USB Type C 포트가 있으면 더더욱 좋다.

 

 

아무래도 나는 전문적으로 개발을 공부한 사람도 아니고 공부중인 고등학생이기 때문에 프로그램이 많이 부족할 수 있다. 비판점과 개선점은 대환영이지만, 비난은 자제해 주셨으면 좋겠다. 실패했다면 실패한 사례를, 성공했다면 성공한 사례를 적어 주시면 도움이 될 것 같다. 

 

특히 오류 발생은 댓글로 제보해 주시면 해결할 수 있도록 노력하겠다.

 

아래는 티켓팅 매크로 일반, 라이트 버전 다운로드 링크다

일반 버전

 

38.62 MB file on MEGA

 

mega.nz

라이트 버전

 

38.62 MB file on MEGA

 

mega.nz

바이러스가 의심되면 직접 VirusTotal같은 곳에서 검사를 해 보시라. 문제 없을 것이다.

 

*추가

이번에 보고된 오류들을 패치하였다. 최근 글에 올라와 있으니 참고하시면 된다.

  1. ㅠㅠ 2020.05.07 01:07

    ㅠㅠ 크롬에서 소프트웨어가 제어되고 있는거 같다고 아예 안들어가지네요 ㅠㅠ 무료로 배포해주셔서 감사한데 정말 슬프네요 ㅠㅠㅠ...

  2. 파이썬바라기 2020.05.18 06:40

    안심예매 상품의 경우 좌석 선택 전 보안문자 입력단계가 있고, 이경우 예매가능한 좌석이 있다면 프로그램이 죽네요..

  3. 2020.05.25 16:37

    [0525/162752.853:ERROR:process_reader_win.cc(123)] NtOpenThread: {액세스 거부} 프로세스가 개체에 액세스를 요구했지만 액세스 권한이 없습니다. (0xc0000022) ....ㅠ이렇게 뜨네요.

  4. 할수있다 2020.05.27 01:06

    좌석선택은 수동으로 할 수 있는 옵션있었으면 좋겠어요. 좌석 클릭하면 바로 선택완료 버튼 눌러주는 거요.

  5. 감사합니당 2020.05.30 00:25

    공유감사합니다. 공연이 하루에 한 회차면 문제없이 실행이 잘되는데 하루에 2개의 공연이 있을때는 직링창에서 회차를 눌러줘야하네요 이 부분 업데이트 가능할까요? ㅠㅠ

  6. 감사감사 2020.06.09 16:08

    좌석선택 전 뜨는 팝업창을 닫을 수 있었으면 좋겠어요.

  7. 2020.06.18 02:11

    비밀댓글입니다

    • 2020.07.10 17:48

      비밀댓글입니다

  8. 2020.07.03 23:46

    비밀댓글입니다

    • 2020.07.10 17:46

      비밀댓글입니다

  9. 2020.07.07 09:06

    좌석 선택 페이지 들어가는 것 까진 작동되는데 그 이후엔 멈추네요. 그 이후엔 직접 해야 하나요?

  10. 2020.07.10 08:35

    비밀댓글입니다

  11. 싼쵸 2020.07.29 20:26

    덕분에 티켓팅 잘했어요!!
    구역 있는거였는데 구역 없이해서 좌석선택하느라 쫄렸지만
    감사드립니다

    • 페이지다운 2020.07.29 21:15 신고

      성공하셨다니 축하드립니다. 앞으로 더 발전시키겠습니다.

    • 싼쵸 2020.07.30 12:56

      혹시! 취케팅 기능 사용 방법 적어주신게 워딩이 헷갈리는데

      좌석은 1개로만 선택해야하고,
      중간부터 선택(랜덤)은 체크하지 말아야하는게 맞는거죠?

      구역 분리가 있는 공연의 경우에는 구역 분리는 선택해줘야 하나요?

  12. 2020.07.31 00:46

    비밀댓글입니다

  13. 문의 2020.08.02 16:14

    훌륭한 프로그램 공유 감사합니다! 그런데 안심예매 걸린 티켓창엔 적용이 안되나 봅니다. 그리고 회차002등을 눌러도 적용이 될때가 있고 안될때가 있고 그러네요. 취켓팅 모드에선 예매성공시 알림이 울렸으면 합니다. 유료로 구매한 프로그램보다 이게 훨씬 편리해서 이거로 계속 연습중인데 아직 불안불안해서 걱정이에요 ㅠㅠ

  14. 살짝오류? 2020.08.21 21:16

    ERROR:process_reader_win.cc(123)] NtOpenThread: {액세스 거부} 프로세스가 개체에 액세스를 요구했지만 액세스 권한이 없습니다. (0xc0000022)
    ERROR:exception_snapshot_win.cc(98)] thread ID 19496 not found in process

    만들어주셔서 너무나도 감사하지만 잘 작동되다가 멈추고 다시 켜도 구동이 안 됩니다 ㅠㅠㅠ 혹시 해결 방안이 있을까요?

    • 페이지다운 2020.08.21 21:17 신고

      재부팅 후 다시 켜 보시고 크롬 버전과 드라이버 버전이 일치하는지 봑인 바랍니다.

  15. 2020.09.08 13:39

    비밀댓글입니다

  16. ㅇㅇ 2020.09.09 08:18

    정말 죄송하지만 혹시 맥북용으로 빌드할 계획은 없으신가요ㅠㅠ??

  17. ㅜㅜ 2020.09.11 11:06

    회차 선택을 미리 해뒀는데도 안넘어가서 수동으로 눌러주니까 넘어갔어요ㅠㅠ 혹시 다른 문제가 있는 걸까요?

    • 2020.09.11 11:19

      비밀댓글입니다

    • 페이지다운 2020.09.11 17:23 신고

      크롬드라이버 버전이 일치하는지 확인해 보시기 바랍니다.

    • ㅜㅜ 2020.09.11 19:47

      오늘 확인하고 설치한거라 일치해요ㅜㅜ 딱 한번 쓰고 그뒤로 못 쓰고있어요ㅠㅠ

    • 페이지다운 2020.09.11 23:34 신고

      공연 번호 알려 주시면 확인하겠습니다.

  18. ㅇㅇ 2020.09.16 17:03

    우선 감사 인사부터 드립니다! 신기하네요 ㅎㅎ
    궁금한게 회차는 어떻게 넣어야해요? 하루에 3회차가 있을 경우, 001 002 003 이런식인가요? 아니면 1,2,3? 한 회차만 있는 경우에는 공란으로 둬도 되나요?

    • 페이지다운 2020.09.16 21:09 신고

      네 그렇게 하시는 것이 맞습니다. 한 회차만 있을 경우 공란으로 둬도 무방합니다.

+ Recent posts