일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- FastAPI
- kotlin
- nginx
- selenium
- App
- WebRTC
- 직링
- fiddler
- 티켓
- 콘서트
- 자동화
- realtime
- 개발자 도구
- 예매
- EC2
- 프록시
- AWS
- GPT
- uvicorn
- 개발자 도구 우회
- 티켓링크
- puppeteer
- linux
- ubuntu
- Django
- 자동화 도구
- netfunnel 우회
- 퍼피티어
- WSL
- 피들러
- Today
- Total
개발 삽질 일지
[직링 자동화 도구] 퍼피티어를 이용해 콘서트 예매하기 본문
지난 글에서는 실제 사용자의 동작을 흉내 내는 방식으로 콘서트 예매를 시도했습니다. 콘솔 창에서 HTML 요소를 확인한 후, 그 요소를 클릭하는 방식으로 진행을 했었지만, 예매하기 버튼이 콜백 함수 안에 감싸져 있는 경우에는 정상적인 사용자의 동작을 흉내내더라도 같은 결과를 얻을 수 없었습니다. 사용자로 변신한 메타몽같은 느낌이였죠. 외형은 사용자지만 능력치는 다른... 이번 글에서는 Node.js 기반의 Puppeteer(퍼피티어)를 사용해서 콘서트 예매를 자동화 시켜보도록 하겠습니다. 개발을 처음 접하시는 분들도 따라갈 수 있도록 0단계부터 시작해서 최종장까지 함께 줄의 앞부분을 노려봅시다.
⚠️ 이 글은 기술적인 호기심과 실험적인 분석을 위한 목적으로 작성되었습니다. 실제 예매 과정에서 이를 악용하거나 무단으로 활용하는 것은 서비스 약관 위반이 될 수 있으며, 법적 책임이 따를 수 있습니다. 또한, 이번 글에서는 코드 구현보다는 개념과 작동 원리에 집중하니 가볍게 읽어주시길 바랍니다.
0단계 : 개발 환경 준비하기
개발자 도구 콘솔창에서 벗어난 것을 환영합니다. 개발을 찍먹하러 오셨군요! 이제 진짜 자동화를 위한 여정이 시작됩니다. 이를 위해 가장 먼저 해야할 일은 개발 환경을 세팅하는 일입니다.
VS Code 설치하기
우리는 이제 코드를 작성할 IDE(통합 개발 환경)로 Visual Studio Code를 사용할 겁니다. 가볍고 확장성이 뛰어나며 퍼피티어가 필요로 하는 Node.js 기반 자동화에 최적화되어 있기 때문이죠. 설치를 진행해보도록 하죠.
1. VS Code 다운로드 사이트로 이동해서 본인의 운영체제에 맞는 버전을 다운로드 합니다.(Windows, macOS, Linux)
Download Visual Studio Code - Mac, Linux, Windows
Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows. Download Visual Studio Code to experience a redefined code editor, optimized for building and debugging modern web and cloud applications.
code.visualstudio.com
2. 아래의 순서대로 설치를 진행하시면 됩니다. 다 기본 설정값대로 누르시고 PATH에 추가만 해주시면 됩니다.
Node.js 설치하기
이제 개발을 할 수 있는 환경은 준비됐으니 또 다른 필수 도구인 Node.js를 설치해봅시다. 퍼피티어는 Node.js 기반의 라이브러리이기 때문에 Node.js가 먼저 설치되어야 합니다.
1. Node.js 다운로드 사이트로 이동해 본인 운영 체제에 맞게 다운로드를 실행합니다. LTS(Long Term Support)가 안정적인 장기 지원 버전이기 때문에 LTS로 설치를 진행하도록 하겠습니다.
Node.js — Run JavaScript Everywhere
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
nodejs.org
2. VS Code와 마찬가지로 아래 순서대로 실행해주시면 됩니다.
1단계 : 프로젝트 초기화 및 퍼피티어 설치
이제 본격적으로 자동화 프로젝트를 진행해봅시다. 폴더를 하나 만들고, 우클릭( > 추가 옵션 표시) > Code(으)로 열기를 통해 폴더를 VS Code에서 사용합시다. VS Code 내부에서 File > Open Folder, 혹은 Ctrl + K , Ctrl + O를 사용해 폴더를 여는 방법도 있습니다. 이제 프로젝트 폴더는 생성됐으니 프로젝트를 시작해봅시다. Ctrl + `을 눌러 터미널을 열고, 아래 명령어를 입력해줍시다.
npm init -y
명령어를 실행하면 package.json 파일이 생성되고, 프로젝트의 기본 정보가 담깁니다. 이제 우리의 주인공 퍼피티어를 설치합시다.
npm install puppeteer
퍼피티어는 내부적으로 크로미움 브라우저를 자동으로 같이 설치하므로 용량이 조금 클 수 있습니다. 모든 설치가 완료되면 node_modules 폴더와 package-lock.json 파일이 생성됩니다. 설치 이후 아래 명령어를 통해서 퍼피티어 버전을 확인해 보실 수 있습니다.
npm list puppeteer
2단계 : 퍼피티어로 브라우저 열어보기
이제 모든 개발 환경 준비가 끝나고, 프로젝트까지 생성되었습니다. 퍼피티어와 익숙해질 겸, 퍼피티어를 통해 브라우저를 열어보도록 합시다. 우선 루트 경로에 퍼피티어 코드를 작성할 index.js를 만들어줍시다. .js 확장자를 사용해야 JavaScript 파일로 인식되며, 이전 글에서 언급한 것처럼 퍼피티어는 JS 기반으로 동작합니다.
puppeteer/
├── node_modules/ # npm이 자동으로 설치하는 의존성 모듈
├── index.js # 메인 퍼피티어 실행 스크립트
├── package.json # 프로젝트 메타 정보 및 의존성
└── package-lock.json # 의존성 고정 파일
저는 puppeteer이라는 폴더 안에서 npm을 시작했습니다. 이제 index.js 안에 코드를 작성합시다.
const puppeteer = require('puppeteer');
async function startBrowser() {
const browser = await puppeteer.launch({
headless: false, // 브라우저 창을 실제로 띄움
defaultViewport: null, // 뷰포트 크기를 제한하지 않음
args: ['--start-maximized'], // 브라우저 창 최대화
});
const page = await browser.newPage();
await page.goto('https://www.naver.com');
// 3초 기다린 뒤 브라우저 종료
// await page.waitForTimeout(3000);
// await browser.close();
}
startBrowser();
간단한 기능들을 넣은 코드를 작성해봤습니다. 위 코드를 index.js에 넣고, 터미널을 열어 아래 명령어를 실행하면 네이버 창이 새로운 브라우저에서 열리게 됩니다. (node.js로 index.js 파일을 실행하겠다는 뜻입니다.)
node index.js
3단계 : 예매 사이트에서 버튼 클릭 확인하기
지난 글 마지막 부분에 보여드렸던 코드입니다. 기본적인 흐름은 예매 사이트에 들어가서, 팝업창이 있으면 팝업창을 닫고, 날짜와 시간을 선택하고 예매하기 버튼을 누른다입니다. 콘솔 & 크롬 익스텐션으로 직링 구하기부터 보신 분들이라면 함수의 정확한 동작은 몰라도 어느정도는 유추하실 수 있을 것이라 생각합니다. (사실 흐름만 알면 됩니다. 코드는 GPT 선생님이 도와줄거에요!)
// 1. puppeteer, fs 모듈 불러오기
const puppeteer = require("puppeteer");
const fs = require("fs");
(async () => {
// 2. 브라우저 실행
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ["--start-maximized"],
});
const page = await browser.newPage();
// 3. 새 탭 열기 & 쿠키 로딩
const cookies = JSON.parse(fs.readFileSync("cookies.json", "utf8"));
await page.setCookie(...cookies);
// 4. 티켓 예매 페이지로 이동
await page.goto(
"https://ticket.sapmle_url.com/concert/index.htm?some_id=******",
{
waitUntil: "networkidle2",
}
);
// 5. 팝업 닫기 시도
try {
await page.waitForSelector(".some_kind_of_notice", {
visible: true,
timeout: 5000,
});
await page.click(".close_some_kind_of_notice");
console.log("예매 안내 팝업 닫기 완료");
} catch (err) {
console.log("예매 안내 팝업 없음");
}
// 6. 날짜 선택
try {
await page.waitForSelector(".select_date button", {
timeout: 5000,
});
await page.click(".select_date button");
console.log("날짜 클릭 완료");
} catch (err) {
console.error("날짜 클릭 실패", err);
await browser.close();
return;
}
// 7. 시간 클릭
try {
await page.waitForSelector(".time button", { timeout: 5000 });
await page.click(".time button");
console.log("시간 클릭 완료");
} catch (err) {
console.error("시간 클릭 실패", err);
await browser.close();
return;
}
// 8. 예매 버튼 클릭
try {
await page.waitForSelector("buy_ticket_btn", { timeout: 5000 });
await page.click(".buy_ticket_btn");
console.log("예매 버튼 클릭 완료");
} catch (err) {
console.error(".예매 버튼 클릭 실패", err);
}
})();
위 코드에서 이상한 부분이 있습니다. 바로 쿠키 가져오기인데요. 코드를 보면 "cookies는 cookies.json에서 utf8형식으로 가져온 값이다. 그리고 기다렸다가 페이지에 쿠키를 세팅해준다" 정도로 해석할 수 있겠네요.
const cookies = JSON.parse(fs.readFileSync("cookies.json", "utf8"));
await page.setCookie(...cookies);
그렇다면 cookies.json은 언제 생겼고, 왜 필요할까요? cookies.json은 우리가 직접 로그인하고 나서 브라우저에 로그인 상태를 저장할 때 생깁니다. 조금 더 구체적으로 말하면 퍼피티어를 이용해 브라우저를 열고, 로그인을 진행하면 현재 쿠키를 저장하게 됩니다. 이를 저장함으로 로그인 과정을 건너뛰고 바로 예매하기 버튼을 클릭하는 동작까지 이어지게 할 수 있습니다. 그렇다면 index.js를 실행하기 전에 쿠키를 저장하기 위한 .js도 만들어야겠군요. save_cookie.js를 만들어서 코드를 입력해줍시다.
const puppeteer = require("puppeteer");
const fs = require("fs");
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ["--start-maximized"],
});
const page = await browser.newPage();
// 페이지로 이동
await page.goto("https://www.login_test_url.com", {
waitUntil: "networkidle2",
});
console.log("60초 동안 기다립니다...");
await new Promise((resolve) => setTimeout(resolve, 60000)); // 60초 대기
// 쿠키 저장
const cookies = await page.cookies();
fs.writeFileSync("cookies.json", JSON.stringify(cookies, null, 2));
console.log("쿠키 저장 완료: cookies.json");
await browser.close();
})();
index.js와 거의 동일한 구조에 이번에는 cookie.json을 불러오는게 아닌 cookies.json을 생성하는 코드가 있습니다. 이번에는 페이지에 있는 쿠키들을 가져와서 cookies.json 형태로 저장을 해준다정도로 이해하시면 될 것 같습니다.
const cookies = await page.cookies();
fs.writeFileSync("cookies.json", JSON.stringify(cookies, null, 2));
console.log("쿠키 저장 완료: cookies.json");
실제로 비교를 해보기 위해서 사이트에 로그인 했을 때 쿠키를 cookies.json에 저장하고, 개발자 도구를 열어서 실제 사이트의 쿠키들과 비교해보겠습니다.
[
{
"name": "NID_AUT",
"value": "xny1E*****************************************/",
},
{
"name": "nid_inf",
"value": "20075*****",
},
{
"name": "SRT5",
"value": "17479*****",
},
......
]
전부 다 확인해보지는 않았지만, 값들을 잘 저장한 것 같습니다. save_cookie.js를 먼저 실행해준 뒤, index.js를 누르면 로그인이 정상적으로 진행되어 버튼 예매까지 눌른 상태임을 확인할 수 있습니다. 그렇다면 지금 흐름은 어떻게 진행될까요?
1. node cookie.js로 페이지에 이동해 로그인한 후, 쿠키를 저장합니다.
2. 60초 이후 브라우저가 자동으로 종료되면 node index.js로 예매 페이지에 이동하고
3. 팝업 닫기, 날짜 선택, 시간 선택, 예매 버튼 클릭까지 실행됩니다.
4단계 : 완전 자동화로 고도화하기
모든 발전과 발명은 귀찮음과 불편함에서 온다고 개인적으로 생각합니다. 아니 개발까지 했는데 3분 전에 로그인해서 쿠키도 받아오고 다시 js 실행해서 티켓 구하는게 맞아? 라고 생각하신 분들, 저도 그렇게 생각합니다. 우선 지금 코드에서 불편함 점들을 정리해봅시다.
1. 코드가 두 개로 나누어져 있어 하나를 실행해서 쿠키를 받아오고, 그 이후에 다시 코드를 실행한다는 점
2. 메인 사이트만 입력해주면 되지, 내가 콘서트 아이디까지 구해가지고 미리 url 이동하는 곳에 넣어둬야 한다는 점
(2번 같은 경우에는 사용자가 클릭으로 이동하는게 더 귀찮으면 url에 아이디 값을 넣어주는 방식이 더 편하긴 합니다.)
그렇다면 2가지 불편함을 해소해 줄만한 통합 코드를 작성해봅시다.
우선 로그인 전 후에 달라지는 요소가 있는지 확인해봅시다. 보통은 로그인 텍스트가 로그아웃으로 변경되거나, 마이페이지, 아이디(닉네임) 등이 생성됩니다. 여기서 클래스 명이 login에서 logout으로 변경된다는 점을 확인했습니다. 그렇다면 로그인 감지는 끝이 났고, 사용자가 클릭했을 때 id 값을 구해오는 로직 하나만 추가해주면 기존 코드들과 연동해서 사용할 수 있을 것 같습니다. 다음은 전체 코드입니다.
const puppeteer = require("puppeteer");
async function startBrowser() {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ["--start-maximized"],
});
const page = await browser.newPage();
await page.goto("https://ticket.sample_url.com/");
try {
// 1. 사이트 이동 후 로그인할 때까지 기다립시다.
// timeout의 단위는 ms입니다. 0을 주면 무한대로 로그인까지 기다립니다.
await page.waitForSelector(".logout", { timeout: 0 });
console.log("로그인 감지됨");
// 2. 로그인이 완료되었으니 사용자가 클릭한 url에서
// 콘서트 id 값들을 자동으로 가져오게 만들어봅시다.
let id = null;
// id 값이 있을 때까지 100ms 단위로 계속 확인을 합니다.
while (!id) {
const current_url = page.url();
const url_obj = new URL(current_url);
id = url_obj.searchParams.get("Id");
if (id) {
console.log("url에 포함된 id 값: ", id);
break;
}
console.log("아직 id 값 찾지 못했음 100ms 이후 다시 확인");
await new Promise((resolve) => setTimeout(resolve, 100));
}
await page.waitForSelector(".sample_date");
await page.click(".sample_date");
await page.waitForSelector(".ticket_buy_btn", { visible: true });
await page.click(".ticket_buy_btn");
} catch {}
}
startBrowser();
이제 불편함 1번과 2번이 모두 해소되었습니다. 만약에 원하는 콘서트 / 원하는 날짜가 명확하게 있으신 경우에는 1번 로그인과 2번 url 감지 사이에 아래 코드를 작성해줍시다. 버튼을 클릭하는 흐름도 특정한 날짜를 원하시면 그 날짜가 포함된 HTML 요소를 클릭하게 하는 방법으로 코드를 완성시킬 수 있습니다. 현재는 단 콘서트 아이디를 단 1회만 찾습니다. 즉 A 콘서트 예매 사이트에서 창이 뜨고, B 콘서트 예매 사이트로 이동하면 창이 뜨지 않습니다. 이정도는 혼자 해결해봅시다. 스스로 검색하며 해결하다 보면 개발에도 흥미를 느끼실겁니다. 길어야 10분 예상합니다.
await page.goto("https://ticket.sample_url.com/id=*******");
다음 글 안내
이번 글에서는 퍼피티어를 이용한 자동화 도구를 개발해봤습니다. 여기서 조금 더 고도화 시킨다면 자동화 프로그램으로 넘어갈 수 도 있습니다. Electron을 사용한 GUI 입히기, .exe로 만들기 등 방법은 많지만 우리는 아직 셀레니움을 통한 자동화 도구를 완성하지 못했습니다. 그래서 다음 글에서는 셀리니움을 통한 자동화 도구를 만들고, 시간이 허락해준다면 포장지를 씌워 프로그램으로 만들어보도록 하겠습니다. 다시 한번 이 글은 기술적인 호기심과 실험적인 분석을 위한 목적으로 작성되었습니다. 실제 예매 과정에서 이를 악용하거나 무단으로 활용하는 것은 서비스 약관 위반이 될 수 있으며, 법적 책임이 따를 수 있습니다.
[직링 자동화 도구] 셀레니움을 이용해 콘서트 예매하기
지난 글에서는 Puppeteer를 활용해 콘서트 예매 자동화를 성공적으로 구현해 보았습니다. 콘솔 창에서 HTML 요소를 분석한 뒤, 실제 사용자의 동작처럼 버튼을 클릭하고 예매 절차를 밟는 과정을 구
gnaaak.tistory.com
'자유로운 개발일지 > 실험일지' 카테고리의 다른 글
[직링 자동화 프로그램] 퍼피티어, 일렉트론을 이용한 콘서트 예매 프로그램 (2) | 2025.05.26 |
---|---|
[직링 자동화 도구] 셀레니움을 이용해 콘서트 예매하기 (1) | 2025.05.24 |
[직링 개발자 도구] 사용자 동작을 흉내 내서 콘서트 예매하기 (5) | 2025.05.22 |
[NetFunnel 우회] 직링 구하기 (2) | 2025.05.21 |
[크롬 익스텐션] 직링 구하기 (4) | 2025.05.17 |