Node.jsでスクレイピングを本格的に始めると、ほぼ確実に「403 Forbidden」やCAPTCHAページに直面します。Node.jsでgot-scrapingを活用すれば、ブラウザと同等のヘッダーとTLSフィンガープリントを自動生成し、プロキシ統合もシームレスに実現できます。本記事では、got-scrapingをProxyHatのレジデンシャルプロキシと組み合わせて本番運用するための実践的なパターンを解説します。
注意: 本記事は公開データの収集を前提としています。CFAA(Computer Fraud and Abuse Act)やGDPRなど、対象サイトの利用規約と適用法令を遵守してください。robots.txtとレート制限を尊重し、公式APIが利用可能な場合はそちらを優先してください。詳細はFTCのプライバシーガイダンスも参照してください。
Node.jsでgot-scrapingが解決するヘッダーとTLSの課題
生のgotやaxiosでリクエストを送ると、ヘッダーの並び順がNode.jsのHTTPクライアント特有のパターンになります。ChromeやFirefoxはヘッダーを特定の順序で送信し、sec-ch-uaやaccept-languageなどのブラウザ固有ヘッダーを含めます。アンチボットシステムは、これらのヘッダーが欠落していたり、順序が不自然だったりするリクエストを即座にフラグ付けします。
具体的には、以下の要因でリクエストがブロックされます:
- ヘッダー順序の不整合: ブラウザは
Host、Connection、sec-ch-ua、acceptの順で送信しますが、Node.jsクライアントは異なる順序になります。MDNのドキュメントによれば、sec-ch-uaはClient Hints仕様の一部で、モダンブラウザが必ず送信するヘッダーです。 - ブラウザヘッダーの欠落:
sec-ch-ua、sec-fetch-dest、sec-fetch-modeなどが存在しないと、非ブラウザトラフィックとして判定されます。 - TLSフィンガープリント: Node.jsのTLSハンドシェイクはJA3/JA4フィンガープリントでブラウザと異なります。
- HTTP/2の未使用: モダンブラウザはHTTP/2を使用しますが、多くのクライアントはHTTP/1.1のみです。
got-scrapingは、これらの課題を解決するためにApifyが開発したgotベースのHTTPクライアントです。header-generatorライブラリを内蔵し、リアルなブラウザヘッダーセットを生成します。HTTP/2も標準でサポートし、TLSフィンガープリントの不一致を軽減します。
got-scrapingのheader-generatorによる解決策
got-scrapingの中核は、header-generatorライブラリです。このライブラリは、指定したブラウザ・OS・デバイスの組み合わせに応じて、整合性のあるヘッダーセットを生成します。「整合性」という点が重要で、Chrome 120 on Windows 11のヘッダーセットには、その環境固有のsec-ch-ua値やuser-agent文字列が含まれます。
headerGeneratorOptionsの設定
headerGeneratorOptionsで、ブラウザ・デバイス・OSを指定します。これにより、生成されるヘッダーが常に整合性を保ちます。例えばbrowsers: ['chrome']とoperatingSystems: ['windows']を指定すれば、Chrome on Windowsのヘッダーセットが生成されます。
import { gotScraping } from 'got-scraping';
const response = await gotScraping({
url: 'https://example.com',
headerGeneratorOptions: {
browsers: ['chrome'],
devices: ['desktop'],
operatingSystems: ['windows'],
locales: ['ja-JP', 'en-US'],
},
});
console.log(response.body);
この例では、Chrome on Windowsのデスクトップヘッダーセットが生成され、ja-JPとen-USのaccept-languageヘッダーが付与されます。ヘッダーの順序もブラウザと一致するため、アンチボットシステムをより確実に通過できます。
got.extendを使った慣用的なセットアップ
got-scrapingはgotの拡張であるため、got.extend()を使ってデフォルトオプションを設定できます。これにより、全リクエストで共通のヘッダー生成設定とプロキシを適用できます。useHeaderGeneratorオプションをtrueに設定すると、header-generatorが全リクエストで自動的に機能します。
import { gotScraping } from 'got-scraping';
const client = gotScraping.extend({
useHeaderGenerator: true,
headerGeneratorOptions: {
browsers: ['chrome', 'firefox'],
devices: ['desktop'],
operatingSystems: ['windows', 'macos'],
locales: ['ja-JP', 'en-US'],
},
timeout: { request: 30000 },
retry: { limit: 3 },
});
// 全リクエストでブラウザヘッダーが自動生成される
const res = await client.get('https://httpbin.org/headers');
console.log(JSON.parse(res.body).headers);
got.extend()で作成したクライアントインスタンスは、全リクエストで共通の設定を継承します。個別リクエストでheaderGeneratorOptionsを上書きすることも可能で、例えばモバイルヘッダーが必要なリクエストだけdevices: ['mobile']を指定できます。
got-scraping プロキシ統合とProxyHatレジデンシャルプロキシ
ヘッダーとTLSが本物らしくなっても、IPアドレスがデータセンターに属していると、多くのアンチボットシステムでフラグ付けされます。got-scraping プロキシ統合は、proxyUrlオプションでシンプルに実装できます。ProxyHatのレジデンシャルプロキシを使用すれば、ISPに割り当てられた実際の住宅IPアドレスからのリクエストとして認識されます。Webスクレイピングのユースケースでも詳しく解説しています。
| プロキシタイプ | 検知リスク | 平均レイテンシ | 適した用途 |
|---|---|---|---|
| レジデンシャル | 低 | ~200ms | SERPスクレイピング、e-commerce監視 |
| データセンター | 高 | ~50ms | 制限の緩いAPI、社内システム |
| モバイル | 極低 | ~300ms | 高度なアンチボット環境 |
got-scrapingはHTTP/1.1プロキシとHTTP/2プロキシの両方をサポートします。proxyUrlオプションにProxyHatのゲートウェイを指定するだけで、全リクエストがプロキシ経由で送信されます。ProxyHatは世界中のロケーションでレジデンシャルIPを提供しています。
import { gotScraping } from 'got-scraping';
const client = gotScraping.extend({
useHeaderGenerator: true,
headerGeneratorOptions: {
browsers: ['chrome'],
operatingSystems: ['windows'],
locales: ['ja-JP', 'en-US'],
},
proxyUrl: 'http://user-country-US:pass@gate.proxyhat.com:8080',
});
const res = await client.get('https://httpbin.org/ip');
console.log(JSON.parse(res.body).origin); // 米国のIP
SOCKS5プロキシの使用
HTTPプロキシがブロックされる環境では、SOCKS5プロトコルが代替として機能します。ProxyHatではポート1080でSOCKS5を提供しています。got-scrapingはsocks5://スキームをサポートしています。
const client = gotScraping.extend({
useHeaderGenerator: true,
proxyUrl: 'socks5://user-country-DE:pass@gate.proxyhat.com:1080',
});
実践例:セッションローテーションとリトライフック
本番環境では、リクエストごとに異なるIPとセッションを使用することが重要です。ProxyHatのユーザー名にsession-プレフィックスを含めることで、スティッキーセッションを制御できます。各セッションは異なるIPにマッピングされ、同じセッションIDを使い続ける限り同じIPが保持されます。詳細な設定についてはProxyHatドキュメントを参照してください。
import { gotScraping } from 'got-scraping';
import { randomUUID } from 'crypto';
function createClient(countryCode) {
return gotScraping.extend({
useHeaderGenerator: true,
headerGeneratorOptions: {
browsers: ['chrome'],
operatingSystems: ['windows'],
devices: ['desktop'],
locales: ['ja-JP', 'en-US'],
},
timeout: { request: 30000 },
retry: {
limit: 5,
statusCodes: [403, 429, 500, 502, 503],
},
hooks: {
beforeRequest: [
(options) => {
const sessionId = randomUUID().slice(0, 8);
const username = `user-country-${countryCode}-session-${sessionId}`;
options.proxy = `http://${username}:pass@gate.proxyhat.com:8080`;
},
],
afterResponse: [
(response, retryWithMergedOptions) => {
if (response.statusCode === 403) {
const newSession = randomUUID().slice(0, 8);
const newProxy = `http://user-country-${countryCode}-session-${newSession}:pass@gate.proxyhat.com:8080`;
return retryWithMergedOptions({ proxy: newProxy });
}
return response;
},
],
},
});
}
const usClient = createClient('US');
const res = await usClient.get('https://httpbin.org/ip');
console.log(JSON.parse(res.body).origin);
このパターンでは、beforeRequestフックでリクエストごとに新しいセッションIDを生成し、afterResponseフックで403応答時に自動的に新しいIPでリトライします。これにより、IPブロックを動的に回避できます。
プロダクションパターン
p-limitによる並行制御
大量のリクエストを送信する場合、並行数を制限しないと対象サーバーに負荷をかけ、ブロックの原因になります。p-limitを使えば、同時実行数を簡単に制御できます。100並行でリクエストを送ると対象サイトにDDoSに近い負荷をかける可能性がありますが、10並行に制限すれば安全な範囲で収集できます。
import pLimit from 'p-limit';
import { gotScraping } from 'got-scraping';
import { randomUUID } from 'crypto';
const limit = pLimit(10); // 最大10並行
const client = gotScraping.extend({
useHeaderGenerator: true,
headerGeneratorOptions: {
browsers: ['chrome'],
operatingSystems: ['windows'],
locales: ['ja-JP', 'en-US'],
},
});
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
];
const results = await Promise.all(
urls.map((url) =>
limit(async () => {
const sessionId = randomUUID().slice(0, 8);
const proxyUrl = `http://user-country-US-session-${sessionId}:pass@gate.proxyhat.com:8080`;
try {
const res = await client.get(url, { proxyUrl });
return { url, status: res.statusCode, body: res.body };
} catch (err) {
return { url, error: err.message };
}
})
)
);
console.log(results);
Cookie Jarによるセッション維持
ログインが必要なサイトや、セッションCookieに依存するサイトでは、tough-cookieベースのCookie Jarを使用してリクエスト間でCookieを維持できます。
import { gotScraping } from 'got-scraping';
import { CookieJar } from 'tough-cookie';
const cookieJar = new CookieJar();
const client = gotScraping.extend({
useHeaderGenerator: true,
cookieJar,
proxyUrl: 'http://user-country-US-session-login01:pass@gate.proxyhat.com:8080',
});
// 1回目のリクエストでCookieを取得
await client.get('https://example.com/login');
// 2回目のリクエストではCookieが自動的に送信される
const res = await client.get('https://example.com/dashboard');
console.log(res.body);
Cookie Jarとスティッキーセッションを組み合わせることで、ログイン状態を維持したままスクレイピングできます。セッションIDを固定すれば、同じIPとCookieセットが継続して使用されます。
CrawleeのCheerioCrawlerへの統合
got-scrapingはCrawleeの一部として設計されており、CheerioCrawlerにシームレスに統合できます。Crawleeを使えば、キュー管理、自動リトライ、プロキシローテーションを宣言的に処理できます。
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
useHeaderGenerator: true,
headerGeneratorOptions: {
browsers: ['chrome'],
operatingSystems: ['windows'],
locales: ['ja-JP', 'en-US'],
},
proxyConfiguration: {
newUrlFunction: () => {
const session = Math.random().toString(36).slice(2, 10);
return `http://user-country-US-session-${session}:pass@gate.proxyhat.com:8080`;
},
},
maxRequestsPerMinute: 60,
requestHandler: async ({ $, request }) => {
const title = $('title').text();
console.log(`${request.url}: ${title}`);
},
});
await crawler.run([
'https://example.com/page1',
'https://example.com/page2',
]);
CheerioCrawlerは、maxRequestsPerMinuteでレート制限を自動管理し、失敗したリクエストを自動的にリトライします。プロキシローテーションはproxyConfigurationで宣言的に設定でき、各リクエストで新しいセッションIPを取得できます。SERPトラッキングのユースケースでもCrawleeパターンが有効です。
ヘッドレスブラウザが必要なケース
got-scrapingは強力ですが、JavaScriptレンダリングが必要なページ(SPA、動的コンテンツ)では限界があります。以下のケースでは、PlaywrightやPuppeteerなどのヘッドレスブラウザが必要です:
- コンテンツがクライアントサイドでレンダリングされる(React、Vue、Angular)
- Cloudflare TurnstileやreCAPTCHA v3などのチャレンジが存在する
- WebSocketやXHRリクエストでデータを読み込むページ
- マウス操作やスクロールで遅延読み込みされるコンテンツ
ヘッドレスブラウザを使用する場合でも、プロキシは同様に機能します。Playwrightの--proxy-serverフラグや、Puppeteerのpage.authenticate()でProxyHatのゲートウェイを指定できます。ヘッドレスブラウザのTLSフィンガープリントは実際のブラウザと同一なため、プロキシIPが適切であれば、非常に高い成功率が期待できます。コンテナ化する場合は、ブラウザインスタンスごとに独立したプロキシセッションを割り当てるアーキテクチャを推奨します。
エシックスと法務
スクレイピングは強力な技術ですが、法的・倫理的境界を守ることが不可欠です。以下のガイドラインを推奨します:
- 公開データのみを収集: ログイン背後のデータや、明確に非公開とマークされたデータは収集しないでください。
- robots.txtを尊重: 対象サイトのrobots.txtで禁止されているパスはスクレイピングしないでください。
- レート制限の遵守: 対象サーバーに過度な負荷をかけないよう、適切な遅延と並行制限を設定してください。
- 利用規約の確認: 多くのサイトが利用規約でスクレイピングを制限しています。CFAAやGDPRの下で法的リスクが生じる可能性があります。
- 公式APIを優先: 対象サイトが公式APIを提供している場合は、スクレイピングよりAPIを使用してください。
スクレイピングの合法性は管轄区域や対象サイトによって異なります。本記事の内容は法的助言ではなく、実装前に適格な法律専門家に相談することを推奨します。
Key Takeaways
- got-scrapingはヘッダー問題を解決: header-generatorがブラウザと整合性のあるヘッダーセットを自動生成し、
sec-ch-uaやaccept-languageを適切に設定します。 - got.extendで慣用的な設定:
useHeaderGeneratorとheaderGeneratorOptionsをgot.extend()で設定すれば、全リクエストで一貫したヘッダーが生成されます。 - レジデンシャルプロキシが鍵: ヘッダーとTLSが本物らしくても、データセンターIPは検知されます。ProxyHatのレジデンシャルプロキシ(
gate.proxyhat.com:8080)でISPベースのIPを使用してください。 - セッションローテーションで回避:
beforeRequestフックでリクエストごとに新しいセッションIDを生成し、afterResponseフックで403時に自動リトライしてください。 - 並行制御は必須: p-limitやCrawleeの
maxRequestsPerMinuteで並行数を制限し、対象サーバーへの負荷を抑えてください。 - Crawleeでスケール: CheerioCrawlerを使えば、キュー管理、自動リトライ、プロキシローテーションを宣言的に処理できます。
ProxyHatのレジデンシャルプロキシを今すぐお試しいただけます。料金プランをご確認いただくか、上記のコード例を参考に実装を始めてください。






