Использование прокси в Kotlin — это не просто вызов HttpClient с дополнительным параметром. Когда вы скрапите поисковые результаты, парсите цены в e-commerce или собираете данные из социальных приложений, целевые сайты блокируют дата-центровые ASN и требуют реалистичных residential-IP. В этом руководстве мы покажем, как настроить Ktor 3 Client и OkHttp для работы с residential-прокси ProxyHat, реализовать гео-таргетинг, sticky-сессии, конкурентные запросы и production-харденинг.
Зачем нужен Kotlin прокси для веб-скрапинга
Дата-центровые IP легко идентифицируются по ASN. По данным MDN Web Docs, серверы используют комбинацию заголовков, IP-репутации и поведенческих сигналов для блокировки. Многие сайты, особенно социальные платформы и маркетплейсы, фильтруют запросы из известных дата-центровых диапазонов (AWS, Google Cloud, Azure). Residential-прокси используют IP реальных устройств в ISP-сетях, что делает их неотличимыми от обычных пользователей.
В экосистеме Kotlin есть два основных HTTP-клиента: OkHttp — зрелый, широко используемый на Android и бэкенде, и Ktor Client — мультиплатформенный, построенный на корутинах. Оба поддерживают прокси, но с разной механикой аутентификации. Мы покажем оба подхода.
| Критерий | OkHttp | Ktor Client (CIO/OkHttp engine) |
|---|---|---|
| Платформа | JVM / Android | KMP (JVM, Android, iOS, JS) |
| Прокси-аутентификация | Встроенный Authenticator для 407 | Зависит от engine; часто нужен ручной заголовок |
| Корутины | Через suspend-обёртки | Нативная поддержка |
| SOCKS5 | Через java.net.Proxy | Через system properties или OkHttp engine |
Настройка проекта: Ktor 3 и OkHttp
Добавьте зависимости в build.gradle.kts. Для Ktor 3 используйте CIO или OkHttp engine:
dependencies {
implementation("io.ktor:ktor-client-core:3.0.3")
implementation("io.ktor:ktor-client-cio:3.0.3")
implementation("io.ktor:ktor-client-okhttp:3.0.3")
implementation("io.ktor:ktor-client-content-negotiation:3.0.3")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.3")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}
Базовый OkHttpClient с прокси без аутентификации выглядит так:
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080))
val client = OkHttpClient.Builder()
.proxy(proxy)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val request = Request.Builder()
.url("https://httpbin.org/ip")
.build()
client.newCall(request).execute().use { response ->
println(response.body?.string())
}
Этот код отправляет запрос через ProxyHat, но без аутентификации он получит 407 Proxy Authentication Required. Далее мы покажем, как добавить учётные данные.
Маршрутизация через gate.proxyhat.com:8080
ProxyHat использует формат имени пользователя для управления гео-таргетингом и сессиями. Например, user-country-DE-city-berlin направит запрос через residential-IP в Берлине, а добавление -session-abc123 создаст sticky-сессию — все запросы будут идти через один и тот же IP в течение сессии.
Ktor Client: ручной Proxy-Authorization
В Ktor Client прокси-аутентификация зависит от движка. CIO не поддерживает её «из коробки», поэтому нужно добавить заголовок Proxy-Authorization: Basic вручную в defaultRequest:
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 java.util.Base64
fun buildProxyAuthHeader(user: String, pass: String): String {
val credentials = "$user:$pass"
val encoded = Base64.getEncoder().encodeToString(credentials.toByteArray())
return "Basic $encoded"
}
suspend fun main() {
val proxyUser = "user-country-DE-city-berlin-session-abc123"
val proxyPass = "your_password"
val client = HttpClient(CIO) {
engine {
proxy = io.ktor.client.engine.ProxyBuilder.http(
io.ktor.http.Url("http://gate.proxyhat.com:8080")
)
}
defaultRequest {
header("Proxy-Authorization", buildProxyAuthHeader(proxyUser, proxyPass))
}
}
val response: HttpResponse = client.get("https://httpbin.org/ip")
println(response.bodyAsText())
client.close()
}
Важно: заголовок Proxy-Authorization отличается от Authorization. Первый отправляется прокси-серверу, второй — конечному хосту. Не путайте их.
OkHttp: встроенный Authenticator для 407
OkHttp элегантно обрабатывает прокси-аутентификацию через интерфейс Authenticator. Когда прокси возвращает 407, OkHttp вызывает authenticate и повторяет запрос с правильным заголовком:
import okhttp3.Authenticator
import okhttp3.Credentials
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
val proxyUser = "user-country-DE-city-berlin-session-abc123"
val proxyPass = "your_password"
val proxyAuthenticator = Authenticator { _: Route?, response: Response ->
val credential = Credentials.basic(proxyUser, proxyPass)
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
val client = OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator(proxyAuthenticator)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
val request = Request.Builder()
.url("https://httpbin.org/ip")
.build()
client.newCall(request).execute().use { response ->
println(response.body?.string())
}
Этот подход надёжнее ручного заголовка, потому что OkHttp автоматически обрабатывает 407-челленджи и повторяет запрос. Для production-использования это предпочтительный вариант.
SOCKS5 на порту 1080
ProxyHat также поддерживает SOCKS5 на порту 1080. В JVM-среде SOCKS5-аутентификация настраивается через системные свойства java.net.socks.username и java.net.socks.password:
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.InetSocketAddress
import java.net.Proxy
fun socks5Example() {
System.setProperty("java.net.socks.username", "user-country-US-session-xyz789")
System.setProperty("java.net.socks.password", "your_password")
val proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress("gate.proxyhat.com", 1080))
val client = OkHttpClient.Builder()
.proxy(proxy)
.build()
val request = Request.Builder()
.url("https://httpbin.org/ip")
.build()
client.newCall(request).execute().use { response ->
println(response.body?.string())
}
}
Внимание: системные свойства глобальны для всей JVM. Если вы запускаете несколько параллельных задач с разными прокси-учётными данными, SOCKS5 через system properties не подойдёт. В этом случае используйте HTTP-прокси на порту 8080, где учётные данные передаются в заголовке.
Почему residential-прокси нужны для социальных и app-таргетов
Социальные платформы (Instagram, TikTok, X) и мобильные приложения активно блокируют дата-центровые IP. Они используют системы антибота, которые анализируют ASN, поведение, частоту запросов и отпечатки TLS. По данным RFC 7231, HTTP-серверы могут возвращать статус 403 или 429 для запросов, которые не соответствуют ожидаемым паттернам. Residential-прокси с IP из реальных ISP-сетей снижают риск блокировки.
Мобильные прокси — ещё более сильный вариант: они используют IP из сотовых сетей (4G/5G), что делает их неотличимыми от реальных мобильных устройств. Подробнее о типах прокси и их применении см. на странице веб-скрапинг.
Конкурентный скрапинг с корутинами и Semaphore
Для массового сбора данных нужен конкурентный подход. Kotlin-корутины с async/awaitAll и Semaphore для контроля скорости — идиоматичный паттерн:
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 kotlinx.coroutines.*
import java.util.Base64
data class ScrapeResult(val url: String, val status: Int, val body: String)
fun authHeader(user: String, pass: String): String {
val encoded = Base64.getEncoder().encodeToString("$user:$pass".toByteArray())
return "Basic $encoded"
}
suspend fun scrapeConcurrently(urls: List<String>, concurrency: Int = 10) {
val semaphore = Semaphore(concurrency)
val client = HttpClient(CIO) {
engine {
proxy = io.ktor.client.engine.ProxyBuilder.http(
io.ktor.http.Url("http://gate.proxyhat.com:8080")
)
}
defaultRequest {
header("Proxy-Authorization", authHeader(
"user-country-US-session-${System.currentTimeMillis()}",
"your_password"
))
}
}
val results = coroutineScope {
urls.map { url ->
async(Dispatchers.IO) {
semaphore.withPermit {
try {
val resp = client.get(url)
ScrapeResult(url, resp.status.value, resp.bodyAsText())
} catch (e: Exception) {
ScrapeResult(url, -1, e.message ?: "error")
}
}
}
}.awaitAll()
}
results.forEach { println("${it.status} — ${it.url}") }
client.close()
}
suspend fun main() = scrapeConcurrently(
listOf(
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/1"
),
concurrency = 3
)
Semaphore(3) ограничивает до 3 одновременных запросов. Это важно: даже с residential-прокси, слишком высокая частота запросов с одного IP может привести к блокировке. Рекомендуем не более 5–10 одновременных запросов на одну sticky-сессию и ротацию сессий между пакетами запросов.
Production-харденинг
OkHttp: ретраи, таймауты, пул соединений
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
val hardenedClient = OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator { _, response ->
val cred = Credentials.basic("user-country-DE-session-prod01", "your_password")
response.request.newBuilder()
.header("Proxy-Authorization", cred)
.build()
}
.connectionPool(ConnectionPool(20, 5, TimeUnit.MINUTES))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor { chain ->
val request = chain.request()
var attempt = 0
var lastError: Exception? = null
while (attempt < 3) {
try {
return@addInterceptor chain.proceed(request)
} catch (e: java.io.IOException) {
lastError = e
attempt++
Thread.sleep(500L * attempt)
}
}
throw lastError ?: java.io.IOException("max retries exceeded")
}
.build()
Этот клиент включает: пул из 20 соединений с 5-минутным таймаутом простоя, 3 ретрая с экспоненциальной задержкой, и аутентификатор для 407. Для production-нагрузки этого достаточно для большинства задач.
TLS-конфигурация
Для скрапинга HTTPS-сайтов через прокси важно правильно настроить TLS. OkHttp по умолчанию использует системный TrustManager, что обычно достаточно. Но если целевой сайт использует самоподписанные сертификаты (не рекомендуется для production), можно настроить кастомный SSLContext:
import okhttp3.OkHttpClient
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
// ТОЛЬКО ДЛЯ ТЕСТИРОВАНИЯ — не используйте в production!
val unsafeTrustManager = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf<TrustManager>(unsafeTrustManager), java.security.SecureRandom())
val testClient = OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, unsafeTrustManager)
.hostnameVerifier { _, _ -> true }
.build()
Внимание: отключение TLS-верификации в production — это серьёзная уязвимость. Используйте только для локального тестирования.
Android: NetworkSecurityConfig
На Android 9+ (API 28) по умолчанию запрещён plain-text HTTP. Если ваш прокси использует HTTP (как ProxyHat на порту 8080), но целевой сайт — HTTPS, это не проблема: прокси-туннель устанавливается по HTTP, а TLS-рукопожатие идёт через туннель. Однако, если вы обращаетесь к HTTP-ресурсам, добавьте исключение в network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">gate.proxyhat.com</domain>
</domain-config>
</network-security-config>
И не забудьте сослаться на него в AndroidManifest.xml:
<application
android:networkSecurityConfig="@xml/network_security_config">
...
</application>
Этика и правовые аспекты
Веб-скрапинг — мощный инструмент, но он должен использоваться ответственно. В США закон Computer Fraud and Abuse Act (CFAA) регулирует несанкционированный доступ к компьютерным системам. В ЕС GDPR ограничивает сбор и обработку персональных данных. Рекомендации:
- Собирайте только публично доступные данные.
- Соблюдайте
robots.txtи условия обслуживания (ToS) целевых сайтов. - Не скрапите персональные данные без законных оснований.
- Предпочитайте официальные API, если они доступны.
- Ограничивайте частоту запросов, чтобы не перегружать серверы.
Подробнее о доступных локациях ProxyHat см. на странице прокси-локации, а для мониторинга поисковой выдачи — SERP-трекинг.
Настройка ProxyHat и SDK
ProxyHat предоставляет residential, mobile и datacenter-прокси с гео-таргетингом по странам и городам. Тарифы доступны на странице цен. Подключение происходит через единый шлюз gate.proxyhat.com:8080 (HTTP) или :1080 (SOCKS5), с управлением через имя пользователя.
ProxyHat SDK зеркарует ту же логику, что и ручные примеры выше: аутентификация через Proxy-Authorization, гео-флаги в username, sticky-сессии через -session- суффикс. Документация доступна на docs.proxyhat.com.
Ключевые выводы
- OkHttp предпочтительнее для Android и JVM: встроенный
Authenticatorобрабатывает 407 автоматически.- Ktor Client на CIO требует ручного заголовка
Proxy-AuthorizationвdefaultRequest.- Гео-таргетинг и sticky-сессии кодируются в username:
user-country-DE-city-berlin-session-abc123.- SOCKS5 на порту 1080 использует системные свойства
java.net.socks.username/password— глобально для JVM.- Конкурентный скрапинг:
Semaphoreдля ограничения до 5–10 одновременных запросов на сессию.- Production: пул соединений, ретраи с задержкой, TLS-верификация включена,
NetworkSecurityConfigна Android.
FAQ
Что такое использование прокси в Kotlin?
Это настройка HTTP-клиентов (OkHttp или Ktor Client) для маршрутизации запросов через прокси-сервер с аутентификацией, гео-таргетингом и управлением сессиями. В Kotlin это реализуется через java.net.Proxy для OkHttp и ProxyBuilder для Ktor, с заголовком Proxy-Authorization: Basic для передачи учётных данных.
Зачем нужен прокси в Kotlin для веб-скрапинга?
Дата-центровые IP блокируются антибот-системами социальных платформ и маркетплейсов. Residential-прокси используют IP реальных ISP-пользователей, что снижает риск блокировки. Для Kotlin-разработчиков это критично при конкурентном скрапинге, SERP-трекинге и сборе ценовых данных.
Какой тип прокси лучше всего подходит для Kotlin?
Residential-прокси — оптимальный выбор для скрапинга, потому что они имитируют реальный пользовательский трафик. Mobile-прокси ещё лучше для социальных платформ. Datacenter-прокси подходят для задач, где блокировка по ASN не важна — например, для внутреннего мониторинга или тестирования API.
Как избежать блокировок при использовании прокси в Kotlin?
Используйте ротацию IP (меняйте -session- суффикс), ограничивайте конкурентность через Semaphore (5–10 запросов на сессию), добавляйте реалистичные заголовки User-Agent, уважайте robots.txt и делайте паузы между пакетами запросов. OkHttp Authenticator автоматически обрабатывает 407 Proxy Authentication Required.
Можно ли использовать SOCKS5 в Kotlin через ProxyHat?
Да, ProxyHat поддерживает SOCKS5 на порту 1080. В JVM настройка производится через системные свойства java.net.socks.username и java.net.socks.password. Учтите, что эти свойства глобальны для всей JVM, поэтому для нескольких разных прокси-учётных данных используйте HTTP-прокси на порту 8080.






