인증된 페이지(로그인 제한 콘텐츠) 스크린샷 방법
Source: Dev.to
인증된 페이지(로그인 제한 콘텐츠) 스크린샷 찍는 방법
소개
웹사이트에서 로그인 후에만 접근할 수 있는 페이지를 스크린샷으로 저장해야 하는 경우가 종종 있습니다. 일반적인 스크린샷 도구는 인증된 세션을 인식하지 못하기 때문에, 로그인된 상태를 재현하는 별도의 절차가 필요합니다. 이 글에서는 Puppeteer와 Playwright를 활용해 로그인된 페이지를 자동으로 열고, 원하는 영역을 캡처하는 방법을 단계별로 설명합니다.
왜 일반 스크린샷 도구로는 안 될까?
- 쿠키와 세션: 로그인 후 브라우저에 저장되는 쿠키와 세션 토큰이 없으면 서버는 요청을 인증되지 않은 사용자로 간주합니다.
- CSRF 토큰: 일부 사이트는 요청마다 동적으로 생성되는 CSRF 토큰을 요구합니다.
- 동적 렌더링: 로그인 후에만 로드되는 JavaScript 기반 UI 요소가 존재합니다.
따라서, 헤드리스 브라우저를 사용해 실제 브라우저와 동일한 환경을 구축해야 합니다.
1. 사전 준비
- Node.js (v14 이상) 설치
- Puppeteer 혹은 Playwright 패키지 설치
npm install puppeteer # 또는
npm install playwright
- 로그인에 사용할 자격 증명(아이디·비밀번호) 혹은 API 토큰을 안전하게 보관합니다.
- 환경 변수(
.env)에 저장하고dotenv패키지로 로드하는 것이 일반적입니다.
- 환경 변수(
2. Puppeteer 로 로그인 후 스크린샷 찍기
핵심 흐름
- 브라우저 인스턴스 실행
- 로그인 페이지로 이동
- 폼에 자격 증명 입력 → 제출
- 로그인 성공 여부 확인(예: 특정 요소 존재 여부)
- 목표 페이지로 이동
page.screenshot()호출
예시 코드
const puppeteer = require('puppeteer');
require('dotenv').config();
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// 1️⃣ 로그인 페이지 이동
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });
// 2️⃣ 폼에 입력
await page.type('#email', process.env.USER_EMAIL);
await page.type('#password', process.env.USER_PASSWORD);
await Promise.all([
page.click('button[type="submit"]'),
page.waitForNavigation({ waitUntil: 'networkidle2' }),
]);
// 3️⃣ 로그인 성공 확인
if (await page.$('.profile-avatar') === null) {
console.error('로그인에 실패했습니다.');
await browser.close();
return;
}
// 4️⃣ 인증된 페이지로 이동
await page.goto('https://example.com/protected/content', {
waitUntil: 'networkidle2',
});
// 5️⃣ 스크린샷 저장
await page.screenshot({ path: 'protected-page.png', fullPage: true });
await browser.close();
})();
팁
waitUntil: 'networkidle2'옵션은 네트워크 요청이 500 ms 동안 2개 이하일 때 페이지 로딩이 완료됐다고 판단합니다. 동적 페이지에 유용합니다.- 로그인 후 쿠키를 파일에 저장하고 재사용하면 매번 로그인 과정을 건너뛸 수 있습니다.
3. Playwright 로 동일 작업 수행하기
Playwright는 멀티 브라우저(Chromium, Firefox, WebKit)를 지원하므로, 다양한 환경에서 테스트하고 싶을 때 유리합니다.
예시 코드
const { chromium } = require('playwright');
require('dotenv').config();
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
// 로그인 페이지 이동
await page.goto('https://example.com/login');
// 폼 입력 및 제출
await page.fill('#email', process.env.USER_EMAIL);
await page.fill('#password', process.env.USER_PASSWORD);
await Promise.all([
page.click('button[type="submit"]'),
page.waitForNavigation(),
]);
// 로그인 성공 확인
if (!(await page.$('.profile-avatar'))) {
console.error('로그인에 실패했습니다.');
await browser.close();
return;
}
// 인증된 페이지로 이동
await page.goto('https://example.com/protected/content');
// 스크린샷 캡처
await page.screenshot({ path: 'protected-page.png', fullPage: true });
await browser.close();
})();
4. 쿠키/세션을 재활용하는 방법
매번 로그인 절차를 수행하면 속도가 느려지고, 로그인 제한(예: 2FA) 때문에 자동화가 어려워질 수 있습니다. 이때는 쿠키 파일을 저장하고 다음 실행 시 로드하는 것이 좋습니다.
Puppeteer 예시
// 로그인 후 쿠키 저장
const cookies = await page.cookies();
const fs = require('fs');
fs.writeFileSync('cookies.json', JSON.stringify(cookies, null, 2));
// 다음 실행 시 쿠키 로드
const savedCookies = JSON.parse(fs.readFileSync('cookies.json'));
await page.setCookie(...savedCookies);
Playwright 예시
// 로그인 후 스토리지(state) 저장
await context.storageState({ path: 'state.json' });
// 다음 실행 시 스토리지 로드
const context = await browser.newContext({ storageState: 'state.json' });
5. 자주 발생하는 오류와 해결책
| 오류 | 원인 | 해결 방법 |
|---|---|---|
TimeoutError: waiting for selector | 로그인 후 페이지 전환이 느리거나, 선택자가 잘못됨 | waitForSelector에 충분한 timeout 지정하거나, 올바른 CSS 선택자 확인 |
401 Unauthorized | 쿠키/세션이 유효하지 않음 | 쿠키 저장/로드 로직을 검증하고, 로그인 단계가 정상적으로 완료됐는지 확인 |
net::ERR_CERT_COMMON_NAME_INVALID | HTTPS 인증서 오류 (자체 서명) | ignoreHTTPSErrors: true 옵션을 브라우저 런치 시 추가 |
ElementHandle is detached from the DOM | 페이지가 리로드된 뒤에 요소에 접근 | 요소를 다시 찾거나, waitForNavigation 후에 작업 수행 |
6. 결론
- 헤드리스 브라우저(Puppeteer / Playwright)를 사용하면 로그인된 상태를 그대로 재현할 수 있습니다.
- 쿠키·스토리지(state)를 저장해 두면 매번 로그인 과정을 건너뛰어 성능을 크게 향상시킬 수 있습니다.
- 오류가 발생하면 네트워크 대기, 선택자 정확도, HTTPS 옵션 등을 점검해 보세요.
이제 인증된 페이지의 스크린샷을 자동으로 생성할 수 있는 기반이 마련되었습니다. 필요에 따라 CI 파이프라인에 통합하거나, 정기적인 모니터링용 스크립트로 활용해 보세요. 🚀
Overview
공개 URL을 캡처하는 것은 간단합니다. 로그인 필요가 있는 대시보드, 인보이스, 혹은 어떤 페이지를 캡처하려면 헤드리스 브라우저 자동화가 한계에 부딪힙니다: 먼저 로그인하고, 세션 쿠키를 보존하고, 스크린샷 요청에 전달하며, 세션 만료를 처리해야 합니다.
PageBolt는 쿠키와 헤더를 직접 전달할 수 있게 해줍니다 — 로그인 자동화가 필요 없습니다.
세션 쿠키 전달
앱이 쿠키 기반 세션을 사용하는 경우(대부분의 서버‑렌더링 앱이 그렇습니다):
const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
method: "POST",
headers: {
"x-api-key": process.env.PAGEBOLT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://yourapp.com/dashboard",
cookies: [
{
name: "session",
value: "your-session-token-here",
domain: "yourapp.com",
},
],
fullPage: true,
blockBanners: true,
}),
});
const image = Buffer.from(await res.arrayBuffer());
헤더를 통해 Bearer 토큰 전달
SPA 및 Authorization 헤더를 사용하는 API의 경우:
const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
method: "POST",
headers: {
"x-api-key": process.env.PAGEBOLT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://yourapp.com/reports/q4-2024",
headers: {
Authorization: `Bearer ${userToken}`,
},
fullPage: true,
}),
});
여러 쿠키 (예: CSRF + 세션)
일부 프레임워크에서는 세션 쿠키와 CSRF 토큰을 모두 필요로 합니다:
body: JSON.stringify({
url: "https://yourapp.com/admin/users",
cookies: [
{ name: "session_id", value: sessionId, domain: "yourapp.com" },
{ name: "csrf_token", value: csrfToken, domain: "yourapp.com" },
],
}),
Source: …
앱 내부에서 스크린샷 생성 (서버‑사이드)
내부 도구에 가장 깔끔한 패턴: 서버에서 단기간 토큰을 생성하고, URL에 삽입한 뒤 페이지를 스크린샷하고, 토큰을 만료시킵니다.
// Express route — generates signed URL, screenshots it, returns PDF
app.get("/invoices/:id/pdf", requireAuth, async (req, res) => {
const { id } = req.params;
// Create a single‑use signed token
const token = await db.signedTokens.create({
invoiceId: id,
userId: req.user.id,
expiresAt: new Date(Date.now() + 60_000), // 1 minute
});
const signedUrl = `https://yourapp.com/invoices/${id}?token=${token.value}`;
const pdfRes = await fetch("https://pagebolt.dev/api/v1/pdf", {
method: "POST",
headers: {
"x-api-key": process.env.PAGEBOLT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ url: signedUrl }),
});
const pdf = Buffer.from(await pdfRes.arrayBuffer());
// Expire the token immediately
await db.signedTokens.expire(token.id);
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="invoice-${id}.pdf"`
);
res.send(pdf);
});
이 방법은 서드‑파티 서비스에 장기 인증 정보를 전달하지 않으면서, 토큰을 60초 동안만 유효하게 하고 사용 후 즉시 무효화합니다.
사용자를 대신하여 대시보드 캡처
SaaS 앱에서 사용자의 계정 상태 스크린샷을 이메일로 보내야 할 경우:
async function captureUserDashboard(userId) {
// DB에서 사용자의 현재 세션 토큰을 가져옵니다
const user = await db.users.findById(userId);
const sessionToken = await generateSessionToken(user);
const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
method: "POST",
headers: {
"x-api-key": process.env.PAGEBOLT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: `https://yourapp.com/users/${userId}/dashboard`,
cookies: [
{
name: "session",
value: sessionToken,
domain: "yourapp.com",
},
],
fullPage: true,
blockBanners: true,
blockAds: true,
}),
});
return Buffer.from(await res.arrayBuffer());
}
// 모든 사용자에게 주간 대시보드 스크린샷 전송
async function sendWeeklyDigest() {
const users = await db.users.findActive();
for (const user of users) {
const image = await captureUserDashboard(user.id);
await email.send({
to: user.email,
subject: "Your weekly summary",
attachments: [{ filename: "dashboard.png", content: image }],
});
}
}
Python 예제
import os
import requests
def screenshot_authenticated(url: str, session_cookie: str) -> bytes:
resp = requests.post(
"https://pagebolt.dev/api/v1/screenshot",
headers={"x-api-key": os.environ["PAGEBOLT_API_KEY"]},
json={
"url": url,
"cookies": [
{
"name": "session",
"value": session_cookie,
"domain": url.split("/")[2],
}
],
"fullPage": True,
"blockBanners": True,
},
)
resp.raise_for_status()
return resp.content
image = screenshot_authenticated(
"https://yourapp.com/dashboard",
session_cookie="your-session-token"
)
with open("dashboard.png", "wb") as f:
f.write(image)
What doesn’t work (and why)
The alternative — automating login via a sequence of clicks — is fragile because it depends on UI stability, timing, and can break whenever the login flow changes. Using PageBolt’s cookie/header injection is far more reliable and maintainable.
Challenges with Traditional Authentication
- Login forms change layout → automation breaks
- CAPTCHAs and bot detection block headless browsers
- MFA flows are hard to automate
- Session cookies from automated logins expire differently than API tokens
Solution
Passing a cookie or header directly sidesteps all of that. Generate a token server‑side where you control the auth logic, pass it to the capture request, and expire it immediately after.
Try it free — 100 requests/month, no credit card. → Get started in 2 minutes