Hunt

Hunt 验证码是一种用于博彩平台的反机器人系统,用于检测自动化行为。它会跟踪用户行为,并在检测到可疑活动时启动交互式验证。
-
要完成此任务,请使用您自己的代理。
-
我们的解决系统有两种工作模式:生成 X-HD(指纹) 和 验证码求解。如果您只需要生成 X-HD,请不要传递
data参数。如果您需要解决验证码,请在data中传递目标网站在特定操作时生成的令牌(例如请求短信时)。
请求参数
重要提示:某些参数的值是动态的 — 它们会在每次渲染包含 Hunt 的页面时发生变化。
请在创建任务前立即提取这些参数,以避免在解决过程中出现错误。
自动提取参数的示例请参见章节 如何获取 meta.token 和 Hunt CAPTCHA 解决示例。
type<string>requiredCustomTask
class<string>requiredHUNT
websiteURL<string>required包含 Hunt 验证码的页面地址。
apiGetLib (在 metadata 内)<string>requiredapi.js 文件的完整链接。
示例:
https://www.example.com/hd-api/external/apps/<hash>/api.js
请按以下格式传递:
"apiGetLib":"https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js"
您可以在包含 Hunt 验证码的页面中,通过 DevTools(Network 或 Elements 标签)找到该链接。
使用关键字搜索:hd-api 或 api.js。
data (在 metadata 内)<string>optional当选择验证码求解模式时,需要传递 data(详见下文说明)。
userAgent<string>optional浏览器的 User-Agent。
请仅传递来自 Windows 操作系统的最新 UA。目前为:userAgentPlaceholder
proxyType<string>requiredhttp - 常规 HTTP/HTTPS 代理;
https - 当 http 不可用时使用(某些自定义代理必填);
socks4 - SOCKS4 代理;
socks5 - SOCKS5 代理。
proxyAddress<string>required代理 IP 地址(IPv4/IPv6)。禁止使用:
- 透明代理
- 本地机器代理
proxyPort<integer>required代理端口
proxyLogin<string>required代理登录名
proxyPassword<string>required代理密码
该解决方案支持两种工作模式:
1. 生成 X-HD(指纹)
- 在此模式下,您无需传递
data。 - 创建任务后,您将获得 X-HD —— 一个与您的 IP 绑定的唯一指纹,可用于后续向网站发送请求。
2. 验证码求解
- 在此模式下,您需要在参数
data中传递网站在特定操作时生成的令牌(meta.token),例如在请求短信时。 - 创建任务后,您将获得验证码解决结果,即一个可直接在网站上使用的令牌。
何时使用每种模式
| 场景 | 是否需要传递 data? |
|---|---|
| 初始初始化 | 否 |
| 需要获取 X-HD | 否 |
| 网站返回 Captcha error | 是 |
已获得 meta.token | 是 |
完整工作流程示例
- 创建任务 不传递
data - 从我们的服务获取 X-HD
- 使用 X-HD 向网站发送请求
- 获取
meta.token - 创建任务 并设置
data = meta.token - 获取解决结果
- 将解决结果提交到网站
如果更换代理,需要重新获取 X-HD。
模式 1:生成 X-HD
用于获取与 IP 绑定的 X-HD 令牌。
创建用于生成 X-HD 的任务方法
https://api.capmonster.cloud/createTask
请求
{
"type": "CustomTask",
"class": "HUNT",
"websiteURL": "https://example.com",
"metadata": {
"apiGetLib": "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js"
},
"userAgent": "userAgentPlaceholder",
"proxyType": "http",
"proxyAddress": "8.8.8.8",
"proxyPort": 8080,
"proxyLogin": "proxyLoginHere",
"proxyPassword": "proxyPasswordHere"
}
}
响应
{
"errorId": 0,
"taskId": 407533072
}
模式 2:验证码求解
当网站返回 captcha 错误并提供 meta.token 后使用。
如何获取 meta.token
- 向网站发送请求(例如请求短信验证码)
- 获取 X-HD
-1-feced69f9da880f442fb82a0d025775d.png)
- 网站返回:
{
"errors":[{"code":"113","title":"Captcha error"}],
"meta":{
"token":"SITE_META_TOKEN"
}
}
-cb52653b917e55094fa4dbc891cd1746.png)
创建用于验证码求解的任务方法
需要将 meta.token 的值传递到 data 参数中。
https://api.capmonster.cloud/createTask
请求
{
"type": "CustomTask",
"class": "HUNT",
"websiteUrl": "https://example.com",
"metadata": {
"apiGetLib": "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js",
"data": "kufyHK/s/jTNU...AfwIW", // META_TOKEN 的值
},
"userAgent": "userAgentPlaceholder",
"proxyType": "http",
"proxyAddress": "8.8.8.8",
"proxyPort": 8080,
"proxyLogin": "proxyLoginHere",
"proxyPassword": "proxyPasswordHere"
}
响应
{
"errorId": 0,
"taskId": 407533072
}
获取任务结果的方法
使用 getTaskResult 方法来获取 X-HD 指纹或 Hunt 验证码的解决结果。
solution.data.token 的值:
-
在生成 X-HD 模式下 —— 这是 X-HD 令牌,需要在向目标网站发送请求时使用。
-
在验证码求解模式下 ——
solution.data.token是验证码解决令牌,需要将其提交回网站以确认操作。
https://api.capmonster.cloud/getTaskResult
请求
{
"clientKey":"API_KEY",
"taskId": 407533072
}
响应
{
"errorId": 0,
"status": "ready",
"solution": {
"data": {
"token": "6IyDCCpDdSK...YGs1Wug/z/kLNSpjewI="
}
}
}
Cookies 的使用
目标网站可能使用特殊的 cookies 来管理会话。如果这些 cookies 缺失或未随请求发送,服务器可能会拒绝请求或要求重新验证。
从网站获取的 cookies 示例:
platform_type=desktop; typeBetNames=full; _glhf=1234567890; coefview=0; visit=1-123abc012345d1be726746568edc62d9; fast_coupon=true;
v3fr=1; lng=en; flaglng=en; SESSION=12a3aea8cdcfdbb9e7df8ee99b526a84; auid=ab0dW2mqlz1Tjo2AAwplAg==; ggru=195; che_g=12abcede-691f-c7b2-d1e8-8488bc557d98
Cookies 的基本工作原理:
-
首先需要打开网站页面,或者向目标页面发送 GET 请求。这样服务器会初始化用户会话并设置必要的 cookies。
-
在响应中,服务器会通过
Set-Cookie头返回 cookies。这些 cookies 可能包含会话 ID、安全参数以及反机器人相关数据。 -
需要保存这些 cookies,因为后续请求会使用它们来识别客户端。
-
在向网站 API 发送后续请求时,需要在
Cookie请求头中将 cookies 发送回服务器,以便服务器确认该请求属于同一会话。 -
所有请求应在同一会话中完成,并使用相同的 IP 地址(或代理) 和 User-Agent。否则服务器可能会将请求视为可疑并要求重新验证。
-
有时部分 cookies 是在客户端创建的(例如页面上的 JavaScript)。在自动化或测试过程中,可能需要直接从浏览器提取 cookies,或以类似方式生成这些 cookies。
Hunt CAPTCHA 解决示例
下面的示例展示了与使用 Hunt CAPTCHA 的网站交互的典型流程。代码演示了如何:
- 打开网站页面以获取初始 cookies;
- 生成可能在客户端创建的额外 cookies;
- 通过 CapMonster Cloud 创建用于请求验证的 fingerprint(X‑HD);
- 向网站 API 发送请求以触发某个操作(例如发送 SMS);
- 将获取的数据传递给验证码识别服务;
- 在获得验证码解决结果后,向 API 发送 最终请求 并附带所需参数。
同时,还需要使用 最新的 HTTP 请求头以及正确的 User-Agent。
- JavaScript
- Python
显示代码 (Node.js)
import { gotScraping } from "got-scraping";
import crypto from "crypto";
/* ================= CONFIG(建议存储在 .env 中) ================= */
// CapMonster Cloud API、网站以及用户数据的配置
const API_KEY = "YOUR_API_KEY"; // 您的 CapMonster Cloud API 密钥
const SOLVER_URL = "https://api.capmonster.cloud";
const BASE_URL = "https://example.com"; // 网站的基础 URL
const API_GET_LIB =
"https://example.com/hd-api/external/apps/a1047eab1035d58682a53557e0b2a75edbfd15fd/api.js"; // 网站的 api.js
const PHONE = "91123456789"; // 用于注册的电话号码(不包含国家代码)
const COUNTRY_CODE = "54"; // 国家代码
const UA = "userAgentPlaceholder"; // 请求使用的 User-Agent
/* ================= 代理设置 ================= */
const PROXY_HOST = "proxyAddress";
const PROXY_PORT = 8080;
const PROXY_USER = "proxyLogin";
const PROXY_PASS = "proxyPassword";
const PROXY = `http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}`;
/* ================= HEADERS ================= */
// HTTP 请求头
// htmlHeaders — 用于 GET 页面请求
// apiHeaders — 用于网站 API 请求
const htmlHeaders = {
accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
"cache-control": "no-cache",
pragma: "no-cache",
priority: "u=0, i",
"sec-ch-ua": `"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"`,
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": `"Windows"`,
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": UA,
};
const apiHeaders = {
accept: "application/vnd.api+json",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
"cache-control": "no-cache",
pragma: "no-cache",
priority: "u=1, i",
"content-type": "application/vnd.api+json",
origin: BASE_URL,
referer: BASE_URL + "/", // 网站当前使用的 referer
"sec-ch-ua": `"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"`,
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": `"Windows"`,
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": UA,
"x-requested-with": "XMLHttpRequest",
};
/* ================= UTILS ================= */
// 辅助函数
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
function randomString(len = 20) {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < len; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
/* ================= CAPMONSTER CLOUD ================= */
// 创建任务并等待验证码解决结果
async function createTask(task) {
const { body } = await gotScraping.post(`${SOLVER_URL}/createTask`, {
json: { clientKey: API_KEY, task },
responseType: "json",
});
if (body.errorId !== 0)
throw new Error("createTask error: " + JSON.stringify(body));
return body.taskId;
}
async function waitResult(taskId) {
while (true) {
const { body } = await gotScraping.post(`${SOLVER_URL}/getTaskResult`, {
json: { clientKey: API_KEY, taskId },
responseType: "json",
});
if (body.status === "ready")
return body.solution?.token || body.solution?.data?.token;
await delay(3000);
}
}
/* ================= MAIN FLOW ================= */
// 带验证码的自动注册主流程
(async () => {
try {
// ---------------- STEP 1: 获取 cookies ----------------
console.log("STEP 1 — Getting cookies...");
const base = await gotScraping.get(
BASE_URL + "/registration?type=phone_reg", // 请替换为网站当前的注册页面
{
headers: htmlHeaders,
proxyUrl: PROXY,
},
);
// 保存服务器返回的 cookies
let cookies = (base.headers["set-cookie"] || [])
.map((c) => c.split(";")[0])
.join("; ");
// 生成客户端创建的额外 cookies
const che_g = crypto.randomUUID();
const ggru = randomString();
cookies += `; che_g=${che_g}`;
cookies += `; ggru=${ggru}`;
console.log("Cookies:", cookies);
// ---------------- STEP 2: 创建 fingerprint ----------------
console.log("\nSTEP 2 — Creating fingerprint...");
const fingerprintTaskId = await createTask({
type: "CustomTask",
class: "HUNT",
websiteURL: BASE_URL + "/",
userAgent: UA,
metadata: { apiGetLib: API_GET_LIB },
proxyType: "http",
proxyAddress: PROXY_HOST,
proxyPort: PROXY_PORT,
proxyLogin: PROXY_USER,
proxyPassword: PROXY_PASS,
});
const xhd = await waitResult(fingerprintTaskId);
console.log("X-HD:", xhd);
// ---------------- STEP 3: 发送 SMS 请求 ----------------
console.log("\nSTEP 3 — Trigger SMS...");
const smsResp = await gotScraping.post(
BASE_URL + "/web-api/api/web/registration/v2/sms", // 请替换为网站当前的 API endpoint
{
headers: { ...apiHeaders, cookie: cookies, "x-hd": xhd },
json: {
data: { attributes: { phone: PHONE, country_code: COUNTRY_CODE } },
},
proxyUrl: PROXY,
responseType: "json",
},
);
const metaToken = smsResp.body?.meta?.token;
if (!metaToken) {
console.log("SMS response:", smsResp.body);
throw new Error("meta.token not received");
}
console.log("meta.token:", metaToken);
// ---------------- STEP 4: 解决验证码 ----------------
console.log("\nSTEP 4 — Solving Hunt captcha...");
const solveTaskId = await createTask({
type: "CustomTask",
class: "HUNT",
websiteURL: BASE_URL + "/",
userAgent: UA,
metadata: { apiGetLib: API_GET_LIB, data: metaToken },
proxyType: "http",
proxyAddress: PROXY_HOST,
proxyPort: PROXY_PORT,
proxyLogin: PROXY_USER,
proxyPassword: PROXY_PASS,
});
const captchaToken = await waitResult(solveTaskId);
console.log("Captcha token:", captchaToken);
// ---------------- STEP 5: 发送最终请求 ----------------
console.log("\nSTEP 5 — Sending final request...");
const finalResp = await gotScraping.post(
BASE_URL + "/api/web/registration/v2/sms", // 请替换为网站当前的 API endpoint
{
headers: { ...apiHeaders, cookie: cookies, "x-hd": xhd },
body: JSON.stringify({
data: {
attributes: {
phone: PHONE,
country_code: parseInt(COUNTRY_CODE),
captcha: captchaToken,
},
},
}),
proxyUrl: PROXY,
},
);
console.log("\nFINAL RESPONSE HEADERS:", finalResp.headers);
console.log("\nFINAL RESPONSE BODY:", finalResp.body);
} catch (err) {
console.error("\nFATAL ERROR:");
console.error(err);
}
})();
显示代码
import requests
import uuid
import random
import string
import time
# ================= CONFIG (建议保存在 .env 中) =================
# CapMonster Cloud API、网站以及用户数据的配置
API_KEY = "YOUR_CAPMONSTER_API_KEY" # 替换为你的 CapMonster Cloud API Key
SOLVER_URL = "https://api.capmonster.cloud"
BASE_URL = "https://example.com" # 网站基础 URL
API_GET_LIB = "https://example.com/hd-api/external/apps/c1e24d5857463de4393e3f1489b00ebd4495da64/api.js" # 替换为网站中实际使用的 api.js 地址
PHONE = "9102345678" # 注册使用的手机号(不包含国家代码)
COUNTRY_CODE = "7" # 国家代码
UA = "userAgentPlaceholder" # User-Agent
# ================= 代理设置 =================
PROXY_HOST = "proxyAdress"
PROXY_PORT = 8080
PROXY_LOGIN = "proxyLogin"
PROXY_PASSWORD = "proxyPassword"
PROXY = f"http://{PROXY_LOGIN}:{PROXY_PASSWORD}@{PROXY_HOST}:{PROXY_PORT}"
proxies = {
"http": PROXY,
"https": PROXY
}
# ================= HEADERS =================
# HTTP 请求头
# html_headers — 用于普通页面 GET 请求
# api_headers — 用于网站 API 请求
html_headers = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
"cache-control": "no-cache",
"pragma": "no-cache",
"priority": "u=0, i",
"sec-ch-ua": '"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": UA
}
api_headers = {
"accept": "application/vnd.api+json",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,ru;q=0.8",
"cache-control": "no-cache",
"pragma": "no-cache",
"priority": "u=1, i",
"content-type": "application/vnd.api+json",
"origin": BASE_URL,
"referer": BASE_URL + "/en", # 替换为网站中实际使用的 referer
"sec-ch-ua": '"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": UA,
"x-requested-with": "XMLHttpRequest"
}
# ================= UTILS =================
# 辅助函数
def delay(ms):
"""毫秒级延时"""
time.sleep(ms / 1000)
def random_string(length=20):
"""生成随机字符串(字母 + 数字)"""
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
# ================= CAPMONSTER CLOUD =================
# 与 CapMonster Cloud 交互的函数(创建任务并等待验证码解决)
def create_task(task):
"""在 CapMonster Cloud 创建任务"""
r = requests.post(
SOLVER_URL + "/createTask",
json={
"clientKey": API_KEY,
"task": task
}
)
data = r.json()
if data["errorId"] != 0:
raise Exception("createTask error: " + str(data))
return data["taskId"]
def wait_result(task_id):
"""等待任务完成"""
while True:
r = requests.post(
SOLVER_URL + "/getTaskResult",
json={
"clientKey": API_KEY,
"taskId": task_id
}
)
data = r.json()
if data["status"] == "ready":
solution = data.get("solution", {})
return solution.get("token") or solution.get("data", {}).get("token")
delay(3000)
# ================= SESSION =================
# 创建 requests 会话,用于在多个请求之间保持 cookies
session = requests.Session()
# ================= MAIN =================
# 自动注册流程(包含验证码处理)
try:
# ---------------- STEP 1: 获取 cookies ----------------
print("STEP 1 — Getting cookies")
r = session.get(
BASE_URL + "/en/registration?type=phone", # 替换为网站实际的注册页面
headers=html_headers,
proxies=proxies
)
# 保存服务器返回的 cookies
cookies = "; ".join([f"{c.name}={c.value}" for c in session.cookies])
# 生成客户端侧创建的 cookies
che_g = str(uuid.uuid4())
ggru = random_string()
cookies += f"; che_g={che_g}"
cookies += f"; ggru={ggru}"
print("Cookies:", cookies)
# ---------------- STEP 2: 创建 fingerprint ----------------
print("\nSTEP 2 — Creating fingerprint")
fingerprint_task = create_task({
"type": "CustomTask",
"class": "HUNT",
"websiteURL": BASE_URL + "/en/",
"userAgent": UA,
"metadata": {
"apiGetLib": API_GET_LIB
},
"proxyType": "http",
"proxyAddress": PROXY_HOST,
"proxyPort": PROXY_PORT,
"proxyLogin": PROXY_LOGIN,
"proxyPassword": PROXY_PASSWORD
})
# 获取结果(X-HD),用于后续 API 请求
xhd = wait_result(fingerprint_task)
print("X-HD:", xhd)
# ---------------- STEP 3: 触发 SMS 请求 ----------------
print("\nSTEP 3 — Trigger SMS")
sms_resp = session.post(
BASE_URL + "/web-api/api/web/registration/v2/sms", # 替换为网站实际 API endpoint
headers={**api_headers, "cookie": cookies, "x-hd": xhd},
json={
"data": {
"attributes": {
"phone": PHONE,
"country_code": COUNTRY_CODE
}
}
},
proxies=proxies
)
print("Status:", sms_resp.status_code)
try:
sms_data = sms_resp.json()
except:
print("服务器返回的不是 JSON:")
print(sms_resp.text)
raise Exception("Invalid JSON response")
meta_token = sms_data.get("meta", {}).get("token")
if not meta_token:
print("SMS response:", sms_data)
raise Exception("meta.token not received")
print("meta.token:", meta_token)
# ---------------- STEP 4: 解决验证码 ----------------
print("\nSTEP 4 — Solving captcha")
solve_task = create_task({
"type": "CustomTask",
"class": "HUNT",
"websiteURL": BASE_URL + "/en/",
"userAgent": UA,
"metadata": {
"apiGetLib": API_GET_LIB,
"data": meta_token
},
"proxyType": "http",
"proxyAddress": PROXY_HOST,
"proxyPort": PROXY_PORT,
"proxyLogin": PROXY_LOGIN,
"proxyPassword": PROXY_PASSWORD
})
captcha_token = wait_result(solve_task)
print("Captcha token:", captcha_token)
# ---------------- STEP 5: 发送最终请求 ----------------
print("\nSTEP 5 — Final request")
final = session.post(
BASE_URL + "/web-api/api/web/registration/v2/sms", # 替换为网站实际 API endpoint
headers={**api_headers, "cookie": cookies, "x-hd": xhd},
json={
"data": {
"attributes": {
"phone": PHONE,
"country_code": int(COUNTRY_CODE),
"captcha": captcha_token
}
}
},
proxies=proxies
)
print("\nFINAL STATUS:", final.status_code)
try:
print(final.json())
except:
print(final.text)
except Exception as e:
print("\nFATAL ERROR")
print(e)
