Tích Hợp

Tập lệnh Google Apps + CaptchaAI: Tự động hóa CAPTCHA bảng tính

Có thể giải CAPTCHA hình ảnh ngay trong Google Trang tính, không cần rời trình duyệt hay dựng server riêng — chỉ một hàm Apps Script gọi UrlFetchApp tới CaptchaAI, kết quả trả thẳng về ô tính như bất kỳ công thức nào khác.

Khi nào nên dùng cách này

Hợp khi dữ liệu đã có sẵn trong Trang tính — URL ảnh CAPTCHA từ crawler, ảnh xác minh export nội bộ, hoặc CAPTCHA văn bản cần OCR. Team QA, outsourcing ở TP.HCM, Hà Nội hay gom dữ liệu này trước khi đưa qua pipeline chính thức, ví dụ cột link ảnh CAPTCHA khi theo dõi giá sàn TMĐT. Một hàm xử lý trọn luồng:

  • Tìm nạp từng ảnh CAPTCHA từ URL trong cột
  • Chuyển ảnh sang base64
  • Gửi ảnh tới CaptchaAI
  • Ghi văn bản đã giải ngược lại vào ô bên cạnh

Ưu điểm và giới hạn của cách làm này

  • Không cần server hay cron job riêng — Trang tính tự chứa toàn bộ luồng xử lý
  • API key lưu trong Script Properties, không lộ ra trong code
  • Custom function giới hạn 30 giây, chỉ hợp với vài chục ảnh mỗi lần gọi
  • Khối lượng lớn hơn cần menu hàng loạt (6 phút) hoặc worker chạy ngoài Apps Script

Bước 1: Tạo project Apps Script trong Trang tính

Trong Google Trang tính của bạn:

  1. Đi tới Tiện ích mở rộngApps Script
  2. Tạo một tệp tập lệnh mới, đặt tên captchaai.gs

Tên captchaai.gs giúp dễ tìm khi project có nhiều file script.

Bước 2: Viết hàm giải CAPTCHA gọi CaptchaAI

Đoạn code định nghĩa hai hàm tùy chỉnh (SOLVE_CAPTCHA cho URL ảnh, SOLVE_CAPTCHA_BASE64 cho base64 có sẵn) cùng hai hàm nội bộ để gửi task và polling kết quả:

// captchaai.gs

// Store your API key in Script Properties for security
// File → Project Properties → Script Properties → Add: CAPTCHAAI_KEY
function getApiKey() {
  return PropertiesService.getScriptProperties().getProperty('CAPTCHAAI_KEY');
}

/**

 * Solve an image CAPTCHA from a URL.
 * @param {string} imageUrl URL of the CAPTCHA image to solve.
 * @return {string} The solved CAPTCHA text.
 * @customfunction
 */
function SOLVE_CAPTCHA(imageUrl) {
  if (!imageUrl) return 'No URL provided';

  try {
    // Fetch the image and convert to base64
    var imageResponse = UrlFetchApp.fetch(imageUrl);
    var imageBlob = imageResponse.getBlob();
    var base64Image = Utilities.base64Encode(imageBlob.getBytes());

    // Submit to CaptchaAI
    var taskId = submitCaptcha(base64Image);
    if (!taskId) return 'Submit failed';

    // Poll for result
    var result = pollForResult(taskId);
    return result || 'Solve failed';
  } catch (e) {
    return 'Error: ' + e.message;
  }
}

/**

 * Solve an image CAPTCHA from base64 text in a cell.
 * @param {string} base64Image Base64-encoded CAPTCHA image.
 * @return {string} The solved CAPTCHA text.
 * @customfunction
 */
function SOLVE_CAPTCHA_BASE64(base64Image) {
  if (!base64Image) return 'No image data';

  try {
    var taskId = submitCaptcha(base64Image);
    if (!taskId) return 'Submit failed';

    var result = pollForResult(taskId);
    return result || 'Solve failed';
  } catch (e) {
    return 'Error: ' + e.message;
  }
}

function submitCaptcha(base64Image) {
  var apiKey = getApiKey();

  var payload = {
    'key': apiKey,
    'method': 'base64',
    'body': base64Image,
    'json': '1'
  };

  var options = {
    'method': 'post',
    'payload': payload,
    'muteHttpExceptions': true
  };

  var response = UrlFetchApp.fetch(
    'https://ocr.captchaai.com/in.php', options
  );
  var result = JSON.parse(response.getContentText());

  if (result.status === 1) {
    return result.request;
  }

  Logger.log('Submit error: ' + result.request);
  return null;
}

function pollForResult(taskId) {
  var apiKey = getApiKey();
  var maxAttempts = 20;

  // Initial wait
  Utilities.sleep(5000);

  for (var i = 0; i < maxAttempts; i++) {
    var url = 'https://ocr.captchaai.com/res.php'
      + '?key=' + apiKey
      + '&action=get'
      + '&id=' + taskId
      + '&json=1';

    var response = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
    var result = JSON.parse(response.getContentText());

    if (result.status === 1) {
      return result.request;
    }

    if (result.request !== 'CAPCHA_NOT_READY') {
      Logger.log('Solve error: ' + result.request);
      return null;
    }

    Utilities.sleep(5000);
  }

  return null; // Timeout
}

Bước 3: Lưu API key an toàn trong Script Properties

Không hard-code API key trong code — lưu trong Script Properties:

  1. Trong trình chỉnh sửa Apps Script, nhấp Cài đặt dự án (biểu tượng bánh răng)
  2. Cuộn xuống Thuộc tính tập lệnh
  3. Nhấp Thêm thuộc tính tập lệnh
  4. Property: CAPTCHAAI_KEY, Value: API key CaptchaAI của bạn
  5. Lưu lại

Không cần bước xác thực nào khác — API key trong Script Properties là đủ.

Bước 4: Gọi hàm ngay trong Trang tính

Dùng như một công thức thông thường

Dùng trực tiếp trong ô với URL ảnh, hoặc với dữ liệu base64 đã có sẵn ở ô khác:

=SOLVE_CAPTCHA("https://example.com/captcha-image.jpg")
=SOLVE_CAPTCHA_BASE64(A2)

Giải hàng loạt bằng menu tùy chỉnh

Gọi SOLVE_CAPTCHA trực tiếp tiện cho vài chục ảnh, nhưng cột hàng trăm dòng sẽ hết timeout 30 giây của custom function. Giải pháp: thêm menu chạy hàm riêng (giới hạn 6 phút):

function onOpen() {
  SpreadsheetApp.getUi().createMenu('CaptchaAI')
    .addItem('Solve Column A CAPTCHAs', 'solveColumnCaptchas')
    .addItem('Check Balance', 'checkBalance')
    .addToUi();
}

function solveColumnCaptchas() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var urls = sheet.getRange('A2:A' + lastRow).getValues();

  for (var i = 0; i < urls.length; i++) {
    if (!urls[i][0]) continue;

    var row = i + 2;
    var existingResult = sheet.getRange('B' + row).getValue();
    if (existingResult) continue; // Skip already solved

    sheet.getRange('B' + row).setValue('Solving...');
    SpreadsheetApp.flush();

    var result = SOLVE_CAPTCHA(urls[i][0]);
    sheet.getRange('B' + row).setValue(result);
    sheet.getRange('C' + row).setValue(new Date()); // Timestamp

    // Avoid hitting Apps Script quotas
    Utilities.sleep(1000);
  }

  SpreadsheetApp.getUi().alert('Batch solving complete!');
}

function checkBalance() {
  var apiKey = getApiKey();
  var url = 'https://ocr.captchaai.com/res.php'
    + '?key=' + apiKey
    + '&action=getbalance'
    + '&json=1';

  var response = UrlFetchApp.fetch(url);
  var result = JSON.parse(response.getContentText());

  SpreadsheetApp.getUi().alert(
    'CaptchaAI Balance: $' + result.request
  );
}

Menu thêm mục Check Balance để xem số dư ngay trong Trang tính, không cần mở dashboard riêng.

Bước 5: Kết quả trông như thế nào trên trang tính

Sau khi chạy solveColumnCaptchas, cột B và C tự điền theo từng dòng:

  • https://example.com/captcha1.jpgABC123, giải lúc 2026-04-04 10:15:00
  • https://example.com/captcha2.jpgXYZ789, giải lúc 2026-04-04 10:15:12
  • https://example.com/captcha3.jpg → vẫn hiện Giải quyết... cho tới khi có kết quả

Cột thời điểm giải giúp đối chiếu khi audit dữ liệu (chi tiết ở FAQ bên dưới).

Xử lý sự cố thường gặp

Lỗi hay gặp khi chạy SOLVE_CAPTCHA và cách xử lý:

Vấn đề Nguyên nhân Cách xử lý
TypeError: Cannot read property 'getProperty' Chưa đặt Script Properties Thêm CAPTCHAAI_KEY trong Cài đặt dự án
Exception: Address unavailable URL hình ảnh bị chặn hoặc không hợp lệ Xác minh URL ảnh có thể truy cập được từ máy chủ Google
Hàm tùy chỉnh trả về Loading... mãi không dừng Vượt timeout 30 giây của custom function Chuyển sang cách tiếp cận menu hàng loạt — hàm menu có giới hạn 6 phút
ERROR_ZERO_BALANCE Tài khoản hết credit Nạp thêm vào số dư CaptchaAI
Vượt hạn ngạch UrlFetchApp Gọi API quá nhiều trong ngày Thêm Utilities.sleep() giữa các lần gọi; chia nhỏ theo lô

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

Giới hạn thời gian thực thi của Apps Script khi giải CAPTCHA là bao nhiêu?

  • Custom function: 30 giây
  • Hàm chạy từ menu: 6 phút
  • Trigger theo thời gian: 6 phút (30 phút Workspace)

Dùng menu/trigger cho lô lớn; công thức SOLVE_CAPTCHA chỉ hợp số lượng nhỏ.

Apps Script có giải được reCAPTCHA v2, hay chỉ giải CAPTCHA hình ảnh?

Có, nếu thêm sitekey và pageurl của trang đích — dùng method=userrecaptcha thay vì method=base64, kèm tham số googlekeypageurl.

Làm sao kiểm tra số dư CaptchaAI ngay trong Trang tính?

  • Mở menu CaptchaAI vừa thêm ở Bước 4
  • Chọn Check Balance
  • Hàm checkBalance() gọi action=getbalance và hiện số dư trong hộp thoại — không cần mở dashboard riêng

Khi nào nên chuyển từ Apps Script sang worker Python/Node để giải hàng loạt?

  • Khối lượng vượt vài nghìn CAPTCHA/ngày
  • Chạm hạn ngạch UrlFetchApp (20.000 lệnh/ngày cá nhân, 100.000 Workspace)
  • Trước đó, Trình kích hoạt (Chỉnh sửa → Trình kích hoạt) chạy solveColumnCaptchas theo lịch vẫn là bước trung gian đủ dùng

Cột thời điểm giải trong Bước 5 có đủ cho audit dữ liệu theo Nghị định 13/2023/NĐ-CP không?

  • Là điểm khởi đầu hợp lý, chưa phải hồ sơ audit đầy đủ
  • Muốn truy vết kỹ hơn, ghi thêm người thực thi bằng Session.getActiveUser().getEmail()
  • Đây là gợi ý kỹ thuật, không phải tư vấn pháp lý

Bước tiếp theo

Dán script vào Trang tính, thêm CAPTCHAAI_KEY, thử SOLVE_CAPTCHA với URL ảnh thật — lấy API key CaptchaAI tại đây. Xem thêm giải CAPTCHA không cần code với Zapier, giải 1.000 CAPTCHA hình ảnh hàng loạt, hoặc tích hợp Make (Integromat).

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