Node.js로 웹 스크래핑을 하다 보면 이상한 현상을 겪는다. got이나 axios로 보낸 요청이 브라우저와 동일한 URL을 향하는데도 403 Forbidden이나 Cloudflare 챌린지 페이지만 돌아온다. User-Agent를 Chrome으로 바꿔도 결과는 같다. 이유는 단순하다 — 현대 안티봇 시스템은 헤더 하나만 보지 않는다. got-scraping은 Apify가 만든 got 기반 HTTP 클라이언트로, 브라우저와 동일한 헤더 세트를 자동 생성하고 HTTP/2를 지원한다. 이 글에서는 Node.js에서 got-scraping을 사용하고 ProxyHat 주거용 프록시와 결합하는 방법을 프레임워크의 확장 지점 중심으로 설명한다.
got-scraping이 Node.js 스크래핑에서 중요한 이유
일반적인 HTTP 클라이언트로 스크래핑하면 차단되는 이유는 여러 층에 걸쳐 있다:
- 헤더 순서: 브라우저는 정해진 순서로 헤더를 보낸다.
got이나axios는 객체 키 순서대로 헤더를 직렬화하므로, 브라우저 패턴과 불일치한다. - Sec-CH-UA: Chrome 89+는
Sec-CH-UA헤더로 클라이언트 힌트를 보낸다. 이 헤더가 없거나 User-Agent와 불일치하면 봇으로 간주된다. 자세한 내용은 MDN 문서를 참조하라. - Accept-Language:
Accept-Language: en-US만 보내는 단순한 패턴은 브라우저의en-US,en;q=0.9,ko;q=0.8같은 복합 패턴과 다르다. - TLS 지문: Node.js의 TLS 핸드셰이크는 Chrome의 것과 다르다. 일부 안티봇은 JA3 해시로 이를 구별한다.
- HTTP/2: Chrome은 HTTP/2로 요청을 보낸다.
got의 기본 HTTP/1.1 요청은 프로토콜 레벨에서 이미 차이가 난다.
got-scraping은 이 문제를 두 가지로 해결한다. 첫째, header-generator 패키지로 브라우저, 디바이스, OS에 맞는 일관된 헤더 세트를 생성한다. 둘째, HTTP/2를 기본 지원하여 실제 브라우저와 동일한 프로토콜로 요청을 보낸다. 공식 got-scraping GitHub 저장소에서 전체 API를 확인할 수 있다.
got-scraping의 확장 API — got.extend와 헤더 생성기
got-scraping은 got 위에 얇은 레이어를 추가한다. 핵심 API는 gotScraping() 함수와 gotScraping.extend() 메서드다. extend로 기본 옵션을 설정하고, 각 요청에서 옵션을 덮어쓸 수 있다. 이는 got의 확장 패턴을 그대로 따르며, 미들웨어처럼 동작한다.
headerGeneratorOptions
headerGeneratorOptions는 헤더 생성기에 전달되는 설정 객체다. 주요 필드는 다음과 같다:
browsers:['chrome', 'firefox']등 생성할 브라우저 목록devices:['desktop', 'mobile']operatingSystems:['windows', 'macos', 'linux', 'android', 'ios']
이 옵션들을 조합하면, 예를 들어 "Chrome on Windows 데스크톱"에 해당하는 헤더 세트가 생성된다. Sec-CH-UA, Accept-Language, Accept-Encoding 등 모든 헤더가 이 조합과 일관성을 유지한다. 각 요청마다 다른 조합을 지정하면, 여러 브라우저 프로필을 흉내 내어 지문 다양성을 확보할 수 있다.
const { gotScraping } = require('got-scraping');
const response = await gotScraping({
url: 'https://example.com/data',
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['linux', 'macos'],
},
});
console.log(response.statusCode, response.body.slice(0, 200));
proxyUrl 옵션
got-scraping은 proxyUrl 옵션으로 HTTP/1.1 및 HTTP/2 프록시를 지원한다. 이 옵션에 프록시 URL을 전달하면 모든 요청이 해당 프록시를 통해 라우팅된다. got의 공식 문서에서 프록시 관련 옵션의 전체 목록을 확인할 수 있다. got.extend()를 통해 기본 proxyUrl을 설정하면, 모든 파생 요청에 동일한 프록시가 적용된다.
ProxyHat 주거용 프록시로 라우팅하기
헤더와 프로토콜이 브라우저와 일치해도, IP 주소가 데이터센터 대역이면 차단된다. Cloudflare, Akamai, PerimeterX 같은 안티봇 서비스는 IP 평판 데이터베이스를 사용하여 요청 출처를 평가한다. 주거용 IP는 실제 ISP에서 할당된 주소이므로, 일반 사용자 트래픽과 구별하기 어렵다. 헤더와 TLS가 브라우저와 일치할 때 주거용 IP가 마지막 차단 레이어를 해결한다.
| 프록시 유형 | IP 출처 | 탐지 난이도 | 평균 응답 시간 | 적합한 용도 |
|---|---|---|---|---|
| 주거용 (Residential) | 실제 ISP | 매우 낮음 | 200ms~800ms | 스크래핑, SERP 추적, 가격 모니터링 |
| 데이터센터 | 클라우드/호스팅 | 높음 | 50ms~150ms | 비차단 사이트, 대량 요청 |
| 모바일 | 이동통신사 | 매우 낮음 | 500ms~2000ms | 모바일 타겟팅, 고도 보호 사이트 |
ProxyHat 주거용 프록시는 gate.proxyhat.com:8080 게이트웨이를 통해 HTTP 프록시로, gate.proxyhat.com:1080을 통해 SOCKS5 프록시로 사용할 수 있다. 사용자 이름에 국가 및 세션 플래그를 포함하여 지역 타겟팅과 세션 고정을 제어한다. 지원 위치 목록에서 사용 가능한 국가 코드를 확인하라.
const { gotScraping } = require('got-scraping');
const response = await gotScraping({
url: 'https://example.com/api/products',
proxyUrl: 'http://user-country-US:pass@gate.proxyhat.com:8080',
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows'],
},
timeout: { request: 15000 },
});
SOCKS5가 필요한 경우, 포트 1080을 사용한다:
proxyUrl: 'socks5://user-country-DE:pass@gate.proxyhat.com:1080'
자세한 연결 설정은 ProxyHat 문서를 참조하라. 요금제에서 주거용, 데이터센터, 모바일 프록시의 가격을 비교할 수 있다.
실행 가능한 Node.js 예제 — 회전과 재시도
실제 스크래핑에서는 각 요청마다 다른 IP를 사용하는 것이 중요하다. ProxyHat의 사용자 이름 형식 user-country-US-session-abc123을 활용하면, 세션 ID를 변경할 때마다 새로운 주거용 IP가 할당된다. 아래 예제는 각 요청마다 임의의 세션 ID를 생성하고, 실패 시 지수 백오프로 재시도한다.
const { gotScraping } = require('got-scraping');
const crypto = require('crypto');
const GATE = 'gate.proxyhat.com';
const PORT = 8080;
const USER = 'your_username';
const PASS = 'your_password';
function buildProxyUrl(country, session) {
const username = `${USER}-country-${country}-session-${session}`;
return `http://${username}:${PASS}@${GATE}:${PORT}`;
}
async function fetchWithRotation(url, country = 'US', maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const session = crypto.randomBytes(6).toString('hex');
try {
const response = await gotScraping({
url,
proxyUrl: buildProxyUrl(country, session),
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows', 'macos'],
},
timeout: { request: 15000 },
});
return response;
} catch (err) {
console.error(`Attempt ${attempt + 1} failed: ${err.message}`);
if (attempt === maxRetries - 1) throw err;
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
}
}
}
fetchWithRotation('https://example.com/search?q=laptop')
.then(res => console.log(res.statusCode))
.catch(err => console.error(err));
이 패턴의 핵심은:
crypto.randomBytes(6)로 12자리 hex 세션 ID 생성 — 충돌 확률은 무시할 수준이다.- 최대 3회 재시도, 각 시도마다 1초, 2초, 3초 대기한다.
- 15초 요청 타임아웃 — 주거용 프록시의 평균 응답 시간은 약 200ms~800ms이지만, 네트워크 지연을 고려해 여유를 둔다.
- 헤더 생성기 옵션을 매 요청마다 재설정하여 브라우저 프로필을 회전한다.
프로덕션 패턴 — 동시성, 쿠키, Crawlee
동시성 제어
대규모 스크래핑에서는 동시성을 제어하지 않으면 프록시 풀이 고갈되거나 타겟 서버가 레이트 리밋을 걸 수 있다. p-limit 패키지로 동시 실행 수를 제한한다. 100개의 URL을 스크래핑할 때 동시성 20으로 설정하면, 한 번에 최대 20개 요청만 실행되고 나머지는 큐에서 대기한다.
afterResponse 훅과 재시도
got의 hooks.afterResponse를 사용하면 응답 상태 코드에 따라 동적으로 재시도할 수 있다. 예를 들어 429(Too Many Requests) 응답을 받으면 5초 대기 후 재시도하도록 설정할 수 있다. 이 훅은 got의 확장 지점 중 하나로, 미들웨어처럼 응답을 가로채고 변형할 수 있다.
쿠키 관리
로그인이 필요한 사이트에서는 tough-cookie의 CookieJar를 사용하여 세션을 유지한다. got-scraping은 cookieJar 옵션을 통해 쿠키를 자동으로 관리한다. got.extend()에 cookieJar를 전달하면 모든 파생 요청에서 동일한 쿠키 저장소를 공유한다.
const { gotScraping } = require('got-scraping');
const pLimit = require('p-limit');
const { CookieJar } = require('tough-cookie');
const crypto = require('crypto');
const limit = pLimit(20);
const cookieJar = new CookieJar();
const client = gotScraping.extend({
cookieJar,
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['macos'],
},
hooks: {
afterResponse: [
(response, retryWithMergedOptions) => {
if (response.statusCode === 429) {
return retryWithMergedOptions({
headers: { 'retry-after': '5' },
});
}
return response;
},
],
},
retry: { limit: 3, statusCodes: [429, 503] },
});
function buildProxyUrl(country, session) {
const username = `user-country-${country}-session-${session}`;
return `http://${username}:pass@gate.proxyhat.com:8080`;
}
const urls = [
'https://example.com/page/1',
'https://example.com/page/2',
'https://example.com/page/3',
];
const results = await Promise.all(
urls.map(url =>
limit(() =>
client(url, {
proxyUrl: buildProxyUrl('US', crypto.randomBytes(6).toString('hex')),
})
)
)
);
Crawlee의 CheerioCrawler로 전환
요청 수가 수백 개를 넘어가면, 크롤러 프레임워크로 전환하는 것이 효율적이다. 웹 스크래핑 사용 사례에서 설명한 것처럼, Crawlee의 CheerioCrawler는 got-scraping을 내부적으로 사용하면서 큐 관리, 재시도, 동시성 제어를 자동으로 처리한다. proxyConfiguration의 newUrlFunction으로 각 요청마다 다른 ProxyHat 세션을 생성할 수 있다.
const { CheerioCrawler } = require('crawlee');
const crypto = require('crypto');
const crawler = new CheerioCrawler({
proxyConfiguration: {
newUrlFunction: () => {
const session = crypto.randomBytes(6).toString('hex');
return `http://user-country-US-session-${session}:pass@gate.proxyhat.com:8080`;
},
},
requestHandler: async ({ $, request }) => {
const title = $('title').text();
console.log(request.url, title);
},
maxConcurrency: 20,
});
await crawler.run(['https://example.com/page/1']);
헤드리스 브라우저가 필요한 순간
got-scraping으로 대부분의 정적 페이지를 스크래핑할 수 있지만, 다음 경우에는 Playwright나 Puppeteer 같은 헤드리스 브라우저가 필요하다:
- JavaScript 렌더링이 필수인 SPA (React, Vue, Angular)
- Cloudflare Turnstile이나 hCaptcha 같은 대화형 챌린지
- 마우스 이동이나 스크롤 이벤트를 요구하는 안티봇 시스템
이 경우에도 ProxyHat 주거용 프록시를 브라우저에 연결할 수 있다. Playwright의 --proxy-server 플래그나 Puppeteer의 page.authenticate()를 사용하면 된다. SERP 추적 사용 사례에서 헤드리스 브라우저와 프록시를 결합한 사례를 확인할 수 있다. 헤드리스 브라우저는 리소스 소모가 크므로, got-scraping으로 처리 가능한 페이지는 got-scraping으로 처리하는 것이 비용 효율적이다.
윤리와 법적 고려사항
주의: 이 글은 공개 데이터 수집을 목적으로 한다. 스크래핑 전 대상 사이트의 이용약관(ToS), robots.txt, 그리고 관할권의 법률(CFAA, GDPR 등)을 확인하라. 인증이 필요한 페이지를 무단으로 스크래핑하는 것은 법적 위험이 있다.
실천 원칙:
- 공식 API를 우선 사용하라. 많은 서비스가 REST/GraphQL API를 제공하며, 스크래핑보다 안정적이다.
- robots.txt 준수:
Disallow된 경로는 스크래핑하지 마라. - 속도 제한: 초당 5~10 요청 이하로 유지하고,
Crawl-delay를 따르라. - 개인정보 보호: GDPR 적용 대상 데이터(유럽 사용자 PII) 수집 시 법적 근거가 필요하다.
- 데이터 최소화: 필요한 데이터만 수집하고, 수집 목적을 명확히 하라.
핵심 요약
핵심 요약
- got-scraping은 got 위에 헤더 생성기와 HTTP/2를 추가한 스크래핑 전용 클라이언트다.
got.extend()로 미들웨어처럼 기본 옵션을 설정한다.headerGeneratorOptions의browsers,devices,operatingSystems로 브라우저 프로필을 지정하여 일관된 헤더 세트를 생성한다.- 주거용 프록시는 헤더와 TLS가 브라우저와 일치할 때 가장 효과적이다 — IP 평판이 마지막 차단 레이어이기 때문이다.
- ProxyHat 사용자 이름 형식
user-country-US-session-abc123으로 지역 타겟팅과 IP 회전을 동시에 제어한다.- 프로덕션에서는
p-limit로 동시성을,afterResponse훅으로 재시도를,CookieJar로 세션을 관리하라.- 대규모 크롤링은 Crawlee의
CheerioCrawler로 전환하여 큐와 재시도를 자동화하라.






