Phân Tích Kỹ Thuật

Solve Media CAPTCHA là gì

Solve Media là một loại CAPTCHA quảng cáo: thay vì gõ chuỗi ký tự méo mó kiểu "aX3kP", người dùng phải gõ lại một khẩu hiệu thương hiệu như "Quality You Can Trust". Về mặt kỹ thuật nó vẫn là CAPTCHA văn bản nằm trong ảnh, nên bạn giải nó bằng đúng luồng OCR đang dùng cho image CAPTCHA — không cần phương thức riêng, không cần sitekey.

Nếu bạn vừa thấy các trường adcopy_* trong một form cũ, bài này giải thích thứ bạn vừa gặp và cách xử lý. Solve Media đã được mua lại (nay thuộc Permit.io / Radius Networks) và gần như không còn triển khai mới, nhưng vẫn sống trong các website đời cũ.


Vì sao Solve Media dễ giải hơn CAPTCHA văn bản thường

Điểm mấu chốt: Solve Media sinh ra để bán quảng cáo, không phải để chặn bot. Chữ trong ảnh buộc phải rõ ràng thì người dùng mới đọc được thông điệp thương hiệu — mà cái gì người đọc được thì OCR cũng đọc được.

Tiêu chí Solve Media CAPTCHA văn bản thường
Nội dung Khẩu hiệu, từ tiếng Anh có nghĩa Ký tự ngẫu nhiên
Độ dễ đọc Cao (cố ý cho dễ đọc) Thấp (cố ý gây khó)
Mức bóp méo Tối thiểu Nặng
Mô hình doanh thu Quảng cáo Không có
Độ khó với OCR Dễ (chữ sạch) Khó (chữ méo)
Trải nghiệm người dùng Tốt hơn (từ quen thuộc) Kém hơn (khó đọc)

Luồng hoạt động của widget

Trước khi đụng tới code, cần nắm bốn bước mà widget thực sự chạy:

  1. Widget tải và hiển thị thông điệp quảng cáo dưới dạng ảnh
  2. Người dùng đọc khẩu hiệu và gõ vào ô nhập liệu
  3. Máy chủ Solve Media xác minh phản hồi
  4. Nếu đúng, form được gửi đi
┌─────────────────────────────────┐
│                                 │
│   "Taste The Rainbow"           │
│                                 │
├─────────────────────────────────┤
│ Type the phrase: [____________] │
│                     [Verify]    │
└─────────────────────────────────┘

Bước 3 là chỗ script tự động hay gãy: gửi form mà adcopy_response chưa hợp lệ thì máy chủ trả lỗi challenge và job dừng.


Nhận diện Solve Media trên một trang

Trước khi viết code, hãy xác nhận đúng loại CAPTCHA. Mở DevTools và tìm các dấu hiệu sau trong HTML:

<!-- Solve Media script -->
<script src="https://api.solvemedia.com/papi/challenge.script"></script>

<!-- Widget container -->
<div id="adcopy-outer"></div>

<!-- Hidden response fields -->
<input type="hidden" name="adcopy_challenge" />
<input type="hidden" name="adcopy_response" />

Mọi phần tử có ID bắt đầu bằng adcopy- hoặc chứa solvemedia đều là chỉ dấu chắc chắn. Nếu bạn chỉ thấy g-recaptcha hoặc cf-turnstile, thì bạn đang đọc nhầm bài: đó là reCAPTCHA v2 hoặc Cloudflare Turnstile, hai loại đi theo luồng sitekey chứ không phải luồng OCR trong bài này.


Giải Solve Media bằng CaptchaAI

Vì nội dung nằm trong ảnh, bạn dùng endpoint OCR tiêu chuẩn: chụp widget, gửi lên in.php dạng base64, polling res.php, rồi điền chuỗi nhận được vào adcopy_response.

Python

import requests
import time
import base64

API_KEY = "YOUR_API_KEY"

# Capture the CAPTCHA image
# Method 1: Screenshot the widget
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/page-with-solvemedia")

captcha_img = driver.find_element(By.CSS_SELECTOR, "#adcopy-puzzle-image img")
captcha_img.screenshot("solvemedia.png")

# Submit to CaptchaAI
with open("solvemedia.png", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode()

response = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "base64",
    "body": img_b64,
    "phrase": 1,      # Contains spaces (multi-word phrase)
    "json": 1
})

task_id = response.json()["request"]

# Poll for solution
for _ in range(30):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": 1
    }).json()
    if result.get("status") == 1:
        text = result["request"]
        print(f"Phrase: {text}")
        break

# Type the phrase
driver.find_element(By.CSS_SELECTOR, "#adcopy_response").send_keys(text)
driver.find_element(By.CSS_SELECTOR, "form").submit()

Node.js

const axios = require('axios');
const fs = require('fs');

async function solveSolveMedia(imagePath) {
  const imageB64 = fs.readFileSync(imagePath).toString('base64');

  const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: {
      key: 'YOUR_API_KEY',
      method: 'base64',
      body: imageB64,
      phrase: 1,
      json: 1
    }
  });

  const taskId = submit.data.request;

  for (let i = 0; i < 30; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const res = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
    });
    if (res.data.status === 1) return res.data.request;
  }
  throw new Error('Timeout');
}

Kết quả trả về là văn bản thuần, gán thẳng vào ô nhập liệu rồi submit form như thao tác của người dùng thật.


Bốn tham số quyết định tỷ lệ giải thành công

Tham số Đặt thế nào Lý do
phrase 1 Khẩu hiệu Solve Media có dấu cách giữa các từ
numeric 2 Khẩu hiệu hầu như chỉ gồm chữ cái
min_len / max_len Không đặt Độ dài khẩu hiệu thay đổi theo từng chiến dịch quảng cáo
Chữ hoa/thường Giữ nguyên như OCR trả về Một số triển khai không phân biệt hoa thường

Sai lầm phổ biến nhất là quên phrase=1. Chuỗi lỗi khi đó diễn ra như sau:

  1. Dấu cách giữa các từ bị coi là nhiễu.
  2. OCR trả về khẩu hiệu dính liền, ví dụ TasteTheRainbow.
  3. Form từ chối, log chỉ báo challenge sai chứ không nói gì về tham số.

Các mã lỗi hay gặp khác của luồng OCR được liệt kê trong các lỗi OCR CAPTCHA thường gặp và cách sửa.


Tình huống thực tế: tiếp nhận một crawler cũ

Tình huống quen thuộc với các đội outsourcing tại TP.HCM hay Hà Nội: khách bàn giao một hệ thống scraping viết từ 2015 để bạn bảo trì. Job chạy đêm bỗng dừng ở một form đăng ký cũ, log chỉ báo "invalid challenge". Cách xử lý gọn:

  1. Mở trang bằng trình duyệt có giao diện và kiểm tra DOM — thấy adcopy-outer là biết ngay đây là Solve Media, không phải reCAPTCHA.
  2. Trước khi động tới OCR, đọc trường ẩn adcopy_challenge: ở một số triển khai cũ, khẩu hiệu đã nằm sẵn trong nguồn trang.
  3. Nếu không có, chụp ảnh widget và đẩy qua endpoint OCR với phrase=1.
  4. Ghi lại ảnh đầu vào và chuỗi trả về vào log kiểm thử để lần sau tái hiện được — thói quen này cũng phù hợp với yêu cầu ghi vết và tối thiểu hóa dữ liệu theo Nghị định 13/2023/NĐ-CP khi hệ thống có chạm tới dữ liệu cá nhân.

Về chi phí, CaptchaAI tính tiền theo thread (luồng giải đồng thời), không theo từng lần giải. Một job bảo trì như trên chỉ cần vài thread song song nên gói BASIC ($15/tháng, 5 thread) thường là đủ; chạy nhiều crawler cùng lúc mới cần STANDARD ($30/tháng, 15 thread) trở lên. Giá niêm yết bằng USD.


Câu hỏi thường gặp

Solve Media có còn được triển khai mới không?

Gần như không — công ty đã được mua lại và đổi thương hiệu. Thứ bạn còn gặp là:

  • Các triển khai cũ trên website nhiều năm tuổi.
  • Form đăng ký, form liên hệ hiếm khi được viết lại.

Có bắt buộc phải chụp ảnh widget không?

Không phải lúc nào cũng cần. Thứ tự nên kiểm tra:

  • Trường ẩn adcopy_challenge trong nguồn trang — ở một số triển khai cũ khẩu hiệu đã nằm sẵn ở đây.
  • Phản hồi JSON của chính widget, nếu trang gọi API riêng để lấy challenge.
  • Chỉ khi cả hai đều không có mới cần chụp ảnh và đẩy qua OCR.

OCR trả về sai hoa thường thì form có từ chối không?

Tùy triển khai, nên đừng tự ý chuẩn hóa. Gửi nguyên chuỗi OCR trả về; chỉ khi form từ chối nhiều lần mới thử hạ về chữ thường rồi so sánh tỷ lệ giải thành công.

Nên gộp Solve Media vào chung pipeline với reCAPTCHA hay tách riêng?

Gộp chung, vì phần hạ tầng gần như trùng nhau:

  • Cùng cặp endpoint in.php / res.php.
  • Cùng quota thread, nên không phải tính dung lượng riêng.
  • Khác biệt duy nhất nằm ở tham số gửi lên và cách lấy dữ liệu đầu vào.

Tách hạ tầng riêng chỉ làm tăng số thành phần phải giám sát mà không đổi lại lợi ích nào.


Hướng dẫn liên quan

Os comentários estão desativados para este artigo.