CAPSOLVER
博客
如何在Browser4中通过CapSolver集成解决验证码

如何在浏览器4中通过CapSolver集成解决验证码

Logo of CapSolver

Lucas Mitchell

Automation Engineer

21-Jan-2026

对于网络自动化,Browser4(来自PulsarRPA)已成为一种闪电般的快速、协程安全的浏览器引擎,专为AI驱动的数据提取而设计。其能力支持每台机器每天10万至20万次复杂页面访问,Browser4专为大规模应用而构建。然而,当从受保护的网站提取数据时,验证码挑战成为主要障碍。

CapSolver 为Browser4的自动化功能提供了完美的补充,使您的代理能够无缝地通过验证码保护的页面。这种集成结合了Browser4的高吞吐量浏览器自动化与行业领先的验证码解决能力。


什么是Browser4?

Browser4 是一个高性能、协程安全的浏览器自动化框架,使用Kotlin构建。它专为需要自主代理功能、极端吞吐量以及结合LLM、机器学习和选择器方法的混合数据提取的AI应用而设计。

Browser4 的关键特性

  • 极端吞吐量:每台机器每天10万至20万次复杂页面访问
  • 协程安全:使用Kotlin协程实现高效的并行处理
  • AI驱动的代理:能够进行推理并执行多步骤任务的自主浏览器代理
  • 混合提取:结合LLM智能、机器学习算法和CSS/XPath选择器
  • X-SQL查询:扩展的SQL语法用于复杂数据提取
  • 反机器人功能:配置文件轮换、代理支持和弹性调度

核心API方法

方法 描述
session.open(url) 加载页面并返回 PageSnapshot
session.parse(page) 将快照转换为内存中的文档
driver.selectFirstTextOrNull(selector) 从实时DOM中检索文本
driver.evaluate(script) 在浏览器中执行JavaScript
session.extract(document, fieldMap) 将CSS选择器映射到结构化字段

什么是CapSolver?

CapSolver 是一家领先的验证码解决服务,提供AI驱动的解决方案,用于绕过各种验证码挑战。凭借对多种验证码类型的支持和闪电般的响应时间,CapSolver可无缝集成到自动化工作流中。

支持的验证码类型


为什么将CapSolver与Browser4集成?

当构建与受保护网站交互的Browser4自动化时——无论是用于数据提取、价格监控还是市场研究——验证码挑战都会成为主要障碍。以下是集成的重要性:

  1. 不间断的高吞吐量提取:在不被验证码阻止的情况下保持每天10万+的页面访问量
  2. 可扩展的操作:在并行协程执行中处理验证码
  3. 无缝的工作流程:将验证码解决作为提取流程的一部分
  4. 成本效益高:仅对成功解决的验证码付费
  5. 高成功率:对所有支持的验证码类型具有行业领先的准确性

安装

先决条件

添加依赖项

Maven (pom.xml):

xml 复制代码
<dependencies>
    <!-- Browser4/PulsarRPA -->
    <dependency>
        <groupId>ai.platon.pulsar</groupId>
        <artifactId>pulsar-boot</artifactId>
        <version>2.2.0</version>
    </dependency>

    <!-- HTTP Client for CapSolver -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>

    <!-- JSON 解析 -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>

    <!-- Kotlin 协程 -->
    <dependency>
        <groupId>org.jetbrains.kotlinx</groupId>
        <artifactId>kotlinx-coroutines-core</artifactId>
        <version>1.8.0</version>
    </dependency>
</dependencies>

Gradle (build.gradle.kts):

kotlin 复制代码
dependencies {
    implementation("ai.platon.pulsar:pulsar-boot:2.2.0")
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("com.google.code.gson:gson:2.10.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
}

环境设置

创建一个 application.properties 文件:

properties 复制代码
# CapSolver 配置
CAPSOLVER_API_KEY=your_capsolver_api_key

# LLM 配置(可选,用于AI提取)
OPENROUTER_API_KEY=your_openrouter_api_key

# 代理配置(可选)
PROXY_ROTATION_URL=your_proxy_url

为Browser4创建CapSolver服务

以下是一个可重复使用的Kotlin服务,用于将CapSolver与Browser4集成:

基本CapSolver服务

kotlin 复制代码
import com.google.gson.Gson
import com.google.gson.JsonObject
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit

data class TaskResult(
    val gRecaptchaResponse: String? = null,
    val token: String? = null,
    val cookies: List<Map<String, String>>? = null,
    val userAgent: String? = null
)

class CapSolverService(private val apiKey: String) {
    private val client = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()

    private val gson = Gson()
    private val baseUrl = "https://api.capsolver.com"
    private val jsonMediaType = "application/json".toMediaType()

    private suspend fun createTask(taskData: Map<String, Any>): String {
        val payload = mapOf(
            "clientKey" to apiKey,
            "task" to taskData
        )

        val request = Request.Builder()
            .url("$baseUrl/createTask")
            .post(gson.toJson(payload).toRequestBody(jsonMediaType))
            .build()

        val response = client.newCall(request).execute()
        val result = gson.fromJson(response.body?.string(), JsonObject::class.java)

        if (result.get("errorId").asInt != 0) {
            throw Exception("CapSolver error: ${result.get("errorDescription").asString}")
        }

        return result.get("taskId").asString
    }

    private suspend fun getTaskResult(taskId: String, maxAttempts: Int = 60): TaskResult {
        val payload = mapOf(
            "clientKey" to apiKey,
            "taskId" to taskId
        )

        repeat(maxAttempts) {
            delay(2000)

            val request = Request.Builder()
                .url("$baseUrl/getTaskResult")
                .post(gson.toJson(payload).toRequestBody(jsonMediaType))
                .build()

            val response = client.newCall(request).execute()
            val result = gson.fromJson(response.body?.string(), JsonObject::class.java)

            when (result.get("status")?.asString) {
                "ready" -> {
                    val solution = result.getAsJsonObject("solution")
                    return TaskResult(
                        gRecaptchaResponse = solution.get("gRecaptchaResponse")?.asString,
                        token = solution.get("token")?.asString,
                        userAgent = solution.get("userAgent")?.asString
                    )
                }
                "failed" -> throw Exception("Task failed: ${result.get("errorDescription")?.asString}")
            }
        }

        throw Exception("Timeout waiting for CAPTCHA solution")
    }

    suspend fun solveReCaptchaV2(websiteUrl: String, websiteKey: String): String {
        val taskId = createTask(mapOf(
            "type" to "ReCaptchaV2TaskProxyLess",
            "websiteURL" to websiteUrl,
            "websiteKey" to websiteKey
        ))

        val result = getTaskResult(taskId)
        return result.gRecaptchaResponse ?: throw Exception("No gRecaptchaResponse in solution")
    }

    suspend fun solveReCaptchaV3(
        websiteUrl: String,
        websiteKey: String,
        pageAction: String = "submit"
    ): String {
        val taskId = createTask(mapOf(
            "type" to "ReCaptchaV3TaskProxyLess",
            "websiteURL" to websiteUrl,
            "websiteKey" to websiteKey,
            "pageAction" to pageAction
        ))

        val result = getTaskResult(taskId)
        return result.gRecaptchaResponse ?: throw Exception("No gRecaptchaResponse in solution")
    }

    suspend fun solveTurnstile(
        websiteUrl: String,
        websiteKey: String,
        action: String? = null,
        cdata: String? = null
    ): String {
        val taskData = mutableMapOf(
            "type" to "AntiTurnstileTaskProxyLess",
            "websiteURL" to websiteUrl,
            "websiteKey" to websiteKey
        )

        // 添加可选元数据
        if (action != null || cdata != null) {
            val metadata = mutableMapOf<String, String>()
            action?.let { metadata["action"] = it }
            cdata?.let { metadata["cdata"] = it }
            taskData["metadata"] = metadata
        }

        val taskId = createTask(taskData)
        val result = getTaskResult(taskId)
        return result.token ?: throw Exception("No token in solution")
    }

    suspend fun checkBalance(): Double {
        val payload = mapOf("clientKey" to apiKey)

        val request = Request.Builder()
            .url("$baseUrl/getBalance")
            .post(gson.toJson(payload).toRequestBody(jsonMediaType))
            .build()

        val response = client.newCall(request).execute()
        val result = gson.fromJson(response.body?.string(), JsonObject::class.java)

        return result.get("balance")?.asDouble ?: 0.0
    }
}

解决不同类型的验证码

使用Browser4的reCAPTCHA v2

kotlin 复制代码
import ai.platon.pulsar.context.PulsarContexts
import ai.platon.pulsar.skeleton.session.PulsarSession
import kotlinx.coroutines.runBlocking

class ReCaptchaV2Extractor(
    private val capSolver: CapSolverService
) {
    suspend fun extractWithCaptcha(targetUrl: String, siteKey: String): Map<String, Any?> {
        println("解决 reCAPTCHA v2...")

        // 首先解决验证码
        val token = capSolver.solveReCaptchaV2(targetUrl, siteKey)
        println("验证码已解决,令牌长度: ${token.length}")

        // 创建会话并打开页面
        val session = PulsarContexts.createSession()
        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        // 使用 value 属性将令牌注入隐藏的文本区域(安全)
        driver?.evaluate("""
            (function() {
                var el = document.querySelector('#g-recaptcha-response');
                if (el) el.value = arguments[0];
            })('$token');
        """)

        // 提交表单
        driver?.evaluate("document.querySelector('form').submit();")

        // 等待导航
        Thread.sleep(3000)

        // 从结果页面提取数据
        val document = session.parse(page)

        mapOf(
            "title" to document.selectFirstTextOrNull("h1"),
            "content" to document.selectFirstTextOrNull(".content"),
            "success" to (document.body().text().contains("success", ignoreCase = true))
        )
    }
}

fun main() = runBlocking {
    val apiKey = System.getenv("CAPSOLVER_API_KEY") ?: "your_api_key"
    val capSolver = CapSolverService(apiKey)

    val extractor = ReCaptchaV2Extractor(capSolver)

    val result = extractor.extractWithCaptcha(
        targetUrl = "https://example.com/protected-page",
        siteKey = "6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC"
    )

    println("提取结果: $result")
}

使用Browser4的reCAPTCHA v3

kotlin 复制代码
class ReCaptchaV3Extractor(
    private val capSolver: CapSolverService
) {
    suspend fun extractWithCaptchaV3(
        targetUrl: String,
        siteKey: String,
        action: String = "submit"
    ): Map<String, Any?> {
        println("使用动作: $action 解决 reCAPTCHA v3")

        // 使用自定义页面动作解决 reCAPTCHA v3
        val token = capSolver.solveReCaptchaV3(
            websiteUrl = targetUrl,
            websiteKey = siteKey,
            pageAction = action
        )

        println("令牌获取成功")

        // 创建会话并打开页面
        val session = PulsarContexts.createSession()
        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        // 将令牌注入隐藏输入(使用安全的值赋值)
        driver?.evaluate("""
            (function(tokenValue) {
                var input = document.querySelector('input[name="g-recaptcha-response"]');
                if (input) {
                    input.value = tokenValue;
                } else {
                    var hidden = document.createElement('input');
                    hidden.type = 'hidden';
                    hidden.name = 'g-recaptcha-response';
                    hidden.value = tokenValue;
                    var form = document.querySelector('form');
                    if (form) form.appendChild(hidden);
                }
            })('$token');
        """)

        // 点击提交按钮
        driver?.evaluate("document.querySelector('#submit-btn').click();")

        Thread.sleep(3000)

        val document = session.parse(page)

        mapOf(
            "result" to document.selectFirstTextOrNull(".result-data"),
            "status" to "success"
        )
    }
}

使用Browser4的Cloudflare Turnstile

kotlin 复制代码
class TurnstileExtractor(
    private val capSolver: CapSolverService
) {
    suspend fun extractWithTurnstile(targetUrl: String, siteKey: String): Map<String, Any?> {
        println("解决 Cloudflare Turnstile...")

        // 使用可选元数据(action 和 cdata)解决
        val token = capSolver.solveTurnstile(targetUrl, siteKey)
        println("Turnstile 已解决,令牌长度: ${token.length}")

        // 创建会话并打开页面
        val session = PulsarContexts.createSession()
        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        // 将令牌注入隐藏输入
        driver?.evaluate("""
            (function(tokenValue) {
                var input = document.querySelector('input[name="cf-turnstile-response"]');
                if (input) {
                    input.value = tokenValue;
                } else {
                    var hidden = document.createElement('input');
                    hidden.type = 'hidden';
                    hidden.name = 'cf-turnstile-response';
                    hidden.value = tokenValue;
                    var form = document.querySelector('form');
                    if (form) form.appendChild(hidden);
                }
            })('$token');
        """)

        // 提交表单
        driver?.evaluate("document.querySelector('form').submit();")

        // 等待导航
        Thread.sleep(3000)

        // 从结果页面提取数据
        val document = session.parse(page)

        mapOf(
            "result" to document.selectFirstTextOrNull(".result-data"),
            "status" to "success"
        )
    }
}

val token = capSolver.solveTurnstile(
targetUrl,
siteKey,
action = "login", // optional
cdata = "0000-1111-2222-3333-example" // optional
)
println("Turnstile solved!")

复制代码
    val session = PulsarContexts.createSession()
    val page = session.open(targetUrl)
    val driver = session.getOrCreateBoundDriver()

    // Inject Turnstile token (using safe value assignment)
    driver?.evaluate("""
        (function(tokenValue) {
            var input = document.querySelector('input[name="cf-turnstile-response"]');
            if (input) input.value = tokenValue;
        })('$token');
    """)

    // Submit
    driver?.evaluate("document.querySelector('form').submit();")

    Thread.sleep(3000)

    val document = session.parse(page)

    mapOf(
        "title" to document.selectFirstTextOrNull("title"),
        "content" to document.selectFirstTextOrNull("body")?.take(500)
    )
}

}

复制代码
---

## 与 Browser4 X-SQL 的集成

Browser4 的 X-SQL 提供了强大的提取功能。以下是将其与 CAPTCHA 解决方案结合的方法:

```kotlin
class XSqlCaptchaExtractor(
    private val capSolver: CapSolverService
) {
    suspend fun extractProductsWithCaptcha(
        targetUrl: String,
        siteKey: String
    ): List<Map<String, Any?>> {
        // 预先解决 CAPTCHA
        val token = capSolver.solveReCaptchaV2(targetUrl, siteKey)

        // 创建会话并建立已认证会话
        val session = PulsarContexts.createSession()
        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        driver?.evaluate("""
            (function(tokenValue) {
                var el = document.querySelector('#g-recaptcha-response');
                if (el) el.value = tokenValue;
                document.querySelector('form').submit();
            })('$token');
        """)

        Thread.sleep(3000)

        // 现在解析页面并提取产品数据
        val document = session.parse(page)

        // 使用内置的会话方法提取产品数据
        val products = mutableListOf<Map<String, Any?>>()
        val productElements = document.select(".product-item")

        for ((index, element) in productElements.withIndex()) {
            if (index >= 50) break // LIMIT 50
            products.add(mapOf(
                "name" to element.selectFirstTextOrNull(".product-name"),
                "price" to element.selectFirstTextOrNull(".price")?.let {
                    """(\d+\.?\d*)""".toRegex().find(it)?.groupValues?.get(1)?.toDoubleOrNull() ?: 0.0
                },
                "rating" to element.selectFirstTextOrNull(".rating")
            ))
        }

        return products.map { row ->
            mapOf(
                "name" to row["name"],
                "price" to row["price"],
                "rating" to row["rating"],
                "image_url" to row["image_url"]
            )
        }
    }
}

预认证模式

对于需要在访问内容前解决 CAPTCHA 的网站,请使用预认证工作流:

kotlin 复制代码
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl

class PreAuthenticator(
    private val capSolver: CapSolverService
) {
    data class AuthSession(
        val cookies: Map<String, String>,
        val userAgent: String?
    )

    suspend fun authenticateWithCaptcha(
        loginUrl: String,
        siteKey: String
    ): AuthSession {
        // 解决 CAPTCHA
        val captchaToken = capSolver.solveReCaptchaV2(loginUrl, siteKey)

        // 提交 CAPTCHA 以获取会话 cookies
        val client = OkHttpClient.Builder()
            .cookieJar(object : CookieJar {
                private val cookies = mutableListOf<Cookie>()

                override fun saveFromResponse(url: HttpUrl, cookieList: List<Cookie>) {
                    cookies.addAll(cookieList)
                }

                override fun loadForRequest(url: HttpUrl): List<Cookie> = cookies
            })
            .build()

        val formBody = okhttp3.FormBody.Builder()
            .add("g-recaptcha-response", captchaToken)
            .build()

        val request = Request.Builder()
            .url(loginUrl)
            .post(formBody)
            .build()

        val response = client.newCall(request).execute()

        // 从响应中提取 cookies
        val responseCookies = response.headers("Set-Cookie")
            .associate { cookie ->
                val parts = cookie.split(";")[0].split("=", limit = 2)
                parts[0] to (parts.getOrNull(1) ?: "")
            }

        return AuthSession(
            cookies = responseCookies,
            userAgent = response.request.header("User-Agent")
        )
    }
}

class AuthenticatedExtractor(
    private val preAuth: PreAuthenticator,
    private val capSolver: CapSolverService
) {
    suspend fun extractWithAuth(
        loginUrl: String,
        targetUrl: String,
        siteKey: String
    ): Map<String, Any?> {
        // 预认证
        val authSession = preAuth.authenticateWithCaptcha(loginUrl, siteKey)
        println("使用 ${authSession.cookies.size} 个 cookies 建立了会话")

        // 创建 Browser4 会话
        val session = PulsarContexts.createSession()

        // 配置会话 cookies
        val cookieScript = authSession.cookies.entries.joinToString(";") { (k, v) ->
            "$k=$v"
        }

        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        // 设置 cookies
        driver?.evaluate("document.cookie = '$cookieScript';")

        // 重新加载以使用已认证会话
        driver?.evaluate("location.reload();")
        Thread.sleep(2000)

        // 提取数据
        val document = session.parse(page)

        return mapOf(
            "authenticated" to true,
            "content" to document.selectFirstTextOrNull(".protected-content"),
            "userData" to document.selectFirstTextOrNull(".user-profile")
        )
    }
}

使用 OpenRouter 的 LLM 提取增强

Browser4 的 AI 能力可以通过 OpenRouter 进行增强,OpenRouter 是一个统一的 API 网关,用于访问各种 LLM 模型。这可以实现智能内容提取,适应不同的页面结构。

OpenRouter 服务

kotlin 复制代码
import com.google.gson.Gson
import com.google.gson.JsonObject
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.concurrent.TimeUnit

data class ChatMessage(val role: String, val content: String)
data class ChatCompletion(val content: String, val model: String, val usage: TokenUsage)
data class TokenUsage(val promptTokens: Int, val completionTokens: Int, val totalTokens: Int)

class OpenRouterService(private val apiKey: String) {
    private val client = OkHttpClient.Builder()
        .connectTimeout(60, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .build()

    private val gson = Gson()
    private val baseUrl = "https://openrouter.ai/api/v1"
    private val jsonMediaType = "application/json".toMediaType()

    fun chat(
        messages: List<ChatMessage>,
        model: String = "openai/gpt-4o-mini"
    ): ChatCompletion {
        val payload = mapOf(
            "model" to model,
            "messages" to messages.map { mapOf("role" to it.role, "content" to it.content) }
        )

        val request = Request.Builder()
            .url("$baseUrl/chat/completions")
            .header("Authorization", "Bearer $apiKey")
            .post(gson.toJson(payload).toRequestBody(jsonMediaType))
            .build()

        val response = client.newCall(request).execute()
        val result = gson.fromJson(response.body?.string(), JsonObject::class.java)

        val choice = result.getAsJsonArray("choices")?.get(0)?.asJsonObject
        val content = choice?.getAsJsonObject("message")?.get("content")?.asString ?: ""

        val usage = result.getAsJsonObject("usage")

        return ChatCompletion(
            content = content,
            model = result.get("model")?.asString ?: model,
            usage = TokenUsage(
                promptTokens = usage?.get("prompt_tokens")?.asInt ?: 0,
                completionTokens = usage?.get("completion_tokens")?.asInt ?: 0,
                totalTokens = usage?.get("total_tokens")?.asInt ?: 0
            )
        )
    }

    fun extractStructuredData(html: String, schema: String): String {
        val prompt = """
            从以下 HTML 内容中提取以下数据。
            仅返回符合此模式的 JSON: $schema

            HTML:
            ${html.take(4000)}
        """.trimIndent()

        return chat(listOf(ChatMessage("user", prompt))).content
    }

    fun listModels(): List<String> {
        val request = Request.Builder()
            .url("$baseUrl/models")
            .header("Authorization", "Bearer $apiKey")
            .build()

        val response = client.newCall(request).execute()
        val result = gson.fromJson(response.body?.string(), JsonObject::class.java)

        return result.getAsJsonArray("data")?.mapNotNull {
            it.asJsonObject.get("id")?.asString
        } ?: emptyList()
    }
}

结合 CAPTCHA 解决方案的 LLM 提取

将 CAPTCHA 解决方案与智能内容提取结合:

kotlin 复制代码
class SmartExtractor(
    private val capSolver: CapSolverService,
    private val openRouter: OpenRouterService
) {
    suspend fun extractWithAI(
        targetUrl: String,
        siteKey: String?,
        extractionPrompt: String
    ): Map<String, Any?> {
        // 第一步:如果需要,解决 CAPTCHA
        val captchaToken = siteKey?.let {
            println("解决 CAPTCHA...")
            capSolver.solveReCaptchaV2(targetUrl, it)
        }

        // 第二步:创建会话并打开页面
        val session = PulsarContexts.createSession()
        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        captchaToken?.let { token ->
            driver?.evaluate("""
                (function(tokenValue) {
                    var el = document.querySelector('#g-recaptcha-response');
                    if (el) el.value = tokenValue;
                    var form = document.querySelector('form');
                    if (form) form.submit();
                })('$token');
            """)
            Thread.sleep(3000)
        }

        // 第三步:提取页面内容
        val document = session.parse(page)
        val pageContent = document.body().text().take(8000)

        // 第四步:使用 LLM 提取结构化数据
        val llmResponse = openRouter.chat(listOf(
            ChatMessage("system", "你是一个数据提取助手。从网页中提取结构化数据。"),
            ChatMessage("user", """
                $extractionPrompt

                页面内容:
                $pageContent
            """.trimIndent())
        ))

        println("LLM 使用了 ${llmResponse.usage.totalTokens} 个 token")

        return mapOf(
            "url" to targetUrl,
            "captchaSolved" to (captchaToken != null),
            "extractedData" to llmResponse.content,
            "tokensUsed" to llmResponse.usage.totalTokens
        )
    }
}

// 使用示例
fun main() = runBlocking {
    val capSolver = CapSolverService(System.getenv("CAPSOLVER_API_KEY")!!)
    val openRouter = OpenRouterService(System.getenv("OPENROUTER_API_KEY")!!)

    val extractor = SmartExtractor(capSolver, openRouter)

    val result = extractor.extractWithAI(
        targetUrl = "https://example.com/products",
        siteKey = "6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC",
        extractionPrompt = """
            提取所有产品,包括:
            - 名称
            - 价格(数字格式)
            - 可用性(in_stock/out_of_stock)
            - 评分(1-5)
            以 JSON 数组形式返回。
        """.trimIndent()
    )

    println("提取结果: ${result["extractedData"]}")
}

自适应选择器生成

使用 LLM 为未知页面结构生成 CSS 选择器:

kotlin 复制代码
class AdaptiveExtractor(
    private val capSolver: CapSolverService,
    private val openRouter: OpenRouterService
) {
    suspend fun extractWithAdaptiveSelectors(
        targetUrl: String,
        siteKey: String?,
        dataFields: List<String>
    ): Map<String, Any?> {
        // 首先解决 CAPTCHA
        val token = siteKey?.let { capSolver.solveReCaptchaV2(targetUrl, it) }

        val session = PulsarContexts.createSession()
        val page = session.open(targetUrl)
        val driver = session.getOrCreateBoundDriver()

        token?.let { t ->
            driver?.evaluate("""
                (function(tokenValue) {
                    var el = document.querySelector('#g-recaptcha-response');
                    if (el) el.value = tokenValue;
                })('$t');
            """)
        }

        // 获取页面 HTML 结构
        val htmlSample = driver?.evaluate("document.body.innerHTML")?.toString()?.take(5000) ?: ""

        // 让 LLM 生成选择器
        val selectorPrompt = """
            分析此 HTML 并为以下字段提供 CSS 选择器:${dataFields.joinToString(", ")}

            HTML 样本:
            $htmlSample

            返回 JSON 格式:{"fieldName": "css-selector", ...}
        """.trimIndent()

        val selectorsJson = openRouter.chat(listOf(ChatMessage("user", selectorPrompt))).content
        val selectors = Gson().fromJson(selectorsJson, Map::class.java) as Map<String, String>

        // 使用生成的选择器进行提取
        val document = session.parse(page)
        val extractedData = selectors.mapValues { (_, selector) ->
            document.selectFirstTextOrNull(selector)
        }

        return mapOf(
            "url" to targetUrl,
            "selectors" to selectors,
            "data" to extractedData
        )
    }
}

使用协程的并行提取

Browser4 的协程安全设计支持高效的并行 CAPTCHA 处理:

kotlin 复制代码
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel

data class ExtractionJob(
    val url: String,
    val siteKey: String?
)

data class ExtractionResult(
    val url: String,
    val data: Map<String, Any?>?,
    val captchaSolved: Boolean,
    val error: String?,
    val duration: Long
)

class ParallelExtractor(
    private val capSolver: CapSolverService,
    private val concurrency: Int = 5
) {
    suspend fun extractAll(jobs: List<ExtractionJob>): List<ExtractionResult> = coroutineScope {
        val channel = Channel<ExtractionJob>(Channel.UNLIMITED)
val results = mutableListOf<ExtractionResult>()

        // 将所有任务发送到通道
        jobs.forEach { channel.send(it) }
        channel.close()

        // 以有限并发处理
        val workers = (1..concurrency).map { workerId ->
            async {
                val workerResults = mutableListOf<ExtractionResult>()
                // 每个worker创建自己的session以保证线程安全
                val workerSession = PulsarContexts.createSession()

                for (job in channel) {
                    val startTime = System.currentTimeMillis()
                    var captchaSolved = false

                    try {
                        // 如果提供了站点密钥则解决CAPTCHA
                        val token = job.siteKey?.let {
                            captchaSolved = true
                            capSolver.solveReCaptchaV2(job.url, it)
                        }

                        // 提取数据
                        val page = workerSession.open(job.url)

                        token?.let { t ->
                            val driver = workerSession.getOrCreateBoundDriver()
                            driver?.evaluate("""
                                (function(tokenValue) {
                                    var el = document.querySelector('#g-recaptcha-response');
                                    if (el) el.value = tokenValue;
                                })('$t');
                            """)
                        }

                        val document = workerSession.parse(page)

                        workerResults.add(ExtractionResult(
                            url = job.url,
                            data = mapOf(
                                "title" to document.selectFirstTextOrNull("title"),
                                "h1" to document.selectFirstTextOrNull("h1")
                            ),
                            captchaSolved = captchaSolved,
                            error = null,
                            duration = System.currentTimeMillis() - startTime
                        ))
                    } catch (e: Exception) {
                        workerResults.add(ExtractionResult(
                            url = job.url,
                            data = null,
                            captchaSolved = captchaSolved,
                            error = e.message,
                            duration = System.currentTimeMillis() - startTime
                        ))
                    }
                }

                workerResults
            }
        }

        workers.awaitAll().flatten()
    }
}

// 使用示例
fun main() = runBlocking {
    val capSolver = CapSolverService(System.getenv("CAPSOLVER_API_KEY")!!)

    val extractor = ParallelExtractor(capSolver, concurrency = 5)

    val jobs = listOf(
        ExtractionJob("https://site1.com/data", "6Lc..."),
        ExtractionJob("https://site2.com/data", null),
        ExtractionJob("https://site3.com/data", "6Lc..."),
    )

    val results = extractor.extractAll(jobs)

    val solved = results.count { it.captchaSolved }
    println("完成${results.size}次提取,解决$solved个CAPTCHA")

    results.forEach { r ->
        println("${r.url}: ${r.duration}ms - ${r.error ?: "成功"}")
    }
}

最佳实践

1. 带重试的错误处理

kotlin 复制代码
suspend fun <T> withRetry(
    maxRetries: Int = 3,
    initialDelay: Long = 1000,
    block: suspend () -> T
): T {
    var lastException: Exception? = null

    repeat(maxRetries) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            lastException = e
            println("尝试${attempt + 1}失败: ${e.message}")
            delay(initialDelay * (attempt + 1))
        }
    }

    throw lastException ?: Exception("最大重试次数已用尽")
}

// 使用示例
val token = withRetry(maxRetries = 3) {
    capSolver.solveReCaptchaV2(url, siteKey)
}

2. 余额管理

kotlin 复制代码
suspend fun ensureSufficientBalance(
    capSolver: CapSolverService,
    minBalance: Double = 1.0
) {
    val balance = capSolver.checkBalance()

    if (balance < minBalance) {
        throw Exception("CapSolver余额不足: $${"%.2f".format(balance)}. 请充值。")
    }

    println("CapSolver余额: $${"%.2f".format(balance)}")
}

3. 令牌缓存

kotlin 复制代码
class TokenCache(private val ttlMs: Long = 90_000) {
    private data class CachedToken(val token: String, val timestamp: Long)

    private val cache = mutableMapOf<String, CachedToken>()

    private fun getKey(domain: String, siteKey: String) = "$domain:$siteKey"

    fun get(domain: String, siteKey: String): String? {
        val key = getKey(domain, siteKey)
        val cached = cache[key] ?: return null

        if (System.currentTimeMillis() - cached.timestamp > ttlMs) {
            cache.remove(key)
            return null
        }

        return cached.token
    }

    fun set(domain: String, siteKey: String, token: String) {
        val key = getKey(domain, siteKey)
        cache[key] = CachedToken(token, System.currentTimeMillis())
    }
}

// 使用缓存的示例
class CachedCapSolver(
    private val capSolver: CapSolverService,
    private val cache: TokenCache = TokenCache()
) {
    suspend fun solveReCaptchaV2Cached(websiteUrl: String, websiteKey: String): String {
        val domain = java.net.URL(websiteUrl).host

        cache.get(domain, websiteKey)?.let {
            println("使用缓存令牌")
            return it
        }

        val token = capSolver.solveReCaptchaV2(websiteUrl, websiteKey)
        cache.set(domain, websiteKey, token)

        return token
    }
}

配置选项

设置 描述 默认值
CAPSOLVER_API_KEY 你的CapSolver API密钥 -
OPENROUTER_API_KEY OpenRouter API密钥用于LLM功能 -
PROXY_ROTATION_URL 代理轮换服务URL -
Browser4使用application.properties进行额外配置

结论

将CapSolver与Browser4集成创建了一个强大的高吞吐量网络数据提取解决方案。Browser4的协程安全架构和极致性能,结合CapSolver可靠的CAPTCHA解决能力,使大规模数据提取成为可能。

关键集成模式:

  1. 直接令牌注入:通过JavaScript评估注入解决的令牌
  2. 预认证:在提取前通过解决CAPTCHA建立会话
  3. 并行处理:利用协程进行并发CAPTCHA处理
  4. X-SQL集成:将CAPTCHA解决与Browser4强大的查询语言结合

无论您是构建价格监控系统、市场研究流程还是数据聚合平台,Browser4 + CapSolver组合都提供了生产环境所需的可靠性和可扩展性。


准备开始? 注册CapSolver 并使用优惠码 BROWSER4 获取首次充值6%的额外奖励!


常见问题

什么是Browser4?

Browser4是PulsarRPA开发的高性能协程安全浏览器自动化框架。它用Kotlin编写,专为AI驱动的数据提取设计,支持每台机器每天10万到20万次复杂页面访问。

CapSolver如何与Browser4集成?

CapSolver通过服务类与Browser4集成,该类通过CapSolver API解决CAPTCHA。然后使用Browser4的JavaScript评估功能(driver.evaluate())将解决的令牌注入页面。

CapSolver可以解决哪些类型的CAPTCHA?

CapSolver支持reCAPTCHA v2、reCAPTCHA v3、Cloudflare Turnstile、Cloudflare Challenge(5秒)、AWS WAF、GeeTest v3/v4等。

CapSolver的价格是多少?

CapSolver根据解决的CAPTCHA类型和数量提供具有竞争力的定价。访问capsolver.com查看当前定价。使用代码BROWSER4可获得6%的折扣。

Browser4使用哪种编程语言?

Browser4用Kotlin编写,运行在JVM(Java 17+)上。也可以从Java应用程序中使用。

Browser4能处理并行CAPTCHA解决吗?

可以!Browser4的协程安全设计支持高效的并行处理。结合CapSolver的API,您可以在不同的提取任务中同时解决多个CAPTCHA。

如何找到CAPTCHA站点密钥?

站点密钥通常在页面的HTML源代码中找到:

  • reCAPTCHA:.g-recaptcha元素的data-sitekey属性
  • Turnstile:.cf-turnstile元素的data-sitekey属性
  • 或检查网络请求中的API调用

合规声明: 本博客提供的信息仅供参考。CapSolver 致力于遵守所有适用的法律和法规。严禁以非法、欺诈或滥用活动使用 CapSolver 网络,任何此类行为将受到调查。我们的验证码解决方案在确保 100% 合规的同时,帮助解决公共数据爬取过程中的验证码难题。我们鼓励负责任地使用我们的服务。如需更多信息,请访问我们的服务条款和隐私政策。

更多

Maxun 与 CapSolver 集成
如何在 Maxun 中使用 CapSolver 集成解决验证码

将CapSolver与Maxun集成以进行实际网络爬虫的实用指南。学习如何通过预认证和机器人工作流程处理reCAPTCHA、Cloudflare Turnstile和CAPTCHA保护的网站。

web scraping
Logo of CapSolver

Emma Foster

21-Jan-2026

Browser4 与 CapSolver 集成
如何在浏览器4中通过CapSolver集成解决验证码

高吞吐量Browser4自动化结合CapSolver用于处理大规模网络数据提取中的CAPTCHA挑战。

web scraping
Logo of CapSolver

Lucas Mitchell

21-Jan-2026

什么是爬虫以及如何构建一个
什么是爬虫机器人以及如何构建一个

了解什么是爬虫以及如何构建一个用于自动化数据提取的爬虫。发现顶级工具、安全绕过技术以及道德爬取实践。

web scraping
Logo of CapSolver

Ethan Collins

15-Jan-2026

Scrapy 与 Selenium
Scrapy 与 Selenium:哪个更适合您的网络爬虫项目?

了解Scrapy和Selenium在网页爬虫中的优势和差异。学习哪个工具最适合您的项目,以及如何处理像验证码这样的挑战。

web scraping
Logo of CapSolver

Sora Fujimoto

14-Jan-2026

如何使用 Selenium Driverless 进行高效网络爬虫
如何使用 Selenium Driverless 进行高效网页抓取

学习如何使用Selenium Driverless进行高效网页抓取。本指南提供分步说明,介绍如何设置您的环境、编写您的第一个Selenium Driverless脚本以及处理动态内容。通过避免传统WebDriver管理的复杂性,简化您的网页抓取任务,使数据提取过程更简单、更快捷且更易移植。

web scraping
Logo of CapSolver

Lucas Mitchell

14-Jan-2026

使用 Python 爬取网站时解决 403 禁止访问错误
解决使用 Python 爬取网站时的 403 禁止访问错误

学习如何在使用Python爬取网站时克服403禁止错误。本指南涵盖IP轮换、用户代理伪装、请求节流、身份验证处理以及使用无头浏览器绕过访问限制,从而成功继续网络爬取。

web scraping
Logo of CapSolver

Ethan Collins

13-Jan-2026