Если запись в Airtable должна дождаться решённой reCAPTCHA v2, прежде чем двигаться дальше по воронке, вставлять токен вручную — не вариант: это ломается на втором десятке записей. Решение — привязать CaptchaAI прямо к Airtable Automation или Scripting extension: смена статуса записи сама запускает решение CAPTCHA, а результат возвращается в ту же таблицу.
Ниже — рабочая схема: структура таблицы, скрипт для одиночного триггера, скрипт для пакетной обработки очереди и разбор типичных сбоев.
Типичный кейс: очередь URL с CAPTCHA
Команда ведёт таблицу целевых URL-адресов, каждый из которых защищён reCAPTCHA v2 — данные с этих страниц нельзя забрать, пока капча не решена. Цепочка простая: Airtable Automation замечает запись со статусом «pending», скрипт отправляет параметры CAPTCHA в CaptchaAI, CaptchaAI возвращает решённый токен, а токен вместе с новым статусом записывается обратно в ту же запись Airtable. Дальше — таблица, два скрипта и то, что обычно идёт не так.
Схема таблицы
Создайте в Airtable таблицу CAPTCHA Tasks со следующими полями (имена полей важны — они используются в скрипте один в один):
| Поле | Тип | Назначение |
|---|---|---|
| URL | URL | Целевая страница, где стоит CAPTCHA |
| Sitekey | Однострочный текст | reCAPTCHA sitekey целевой страницы |
| Status | Одиночный выбор | pending, solving, solved, failed |
| Token | Длинный текст | Решённый токен CAPTCHA |
| Solved At | Дата/время | Метка времени решения |
| Error | Однострочный текст | Текст ошибки, если решение не удалось |
Названия полей — латиницей и совпадают с ключами в коде ниже ('Status', 'Token', 'Solved At', 'Error'). Если переименовать поле в интерфейсе Airtable, не поменяв его в скрипте, автоматизация будет молча падать на updateRecordAsync.
Шаг 1. Настройте автоматизацию
Откройте в Airtable вкладку Automations, нажмите Create automation и назовите её, например, «Solve CAPTCHA on New Record».
Триггер
Выберите условие When record matches conditions — таблица CAPTCHA Tasks, условие Status = «pending».
Один и тот же триггер обслуживает и новые записи, и повторные попытки: чтобы переотправить запись на решение, достаточно вручную вернуть её статус в
pending.
Шаг 2. Добавьте действие со скриптом
Добавьте действие 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}`);
}
Сопоставьте входные переменные
На левой панели действия скрипта свяжите три переменные с полями шага триггера: recordId берётся из идентификатора записи, sitekey — из поля Sitekey, а pageurl — из поля URL той же записи.
Шаг 3. Пакетная обработка через Scripting extension
Automation хороша для одиночного триггера, но для очереди из десятков записей удобнее Scripting extension Airtable (открывается на панели Apps) — один запуск скрипта разбирает всю очередь pending-записей подряд:
// 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');
Частые сбои и что с ними делать
- Автоматизация не запускается. Значение поля
Statusне совпадает с условием триггера дословно — проверьте, что опция single-select называется точноpending, без лишних пробелов. fetch is not defined. В части сред Airtable Scripting доступен толькоremoteFetchAsync— заменитеfetchи адаптируйте синтаксис под его сигнатуру.- Скрипт обрывается по таймауту. У Automation-скриптов лимит выполнения 30 секунд — сократите число циклов опроса и увеличьте первую паузу до 20 секунд, чтобы реже опрашивать
res.php. updateRecordAsyncне обновляет запись. Имя поля в скрипте не совпадает с именем поля в таблице — сверьте'Status','Token','Solved At','Error'с названиями колонок один в один.- API-ключ виден в теле скрипта. Ключ захардкожен в коде — передавайте его через
input.config()как секретную входную переменную, а не как строковый литерал.
Сколько это будет стоить
Тарифы CaptchaAI считаются по одновременным потокам, а не по числу решений, поэтому для Airtable-очереди это удобно прогнозировать заранее. reCAPTCHA v2 обычно решается менее чем за 60 секунд на поток — значит, даже тариф BASIC ($15/мес, 5 потоков, неограниченное число решений на поток) спокойно закрывает несколько сотен URL в день, если очередь идёт равномерно, а не залпом. Если Automation обслуживает несколько таблиц или партнёрских баз одновременно, разумно сразу закладывать STANDARD ($30/мес, 15 потоков) или ADVANCE ($90/мес, 50 потоков) — актуальные тарифы и число потоков всегда проверяйте на странице тарифов CaptchaAI.
Для агентств, которые ведут несколько Airtable-баз для разных клиентов (частый сценарий у фрилансеров и небольших data-ops команд в СНГ, выставляющих счета в долларах), фиксированная помесячная стоимость в USD за поток считается проще, чем платить за каждое отдельное решение — особенно когда объём по клиентам скачет от недели к неделе.
FAQ
Когда для этой схемы лучше взять Scripting extension вместо Automation-триггера? Automation срабатывает сама на каждую смену статуса записи и удобна, когда URL добавляются по одному в течение дня, но её скрипт жёстко ограничен 30 секундами выполнения. Scripting extension запускается вручную (или по внешнему расписанию) и за один проход разбирает всю накопившуюся очередь pending-записей — берите её, если URL приходят пакетами по 10–50 штук, а не поодиночке.
Какой тариф CaptchaAI выбрать под Airtable-очередь? Ориентируйтесь на пиковую параллельность, а не на суточный объём: BASIC (5 потоков) хватает для нескольких сотен решений в день с равномерной нагрузкой, STANDARD (15 потоков) и выше — когда очередь обрабатывается пакетами через Scripting extension. Точные цены и число потоков — в актуальной таблице тарифов на сайте CaptchaAI.
Можно ли в этой же схеме решать Cloudflare Turnstile или GeeTest v3 вместо reCAPTCHA v2? Да, структура таблицы и логика опроса in.php/res.php не меняются — достаточно поменять method на turnstile или geetest и соответствующие параметры запроса. CaptchaAI поддерживает reCAPTCHA v2/v3, Cloudflare Turnstile и Challenge, GeeTest v3, изображения и BLS CAPTCHA; hCaptcha и FunCaptcha CaptchaAI не решает.
Как безопасно хранить API-ключ, если скрипт открыт нескольким сотрудникам? Не держите ключ строкой в теле скрипта. В Automation задайте его как input-переменную в панели настроек действия — так ключ не виден в самом коде скрипта. В Scripting extension аналогично можно запросить ключ через input.textAsync() во время запуска, не сохраняя его в тексте.
Что делать с записями, которые ушли в статус failed? Верните полю Status значение pending — тем же триггером Automation подхватит запись повторно. Для контроля добавьте в таблицу отдельное числовое поле-счётчик попыток и остановите повторные запуски после 3–5 неудач, чтобы не гонять по кругу заведомо нерабочий sitekey или URL.