Kotlinでのプロキシ利用は、Androidアプリからバックエンドスクレイピングまで幅広い場面で必要になります。本記事では Ktor Client と OkHttp の2つのHTTPクライアントでProxyHatのプロキシを経由する方法を、実行可能なコードとともに解説します。
Kotlinでのプロキシ利用:なぜ必要なのか
データセンタープロキシは高速ですが、多くのウェブサイトやモバイルAPIがホスティングプロバイダーのASNをブロックします。MDNのプロキシ解説にあるように、プロキシはクライアントとサーバー間の中継点として機能しますが、IPの性質(レジデンシャルかデータセンターか)が成否を分けます。
レジデンシャルプロキシは実際のISPに割り当てられたIPを使用するため、ターゲットサイトのIPレピューションチェックを通過しやすくなります。SNSプラットフォームやe-commerceサイト、SERP収集などで Kotlinウェブスクレイピング を行う場合、レジデンシャルプロキシはほぼ必須です。
プロジェクトセットアップ
Gradle依存関係
Ktor 3.xとOkHttpを併用する場合、以下の依存関係を build.gradle.kts に追加します。
// build.gradle.kts
plugins {
kotlin("jvm") version "2.0.21"
application
}
dependencies {
// Ktor Client 3.x with CIO engine
implementation("io.ktor:ktor-client-core:3.0.3")
implementation("io.ktor:ktor-client-cio:3.0.3")
implementation("io.ktor:ktor-client-auth:3.0.3")
// OkHttp for baseline / comparison
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}
OkHttpベースライン:java.net.Proxy
まずはOkHttpで直接プロキシを設定する基本パターンです。OkHttpClient.Builder().proxy() に java.net.Proxy を渡します。
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.PasswordAuthentication
import java.net.Authenticator
val proxyHost = "gate.proxyhat.com"
val proxyPort = 8080
val username = "user-country-DE-city-berlin"
val password = "pass"
// Proxy-Authorization用のAuthenticator設定
val proxyAuth = object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(username, password.toCharArray())
}
}
val client = OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort)))
.authenticator(proxyAuth)
.connectTimeout(10_000, java.util.concurrent.TimeUnit.MILLISECONDS)
.readTimeout(30_000, java.util.concurrent.TimeUnit.MILLISECONDS)
.build()
val request = Request.Builder()
.url("https://httpbin.org/ip")
.build()
client.newCall(request).execute().use { response ->
println(response.body?.string())
// => {"origin": "xxx.xxx.xxx.xxx"} (ドイツ・ベルリンのIP)
}
ProxyHatゲートウェイへのルーティング
geo-targetingとsticky sessionのエンコード
ProxyHatでは、ユーザー名にgeo-targetingとsticky sessionをエンコードします。これにより追加のヘッダーやクエリパラメータなしで柔軟な制御が可能です。
| パラメータ | 形式 | 例 |
|---|---|---|
| 国指定 | user-country-{ISO} |
user-country-US |
| 都市指定 | user-country-DE-city-berlin |
ベルリンのIP |
| Sticky session | -session-{id} |
user-session-abc123 |
| 組み合わせ | user-country-DE-city-berlin-session-abc123 |
ベルリン固定IP |
Ktor Clientでのプロキシ認証
Ktorではプロキシ認証の実装がエンジン固有です。CIOエンジンの場合、defaultRequest で Proxy-Authorization: Basic ヘッダーを明示的に追加する必要があります。詳細は Ktor公式ドキュメント を参照してください。
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import java.util.Base64
val proxyHost = "gate.proxyhat.com"
val proxyPort = 8080
val username = "user-country-DE-city-berlin"
val password = "pass"
// Basic認証ヘッダーを生成
val credentials = "$username:$password"
val encoded = Base64.getEncoder().encodeToString(credentials.toByteArray())
val ktorClient = HttpClient(CIO) {
engine {
proxy = ProxyBuilder.http("http://$proxyHost:$proxyPort")
}
defaultRequest {
header("Proxy-Authorization", "Basic $encoded")
}
}
// リクエスト実行
val response: HttpResponse = ktorClient.get("https://httpbin.org/ip")
println(response.bodyAsText())
// => {"origin": "xxx.xxx.xxx.xxx"} (ベルリンIP)
ktorClient.close()
注意: KtorのCIOエンジンは
Proxy-Authorizationヘッダーを自動付与しないため、defaultRequestで明示的に設定する必要があります。OkHttpエンジンを使用する場合はAuthenticatorパターンが使えます。
SOCKS5プロキシの利用(ポート1080)
SOCKS5を使用する場合、JVMのシステムプロパティで認証情報を設定するのが最もシンプルです。RFC 1928 で規定されたSOCKS5プロトコルは、HTTPプロキシとは異なるトンネリング方式を使用します。
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
// SOCKS5認証用システムプロパティ
System.setProperty("java.net.socks.username", "user-country-US")
System.setProperty("java.net.socks.password", "pass")
val socksClient = HttpClient(CIO) {
engine {
proxy = ProxyBuilder.socks("gate.proxyhat.com", 1080)
}
}
val resp: HttpResponse = socksClient.get("https://httpbin.org/ip")
println(resp.bodyAsText())
socksClient.close()
OkHttpでSOCKS5を使用する場合は Proxy(Proxy.Type.SOCKS, ...) を指定し、Authenticator で認証情報を返します。SOCKS5プロキシはHTTPSトラフィックをより透過的にトンネリングするため、一部の厳格なファイアウォール環境で有利です。
レジデンシャルプロキシで並行スクレイピング
SNSやモバイルAPIはデータセンターアドレス(AWS、GCP、AzureのASN)をブロックすることが多く、レジデンシャルプロキシが必須になります。以下はKotlinコルーチンで async/awaitAll を使い、Semaphore で同時接続数を制御しながら複数URLを並行取得する例です。
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import java.util.Base64
val proxyHost = "gate.proxyhat.com"
val proxyPort = 8080
fun buildClient(countryCode: String): HttpClient {
val username = "user-country-$countryCode-session-${java.util.UUID.randomUUID()}"
val encoded = Base64.getEncoder()
.encodeToString("$username:pass".toByteArray())
return HttpClient(CIO) {
engine {
proxy = ProxyBuilder.http("http://$proxyHost:$proxyPort")
}
defaultRequest {
header("Proxy-Authorization", "Basic $encoded")
header(HttpHeaders.UserAgent, "Mozilla/5.0 (compatible; ScraperBot/1.0)")
}
}
}
suspend fun fetchAll(urls: List<String>, concurrency: Int = 10): List<String> {
val semaphore = Semaphore(concurrency)
return coroutineScope {
urls.map { url ->
async(Dispatchers.IO) {
semaphore.withPermit {
val client = buildClient("US")
try {
val resp = client.get(url)
if (resp.status == HttpStatusCode.OK) {
resp.bodyAsText()
} else {
"ERROR ${resp.status.value}"
}
} catch (e: Exception) {
"FAIL: ${e.message}"
} finally {
client.close()
}
}
}
}.awaitAll()
}
}
// 使用例
fun main() = runBlocking {
val urls = (1..50).map { "https://httpbin.org/delay/1?id=$it" }
val results = fetchAll(urls, concurrency = 10)
results.forEachIndexed { i, r ->
println("[$i] ${r.take(80)}")
}
}
このパターンでは、各コルーチンが独立したsticky sessionを持つクライアントを作成し、Semaphore(10) で同時接続を10に制限しています。これによりターゲットサーバーへの負荷を抑えつつ、約5倍のスループット向上が期待できます(直列実行と比較して)。
本番運用のためのハードニング
OkHttp Authenticatorで407チャレンジに対応
プロキシが 407 Proxy Authentication Required を返した場合、OkHttpの Authenticator インターフェースで自動的に認証ヘッダーを再付与してリトライできます。
import okhttp3.Authenticator
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64
import java.util.concurrent.TimeUnit
val proxyAuthenticator = Authenticator { _: Route?, response: Response ->
if (response.code == 407) {
val credential = Credentials.basic("user-country-US", "pass")
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
} else {
null // 他のステータスコードはリトライしない
}
}
val productionClient = OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.connectionPool(okhttp3.ConnectionPool(20, 5, TimeUnit.MINUTES))
.build()
タイムアウトとリトライのベストプラクティス
- 接続タイムアウト: 10秒〜15秒。プロキシ経由のDNS解決とTCPハンドシェイクには直結より時間がかかります。
- 読み取りタイムアウト: 30秒。大容量HTMLやAPIレスポンスを考慮。
- リトライ: 指数バックオフで3回まで。Ktorでは
HttpRetryプラグインが利用可能です。 - 接続プール: 同一プロキシへの接続を再利用することで、ハンドシェイクオーバーヘッドを削減。OkHttpでは
ConnectionPool(20, 5, TimeUnit.MINUTES)で最大20接続を5分間キープします。
TLS/SSL設定
プロキシ経由のHTTPSでは、プロキシ自体はTLSを終端せずCONNECTメソッドでトンネルを確立します。Ktor/OkHttpはデフォルトでJVMのトラストストアを使用しますが、カスタムCA証明書が必要な場合は以下のように設定します。
// Ktor 3でカスタムTLSを設定する例
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import java.security.KeyStore
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(KeyStore.getDefaultInstance())
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.trustManagers, null)
val tlsClient = HttpClient(CIO) {
engine {
https {
trustManager = tmf.trustManagers[0] as javax.net.ssl.X509TrustManager
}
proxy = ProxyBuilder.http("http://gate.proxyhat.com:8080")
}
}
AndroidのNetworkSecurityConfig
Android 9(API 28)以降では、デフォルトでcleartext HTTPトラフィックがブロックされます。プロキシの認証エンドポイントがHTTPの場合は network_security_config.xml で例外を設定する必要があります。ただし、プロキシ自体はHTTPS CONNECTトンネリングで通信するため、ターゲットURLがHTTPSであれば通常は問題ありません。詳細は Android Network Security Configuration を参照してください。
倫理的スクレイピングと法務
プロキシを使ったスクレイピングは強力ですが、法的・倫理的ガイドラインを遵守することが不可欠です。
- 公開データのみ: ログイン背後のデータやパスワードで保護されたコンテンツは避ける。
- 米国CFAA: Computer Fraud and Abuse Actは、不正アクセスを禁じます。ToS違反がCFAA違反に直結するかは判例により異なりますが、慎重なアプローチが重要です。
- EU GDPR: 個人データの収集・処理には同意ベースまたは合法的根拠が必要です。IPアドレスも個人データとみなされます。
- 公式APIを優先: 多くのプラットフォームが公式APIを提供しており、利用可能な場合はスクレイピングより優先すべきです。
- robots.txtの尊重: 標準的なクローラー礼儀として
robots.txtを確認し、許可されたパスのみにアクセスする。
ProxyHat SDKは、上記のパターンをラップし、セッション管理やローテーションを抽象化します。ProxyHat公式ドキュメント でSDKの詳細を確認できます。また、プロキシプランの料金や利用可能なロケーションについても参照してください。
プロキシタイプの比較
| プロキシタイプ | IPソース | 検知リスク | 適した用途 |
|---|---|---|---|
| レジデンシャル | ISP割当IP | 低 | SNS、e-commerce、SERP収集 |
| モバイル | 携帯キャリアIP | 極低 | モバイルAPI、アプリテスト |
| データセンター | ホスティングプロバイダー | 高 | 高速取得、制限の緩いサイト |
ウェブスクレイピングのユースケースやSERPトラッキングのページで、具体的な活用シナリオを確認できます。
Key Takeaways(重要ポイント)
- Ktor CIOエンジンでは
Proxy-Authorization: BasicヘッダーをdefaultRequestで明示的に設定する必要がある。- OkHttpでは
Authenticatorで407チャレンジに対応し、自動リトライを実装できる。- geo-targetingとsticky sessionはユーザー名にエンコードする(例:
user-country-DE-city-berlin-session-abc123)。- SOCKS5はポート1080を使用し、
java.net.socks.username/passwordシステムプロパティで認証する。- 並行スクレイピングでは
Semaphoreで同時接続数を制限し、ターゲットサーバーへの負荷を抑える。- 本番環境ではタイムアウト、リトライ、接続プール、TLS設定を必ず設定する。
- 倫理的スクレイピングを徹底し、公式APIの利用を優先する。
FAQ
Kotlinでのプロキシ利用とは何ですか?
Kotlinでのプロキシ利用とは、Ktor ClientやOkHttpなどのHTTPクライアントライブラリを経由して、中間プロキシサーバーにリクエストをルーティングすることです。これによりIPアドレスのローテーション、geo-targeting、レート制限の回避などが可能になります。ProxyHatの場合は gate.proxyhat.com:8080(HTTP)または :1080(SOCKS5)をプロキシアドレスとして指定します。
Kotlinでのプロキシ利用がプロキシユーザーにとって重要なのはなぜですか?
データセンタープロキシは多くのウェブサイトやAPIでASNベースのブロック対象になります。レジデンシャルプロキシをKotlinアプリケーションで利用することで、実際のISP IPでリクエストを送信でき、ブロック率を大幅に下げられます。KtorとOkHttpはどちらもプロキシ設定をネイティブサポートしており、Androidとサーバー両方で利用可能です。
Kotlinでのプロキシ利用に最適なプロキシタイプは何ですか?
SNSやe-commerceなどASNブロックが厳しいターゲットではレジデンシャルプロキシが最適です。モバイルAPIやアプリのテストではモバイルプロキシがさらに有効です。データセンターの制限が緩いサイトや高速取得が優先される場合はデータセンタープロキシで十分です。用途に応じて使い分けるのがベストプラクティスです。
Kotlinでのプロキシ利用でブロックを回避するにはどうすればよいですか?
レジデンシャルプロキシの使用、リクエストごとのIPローテーション、適切なUser-Agentの設定、Semaphore による同時接続数の制限、指数バックオフによるリトライを組み合わせることでブロックを回避できます。また、sticky sessionで同一IPを維持しつつセッション単位でローテーションする戦略も有効です。robots.txtの尊重と公式APIの優先も重要です。
ProxyHatのプロキシをKtorで使う場合、認証はどう設定しますか?
Ktor CIOエンジンではプロキシ認証が自動で付与されないため、defaultRequest ブロックで Proxy-Authorization: Basic {base64エンコードされた認証情報} ヘッダーを明示的に追加します。ユーザー名にはgeo-targetingやsticky sessionの情報をエンコードします(例: user-country-DE-city-berlin-session-abc123)。パスワードはProxyHatのダッシュボードで確認できます。






