MicroArchitectures
H.Ueda
Programmer
ブログ
100万ページのスクレイピングで検証する Python、Go、Rust のパフォーマンス特性と運用コスト
I Scraped 1 Million Pages in Python, Rust, and Go - The Performance Gap is Embarrassing (2026 Benchmarks) という記事を読み、言語選定がインフラコストにどれほどの影響を与えるのか、その実態が非常に興味深かったので自分なりに考察を交えて整理してみました。
まぁ、この手の検証なら Rust かなって思いますけどね。開発効率まで合わせたバランスだと Go なのかなぁ。
システムの運用において、「使い慣れた言語だから」という理由でスタックを選び続けることが、時に月額数千ドルの余計なコストを招くことがあります。今回は、100万ページの大規模スクレイピングという、I/OとCPUの両方に負荷がかかるタスクを通じて、主要な3言語の実力差を見ていきましょう。
ウェブスクレイピングが「究極のテスト」になる理由
ウェブスクレイピングは、単にリクエストを投げるだけの作業ではありません。実際には以下のような、計算機リソースを全方位的に消費する複雑なワークロードです。
- ネットワークI/O: 数千件の同時リクエストとタイムアウト管理。
- CPU負荷: 巨大なHTMLドキュメントの解析、DOM操作、正規表現処理。
- メモリ管理: 大量のレスポンスデータを効率よくバッファリングする能力。
- 例外処理: レート制限、CAPTCHA、接続エラーへの耐性。
これらの処理を並列で行う際、言語ごとの並行処理モデルの設計思想が、そのままパフォーマンスの差として現れます。
flowchart LR
A["ターゲットリスト<br>(100万URL)"] --> B["並行リクエスト処理<br>(I/O待ち)"]
B --> C["HTML解析<br>(CPU集中)"]
C --> D["データ抽出・保存<br>(メモリ管理)"]
subgraph "ボトルネックの発生ポイント"
B
C
D
end
ベンチマークの測定環境
今回の検証では、公平性を保つために各言語でプロダクションレベルの最適化が行われています。実行環境は AWS c7i.2xlarge インスタンス(8 vCPU, 16 GB RAM)で統一されています。
| 項目 | Python (3.12) | Go (1.22) | Rust (1.78) |
|---|---|---|---|
| 主なライブラリ | aiohttp, uvloop, BeautifulSoup4 | net/http, x/net/html | reqwest, tokio, scraper |
| 並行処理モデル | asyncio (500タスク) | Goroutines (2,000件) | tokio tasks (2,000件) |
| 解析手法 | LXMLエンジン | 標準準拠パーサー | CSSセレクタ (scraper) |
検証結果:完了時間とリソース消費
100万ページの処理を完了するまでにかかった時間と、その際のリソース使用率は以下の通りです。
1. 完了時間とスループット
処理速度の面では、Rustが圧倒的な結果を残しました。
| 言語 | 完了時間 | 秒間処理数 (req/s) | Pythonとの比較 |
|---|---|---|---|
| Python | 4時間52分 | 約57 | 1.0倍 (基準) |
| Go | 1時間14分 | 約225 | 約3.9倍 |
| Rust | 42分 | 約397 | 約7.0倍 |
Pythonが全体の半分も終わっていないうちに、Rustはすべての処理を完了させていたことになります。これは、大規模なデータ収集を日常的に行うビジネスにおいては、リードタイムの面で決定的な差になります。
2. メモリとCPUの効率性
リソースの消費効率についても、大きな開きが見られました。
| 言語 | ピーク時メモリ使用量 | 1,000接続あたりのメモリ | CPU平均利用率 |
|---|---|---|---|
| Python | 11.2 GB | 約22.0 MB | 78% (GIL競合) |
| Go | 3.8 GB | 約1.9 MB | 92% |
| Rust | 2.1 GB | 約1.0 MB | 86% |
Pythonの場合、メモリ使用量が11GBを超えており、16GBのインスタンスではOOM(Out Of Memory)によるクラッシュの危険が常に付きまといます。対してRustは、Pythonのわずか5分の1程度のメモリで同等以上の仕事をこなしています。
なぜこれほどの差が生まれるのか
この結果の背景には、各言語のランタイムと並行処理の仕組みの違いがあるかと思います。
Pythonの壁:GILとメモリオーバーヘッド
Pythonには GIL(グローバルインタプリタロック) が存在するため、マルチコアCPUの性能を十分に引き出すのが難しいという側面があります。asyncio を使っても、CPU集中型のHTML解析部分がボトルネックとなり、スレッド間でのロックの取り合いが発生してしまいます。また、オブジェクト一つひとつのメモリ消費量が大きいため、大量のタスクを立ち上げるとすぐにメモリを圧迫してしまいます。
Goの強み:軽量なGoroutine
Goの Goroutine は、OSのスレッドよりもはるかに軽量な「グリーンスレッド」として動作します。数千件の並行処理を立ち上げてもメモリ消費が少なく、標準のスケジューラがマルチコアを効率的に使い切るように設計されているため、ネットワークI/O主体のタスクでは非常にバランスの良いパフォーマンスを発揮します。
Rustの極致:ゼロコスト抽象化
Rustが最速である理由は、ランタイムのオーバーヘッドがほぼゼロであることと、メモリ管理がコンパイル時に決定されることにあります。tokio を使った非同期処理は、ステートマシンの最適化が極限まで行われるため、CPUサイクルを無駄にしません。
「たとえば、重い荷物を運ぶときに、Pythonは豪華な梱包材で包んで慎重に運ぶイメージですが、Rustは中身を剥き出しのまま、最短距離を全速力で駆け抜けるようなイメージ」かもしれません。
まとめ:インフラコストへの影響
元記事で触れられていた「月額4,200ドルのEC2費用」というエピソードは、単なる極端な例ではないと感じます。
PythonからGoやRustへ移行することで、インスタンスのサイズを半分以下に落とし、さらに処理時間を数分の一に短縮できるのであれば、インフラコストは劇的に改善されるはずです。
- Python:開発スピードを最優先し、小規模〜中規模のスクレイピングを行う場合に向いています。
- Go:高い開発効率と、優れた並行処理性能のバランスを取りたい場合に最適です。
- Rust:インフラコストを最小限に抑えたい、あるいは1秒を争うような大規模なパフォーマンスが要求されるシステムに適しています。
何でも新しい言語に書き換えるのが正解とは限りませんが、システムの規模が一定ラインを超えたとき、言語の「実行効率」がそのまま「ビジネスの利益」に直結するという事実は、心に留めておきたいところですね。
参照記事
- I Scraped 1 Million Pages in Python, Rust, and Go - The Performance Gap is Embarrassing (2026 Benchmarks)
- 7 Underused Rust Features Every Senior Developer Knows
- Go Just Killed Rust’s Only Advantage (And Nobody’s Talking About It)
- Go vs Rust Showdown 2026: Performance, Safety, and Why Developers Are Questioning Their Choices
- Inside the Secret Tools Real Rust Teams Use (That Cargo Doesn’t Want You to Know About)
- I Cut Rust Compile Time from 22 Minutes to 38 Seconds — With One .cargo/config.toml Line