Um board do Notion vira fila de tarefas de CAPTCHA com poucas linhas de código: leia os registros pendentes pela API do Notion, envie a sitekey e a URL para a CaptchaAI, grave o token de volta na mesma linha. É esse fluxo — sem trocar de ferramenta, sem planilha paralela — que este tutorial monta em Python e em JavaScript.
Times de operações já usam o Notion para controlar backlog; reaproveitar o mesmo board para orquestrar CAPTCHA evita subir mais uma ferramenta. A API do Notion lê e grava os registros; a CaptchaAI resolve o desafio reCAPTCHA v2 que trava o formulário.
Antes de começar
| Requisito | Detalhe |
|---|---|
| Integração do Notion | Integração interna, criada em developers.notion.com |
| Banco de dados no Notion | Compartilhado com essa integração |
| Chave de API | Da CaptchaAI |
| Ambiente | Python 3.8+ ou Node.js 18+ |
Por que usar o Notion como fila de tarefas para CAPTCHA
Imagine uma equipe de precificação em São Paulo que mantém, no Notion, uma lista de páginas de fornecedores para monitorar preços todos os dias. Parte dessas páginas pede reCAPTCHA v2 antes de liberar o conteúdo. Um script batendo direto nelas trava assim que encontra o desafio; a mesma automação, apontando para o Notion como fila, continua rodando sozinha:
- Lê as tarefas com status pendente no Notion
- Resolve o CAPTCHA de cada uma via CaptchaAI
- Grava o token e o novo status de volta no registro
Como o worker roda como um job — não em navegador —, os servidores costumam ficar perto da região
sa-east-1da AWS para reduzir a latência até a API do Notion. Se as URLs vierem de bases internas com dados reais, considere a LGPD antes de gravar algo sensível no board.
Monte a base de dados no Notion
Crie um banco de dados no Notion com estas propriedades, usando exatamente estes nomes — o Notion diferencia maiúsculas de minúsculas:
| Propriedade | Tipo | Finalidade |
|---|---|---|
| Name | Título | Identificador da tarefa |
| URL | URL | Página de destino com o CAPTCHA |
| Sitekey | Texto rico | sitekey do reCAPTCHA v2 |
| Status | Seleção | Pending, Solving, Solved, Failed |
| Token | Texto rico | Token do CAPTCHA resolvido |
| Solved At | Data | Carimbo de data/hora da resolução |
| Error | Texto rico | Mensagem de erro em caso de falha |
Compartilhe o banco com a integração antes de rodar o worker — sem isso, toda chamada retorna 401.
Erros comuns e como corrigir
Antes de escrever o worker, vale conhecer os erros mais frequentes desse fluxo — a maioria vem de um detalhe de configuração no Notion, não do código:
| Problema | Causa | Correção |
|---|---|---|
401 Unauthorized do Notion |
Integração não conectada ao banco de dados | Compartilhe o banco de dados com a integração no Notion |
| Nome de propriedade não bate | O Notion diferencia maiúsculas de minúsculas | Use exatamente os nomes da tabela acima — Sitekey, não sitekey ou Site Key |
| Token cortado no meio | Limite do Notion de 2.000 caracteres em campos rich_text |
Tokens de CAPTCHA normalmente têm menos de 1.000 caracteres — na prática isso não deve dar problema |
| Limite de requisições do Notion (429) | Muitas chamadas seguidas à API | Adicione um atraso de 1 segundo entre as atualizações no Notion |
Três hábitos evitam a maior parte desses problemas em produção:
- Rode um teste com uma única tarefa antes de processar o lote inteiro
- Monitore o campo Error para identificar padrões de falha por fornecedor
- Aumente o intervalo entre chamadas se o Notion começar a retornar
429com frequência
Implementação em Python
O worker consulta o Notion por tarefas com Status = Pending, resolve cada CAPTCHA na CaptchaAI e grava o resultado de volta no mesmo registro.
# notion_captcha_worker.py
import os
import time
import requests
NOTION_TOKEN = os.environ.get("NOTION_TOKEN")
NOTION_DB_ID = os.environ.get("NOTION_DB_ID")
CAPTCHAAI_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")
NOTION_HEADERS = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
}
def get_pending_tasks():
"""Fetch tasks with Status = Pending from Notion."""
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Status",
"select": {"equals": "Pending"},
}
}
resp = requests.post(url, headers=NOTION_HEADERS, json=payload)
resp.raise_for_status()
return resp.json()["results"]
def update_task(page_id, properties):
"""Update a Notion page with new property values."""
url = f"https://api.notion.com/v1/pages/{page_id}"
payload = {"properties": properties}
resp = requests.patch(url, headers=NOTION_HEADERS, json=payload)
resp.raise_for_status()
def set_status(page_id, status, token=None, error=None):
"""Update task status in Notion."""
props = {"Status": {"select": {"name": status}}}
if token:
props["Token"] = {"rich_text": [{"text": {"content": token[:2000]}}]}
props["Solved At"] = {"date": {"start": time.strftime("%Y-%m-%dT%H:%M:%S")}}
if error:
props["Error"] = {"rich_text": [{"text": {"content": error[:200]}}]}
update_task(page_id, props)
def solve_captcha(sitekey, pageurl):
"""Submit to CaptchaAI and poll for result."""
# Submit
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": CAPTCHAAI_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
raise Exception(f"Submit failed: {result.get('request')}")
task_id = result["request"]
# Poll
time.sleep(15)
for _ in range(25):
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": CAPTCHAAI_KEY,
"action": "get",
"id": task_id,
"json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
return poll_result["request"]
if poll_result.get("request") != "CAPCHA_NOT_READY":
raise Exception(f"Solve failed: {poll_result.get('request')}")
time.sleep(5)
raise Exception("Polling timeout")
def extract_property(page, prop_name, prop_type="rich_text"):
"""Extract a property value from a Notion page."""
prop = page["properties"].get(prop_name, {})
if prop_type == "rich_text":
texts = prop.get("rich_text", [])
return texts[0]["plain_text"] if texts else ""
elif prop_type == "url":
return prop.get("url", "")
return ""
def main():
tasks = get_pending_tasks()
print(f"Found {len(tasks)} pending tasks")
for task in tasks:
page_id = task["id"]
sitekey = extract_property(task, "Sitekey")
pageurl = extract_property(task, "URL", "url")
if not sitekey or not pageurl:
set_status(page_id, "Failed", error="Missing sitekey or URL")
continue
print(f"Solving: {pageurl}")
set_status(page_id, "Solving")
try:
token = solve_captcha(sitekey, pageurl)
set_status(page_id, "Solved", token=token)
print(f" Solved successfully")
except Exception as e:
set_status(page_id, "Failed", error=str(e))
print(f" Failed: {e}")
time.sleep(1) # Rate limit for Notion API
print("All tasks processed")
if __name__ == "__main__":
main()
Implementação em JavaScript
A mesma lógica em Node.js, usando o SDK oficial @notionhq/client junto com axios para a chamada à CaptchaAI.
// notion_captcha_worker.js
const { Client } = require('@notionhq/client');
const axios = require('axios');
const notion = new Client({ auth: process.env.NOTION_TOKEN });
const DB_ID = process.env.NOTION_DB_ID;
const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
async function getPendingTasks() {
const response = await notion.databases.query({
database_id: DB_ID,
filter: { property: 'Status', select: { equals: 'Pending' } },
});
return response.results;
}
async function updateTask(pageId, status, token, error) {
const properties = {
Status: { select: { name: status } },
};
if (token) {
properties.Token = { rich_text: [{ text: { content: token.slice(0, 2000) } }] };
properties['Solved At'] = { date: { start: new Date().toISOString() } };
}
if (error) {
properties.Error = { rich_text: [{ text: { content: error.slice(0, 200) } }] };
}
await notion.pages.update({ page_id: pageId, properties });
}
async function solveCaptcha(sitekey, pageurl) {
const submit = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: API_KEY, method: 'userrecaptcha',
googlekey: sitekey, pageurl, json: '1',
},
});
if (submit.data.status !== 1) throw new Error(submit.data.request);
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 25; i++) {
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Timeout');
}
async function main() {
const tasks = await getPendingTasks();
console.log(`Found ${tasks.length} pending tasks`);
for (const task of tasks) {
const sitekey = task.properties.Sitekey?.rich_text?.[0]?.plain_text;
const pageurl = task.properties.URL?.url;
if (!sitekey || !pageurl) {
await updateTask(task.id, 'Failed', null, 'Missing sitekey or URL');
continue;
}
console.log(`Solving: ${pageurl}`);
await updateTask(task.id, 'Solving');
try {
const token = await solveCaptcha(sitekey, pageurl);
await updateTask(task.id, 'Solved', token);
console.log(' Solved');
} catch (e) {
await updateTask(task.id, 'Failed', null, e.message);
console.log(` Failed: ${e.message}`);
}
await new Promise(r => setTimeout(r, 1000));
}
}
main().catch(console.error);
Perguntas frequentes
Por que usar o Notion em vez de um banco de dados tradicional?
A equipe já mexe nele. Pausar ou adicionar uma URL vira editar uma linha, sem tela de admin separada — suficiente para filas de dezenas ou centenas de tarefas por rodada.
Dá para deixar isso rodando sozinho, num agendamento?
Sim. Cron, Agendador de Tarefas do Windows ou um agendador de nuvem (AWS EventBridge, Google Cloud Scheduler) chamam o script no intervalo definido — o worker processa tudo que estiver Pending.
O token do reCAPTCHA expira se eu demorar para usar?
Sim, tokens do reCAPTCHA v2 duram poucos minutos. Grave o token e envie ao formulário logo em seguida — não deixe uma tarefa "Solved" parada na fila.
O fluxo funciona com outros tipos de CAPTCHA além do reCAPTCHA v2?
Sim. Adicione uma propriedade "Tipo de CAPTCHA" ao banco e ajuste a função de resolução para o método correspondente da CaptchaAI:
turnstile— Cloudflare Turnstilegeetest— GeeTest v3base64— CAPTCHAs de imagem/OCR
Preciso de um plano específico da CaptchaAI para esse volume?
Não necessariamente. A CaptchaAI cobra por thread simultânea, não por solução: o BASIC (US$ 15/mês, 5 threads) já cobre filas pequenas no Notion. Se o volume crescer, suba para um plano com mais threads.
Artigos relacionados
- Como resolver o callback do reCAPTCHA v2 pela API
- reCAPTCHA v2 e Turnstile no mesmo site
- Como funciona o callback do reCAPTCHA v2
Coloque em produção
Transforme o board do Notion em fila automática de CAPTCHA — crie sua chave de API da CaptchaAI.
Guias relacionados: