Bạn quản lý danh sách URL trong Airtable và mỗi URL đứng sau reCAPTCHA v2? Không cần tool ngoài: Airtable Automation có thể tự gọi CaptchaAI ngay khi một bản ghi mới xuất hiện, rồi ghi token giải xong thẳng vào bảng — toàn bộ luồng chạy trong chính Airtable, không cần server riêng hay cron job.
Bài viết này đi thẳng vào cách dựng luồng đó: một Automation lắng nghe bản ghi mới, một script gọi in.php/res.php của CaptchaAI, và một biến thể xử lý hàng loạt bằng Scripting extension khi bạn có hàng trăm bản ghi cùng lúc.
Cách này hợp nhất với các đội đã dùng Airtable làm nơi quản lý dữ liệu trung tâm thay vì viết riêng một dịch vụ backend. Nhiều đội vận hành dữ liệu tại các công ty outsourcing ở TP.HCM và Hà Nội (dạng FPT Software, VNG, hoặc các agency nhỏ hơn) dùng đúng mô hình này: một bảng Airtable liệt kê hàng trăm URL cần theo dõi — có thể là biểu mẫu QA nội bộ có reCAPTCHA v2, hoặc trang giá công khai cần thu thập định kỳ — và Automation tự giải CAPTCHA cho từng URL mới thay vì để nhân sự copy-paste sitekey thủ công. Luồng xử lý gồm bốn bước:
- Airtable Automation phát hiện bản ghi mới (hoặc bản ghi được đặt lại "đang chờ xử lý")
- Script gửi
sitekeyvàpageurlsang CaptchaAI - CaptchaAI trả về token đã giải
- Token được ghi lại vào đúng bản ghi đó
Bước 1: Tạo Automation giải CAPTCHA trong Airtable
Trước khi tạo Automation, dựng bảng dữ liệu mà script sẽ đọc và ghi vào — đặt tên CAPTCHA Tasks với các trường sau (tên trường giữ nguyên tiếng Anh vì script bên dưới tham chiếu trực tiếp tới chúng):
| Tên trường | Loại | Mục đích |
|---|---|---|
| URL | URL | URL của trang đích cần giải CAPTCHA |
| Sitekey | Văn bản một dòng | Sitekey reCAPTCHA lấy từ trang đích |
| Status | Chọn một lần | pending, solving, solved, failed |
| Token | Văn bản dài | Token CAPTCHA sau khi giải xong |
| Solved At | Ngày/giờ | Thời điểm giải xong |
| Error | Văn bản một dòng | Nội dung lỗi nếu bản ghi giải thất bại |
Có bảng rồi, tạo Automation trong tab Automations của Airtable:
- Mở tab Automations, nhấp Create automation, đặt tên "Giải CAPTCHA khi có bản ghi mới"
- Chọn trigger When record matches conditions
- Table: CAPTCHA Tasks — Điều kiện: Status = "pending"
- Lưu trigger — điều kiện này kích hoạt cả bản ghi hoàn toàn mới lẫn bản ghi cũ được đặt lại về "pending", tức là bạn có sẵn cơ chế giải lại chỉ bằng cách đổi giá trị Status
Bước 2: Thêm script giải CAPTCHA qua CaptchaAI
Thêm action → Run a script:
// Airtable Automation Script — Solve CAPTCHA via CaptchaAI
// Input configuration (set in the left panel):
// - recordId: Record ID from trigger
// - sitekey: Sitekey field from trigger
// - pageurl: URL field from trigger
const config = input.config();
const recordId = config.recordId;
const sitekey = config.sitekey;
const pageurl = config.pageurl;
const API_KEY = 'YOUR_API_KEY'; // Use input.config() for security
// Update status to "solving"
const table = base.getTable('CAPTCHA Tasks');
await table.updateRecordAsync(recordId, {
'Status': { name: 'solving' },
});
try {
// Step 1: Submit task to CaptchaAI
const submitUrl = `https://ocr.captchaai.com/in.php?key=${API_KEY}&method=userrecaptcha&googlekey=${encodeURIComponent(sitekey)}&pageurl=${encodeURIComponent(pageurl)}&json=1`;
const submitResponse = await fetch(submitUrl);
const submitResult = await submitResponse.json();
if (submitResult.status !== 1) {
throw new Error(`Submit failed: ${submitResult.request}`);
}
const taskId = submitResult.request;
console.log(`Task submitted: ${taskId}`);
// Step 2: Poll for result (wait 15 seconds first)
await new Promise(resolve => setTimeout(resolve, 15000));
let token = null;
for (let i = 0; i < 20; i++) {
const pollUrl = `https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const pollResponse = await fetch(pollUrl);
const pollResult = await pollResponse.json();
if (pollResult.status === 1) {
token = pollResult.request;
break;
}
if (pollResult.request !== 'CAPCHA_NOT_READY') {
throw new Error(`Solve failed: ${pollResult.request}`);
}
await new Promise(resolve => setTimeout(resolve, 5000));
}
if (!token) {
throw new Error('Polling timeout — CAPTCHA not solved in time');
}
// Step 3: Update record with solved token
await table.updateRecordAsync(recordId, {
'Status': { name: 'solved' },
'Token': token,
'Solved At': new Date().toISOString(),
'Error': '',
});
console.log(`CAPTCHA solved for record ${recordId}`);
} catch (error) {
// Update record with error
await table.updateRecordAsync(recordId, {
'Status': { name: 'failed' },
'Error': error.message,
});
console.error(`Failed: ${error.message}`);
}
| Biến input | Ánh xạ từ bước trigger |
|---|---|
recordId |
Record ID |
sitekey |
Trường Sitekey |
pageurl |
Trường URL |
Bước 3: Xử lý hàng loạt bằng Scripting extension
Khi cần giải CAPTCHA cho nhiều bản ghi cùng lúc thay vì từng bản ghi một, dùng Scripting extension của Airtable (nằm trong panel Apps):
// Batch CAPTCHA Solver — Airtable Scripting Extension
const API_KEY = 'YOUR_API_KEY';
const table = base.getTable('CAPTCHA Tasks');
// Get all pending records
const query = await table.selectRecordsAsync({
fields: ['URL', 'Sitekey', 'Status'],
});
const pendingRecords = query.records.filter(
r => r.getCellValueAsString('Status') === 'pending'
);
output.text(`Found ${pendingRecords.length} pending CAPTCHAs`);
for (const record of pendingRecords) {
const sitekey = record.getCellValueAsString('Sitekey');
const pageurl = record.getCellValueAsString('URL');
if (!sitekey || !pageurl) {
output.text(`Skipping ${record.id} — missing sitekey or URL`);
continue;
}
output.text(`Solving for: ${pageurl}`);
await table.updateRecordAsync(record.id, {
'Status': { name: 'solving' },
});
try {
// Submit
const submitResp = await fetch(
`https://ocr.captchaai.com/in.php?key=${API_KEY}&method=userrecaptcha&googlekey=${encodeURIComponent(sitekey)}&pageurl=${encodeURIComponent(pageurl)}&json=1`
);
const submitData = await submitResp.json();
if (submitData.status !== 1) throw new Error(submitData.request);
// Poll
await new Promise(r => setTimeout(r, 15000));
let token = null;
for (let i = 0; i < 20; i++) {
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${submitData.request}&json=1`
);
const pollData = await pollResp.json();
if (pollData.status === 1) { token = pollData.request; break; }
if (pollData.request !== 'CAPCHA_NOT_READY') throw new Error(pollData.request);
await new Promise(r => setTimeout(r, 5000));
}
if (!token) throw new Error('Timeout');
await table.updateRecordAsync(record.id, {
'Status': { name: 'solved' },
'Token': token,
'Solved At': new Date().toISOString(),
});
output.text(`✓ Solved: ${pageurl}`);
} catch (e) {
await table.updateRecordAsync(record.id, {
'Status': { name: 'failed' },
'Error': e.message,
});
output.text(`✗ Failed: ${e.message}`);
}
}
output.text('Batch processing complete');
Các lỗi thường gặp và cách xử lý
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Automation không kích hoạt | Bản ghi không khớp điều kiện "pending" | Kiểm tra lại giá trị trường Status khớp chính xác chuỗi "pending" |
fetch is not defined |
Airtable Scripting dùng remoteFetchAsync trong một số ngữ cảnh |
Thử remoteFetchAsync thay cho fetch |
| Script bị timeout | Automation script của Airtable giới hạn 30 giây | Giảm số lần lặp polling; tăng thời gian chờ ban đầu lên 20 giây |
| Cập nhật bản ghi thất bại | Tên trường trong updateRecordAsync không khớp bảng |
Đối chiếu chính xác tên trường trong bảng với chuỗi trong script |
| API key lộ trong script | API key gắn cứng trực tiếp trong code | Dùng input.config() với biến input dạng secret |
Câu hỏi thường gặp
CaptchaAI hỗ trợ giải những loại CAPTCHA nào khi tích hợp qua Airtable?
- Ngoài reCAPTCHA v2 dùng trong ví dụ này, CaptchaAI còn giải được reCAPTCHA v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, CAPTCHA ảnh/OCR, grid image và BLS
- Tất cả dùng chung một cặp endpoint
in.php/res.php, chỉ đổi tham sốmethod - CaptchaAI không hỗ trợ hCaptcha và FunCaptcha (Arkose Labs); GeeTest v4 đang ở trạng thái sắp ra mắt
- CaptchaFox, Friendly Captcha và Lemin đang trong giai đoạn beta
Automation xử lý hàng trăm bản ghi cùng lúc thì nên chọn gói CaptchaAI nào?
- Tính theo số bản ghi "pending" cùng lúc, không phải tổng số bản ghi trong bảng — CaptchaAI tính phí theo thread (số CAPTCHA giải song song), không theo lượt giải
- Vài chục bản ghi mỗi đợt: BASIC ($15/tháng, 5 thread) là đủ
- Automation thường xuyên kích hoạt hàng trăm bản ghi cùng lúc: cân nhắc STANDARD ($30/tháng, 15 thread) hoặc ADVANCE ($90/tháng, 50 thread) để tránh nghẽn hàng đợi
Vì sao script báo timeout trước khi CaptchaAI kịp trả token?
- Automation script của Airtable chỉ chạy tối đa 30 giây, trong khi giải reCAPTCHA v2 có thể mất 15–30 giây tùy độ khó
- Giảm số vòng lặp polling hoặc tăng thời gian chờ ban đầu (initial wait) lên 20 giây
- Chuyển phần polling sang Scripting extension — nơi không bị giới hạn 30 giây như Automation
Lưu API key CaptchaAI trong Airtable bằng cách nào để không bị lộ trong script?
- Trong Automation: khai báo API key như một input variable ở panel cấu hình script, không gõ trực tiếp vào code
- Trong Scripting extension: dùng
input.textAsync()để nhập key mỗi lần chạy thay vì hardcode
Bước tiếp theo
Tự động hóa việc giải CAPTCHA ngay từ cơ sở dữ liệu Airtable của bạn — lấy API key CaptchaAI và dựng Automation theo các bước trên.
- Giải reCAPTCHA v2 callback bằng API
- Xử lý reCAPTCHA v2 và Turnstile trên cùng một trang
- Cơ chế callback của reCAPTCHA v2
- Google Apps Script + CaptchaAI
- Zapier + CaptchaAI: giải CAPTCHA không cần code
- Make (Integromat) + CaptchaAI