Bir Notion veritabanını CAPTCHA çözüm kuyruğuna dönüştürebilirsiniz: bekleyen URL'leri okuyun, reCAPTCHA v2 korumalı sayfaları CaptchaAI ile çözün, dönen token'ı ve durumu aynı satıra geri yazın. Notion API, veritabanlarına programlı okuma-yazma sağladığı için otomasyon iş akışlarında hafif ama esnek bir görev yönetimi katmanı olarak iş görür. Bu rehber, kuyruk yapısını kurmayı, çözümü tetiklemeyi ve sonucu geri yazmayı adım adım gösterir — Python ve Node.js örnekleriyle.
Senaryo: Notion tablosundan CAPTCHA korumalı URL'ler
Diyelim ki İstanbul'daki ekibiniz, periyodik olarak veri çekilmesi gereken tedarikçi ve ürün sayfası URL'lerini bir Notion veritabanında tutuyor. Bu sayfaların bir kısmı reCAPTCHA v2 ile korumalı. Notion'u tek doğruluk kaynağı olarak bırakıp, arka planda çalışan bir worker'a şu döngüyü yaptırmak istiyorsunuz:
- Notion'dan
Pendingdurumundaki görevleri okur. - Her URL'nin CAPTCHA'sını CaptchaAI üzerinden çözer.
- Her Notion kaydına token'ı ve güncel durumu geri yazar.
Notion'u kuyruk olarak kullanmanın avantajı, ekibin tamamının aynı panelden görev ekleyip ilerlemeyi görebilmesidir — ayrı bir kuyruk altyapısı kurmanıza gerek kalmaz. Kazınan verilerde kişisel veri bulunuyorsa KVKK kapsamına girdiğini unutmayın; bu akışı yetkilendirilmiş QA ve veri toplama işleri için kullanın.
Önkoşullar
- Bir Notion iç entegrasyonu (developers.notion.com üzerinden oluşturulur)
- Entegrasyonla paylaşılmış bir Notion veritabanı
- Bir CaptchaAI API anahtarı
- Python 3.8+ veya Node.js 18+
Notion veritabanını hazırlama
Aşağıdaki özelliklere sahip bir Notion veritabanı oluşturun. İlk sütundaki özellik adları koddaki alanlarla birebir eşleşmeli — Notion özellik adları büyük/küçük harfe duyarlıdır.
| Özellik (Property) | Tür | Amaç |
|---|---|---|
| Name | Başlık (Title) | Görev tanımlayıcı |
| URL | URL | CAPTCHA'lı hedef sayfa |
| Sitekey | Zengin metin | reCAPTCHA sitekey değeri |
| Status | Seçim (Select) | Pending, Solving, Solved, Failed |
| Token | Zengin metin | Çözülen CAPTCHA token'ı |
| Solved At | Tarih | Çözüm zaman damgası |
| Error | Zengin metin | Başarısız olursa hata mesajı |
Son adım olarak veritabanını Notion entegrasyonunuzla paylaşın; aksi halde API çağrıları 401 döner.
Python ile worker
Worker, Pending görevleri çeker, her biri için in.php uç noktasına görev gönderir, res.php'yi sorgulayarak token'ı bekler ve sonucu Notion'a geri yazar.
# 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()
Node.js ile worker
Aynı akışın resmi @notionhq/client ve axios ile yazılmış karşılığı. Görev döngüsü ve Pending → Solving → Solved/Failed durum geçişleri Python sürümüyle birebir aynıdır.
// 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);
Durum akışı: Pending'den Solved'e
Worker, her görevi tek bir durum makinesi üzerinden ilerletir. Bu ayrım, hangi görevin nerede takıldığını Notion panelinden tek bakışta görmenizi sağlar:
- Pending — görev kuyruğa yeni eklendi, henüz işlenmedi.
- Solving — worker görevi aldı ve CaptchaAI'ye gönderdi; bu durumda uzun süre kalan kayıtlar çözümün sürdüğünü ya da worker'ın yarıda kesildiğini gösterir.
- Solved — token yazıldı ve
Solved Atzaman damgası dolduruldu. - Failed — sitekey veya URL eksik ya da çözüm zaman aşımına uğradı; sebep
Erroralanında durur.
Solving durumunda takılı kalan kayıtları düzenli olarak tarayıp Pending'e geri çekmek, yarıda kesilen görevleri otomatik yeniden kuyruğa alan basit ama etkili bir kurtarma yöntemidir.
Worker'ı zamanlanmış çalıştırma
Worker'ı elle tetiklemek yerine belirli aralıklarla çalıştırmak, Notion'a düşen yeni görevlerin gecikmeden işlenmesini sağlar. Ortamınıza göre birkaç seçenek var:
- Linux/macOS:
cronile dakikalık veya saatlik tetikleme. - Windows: Görev Zamanlayıcı (Task Scheduler) üzerinden zamanlanmış görev.
- Bulut: AWS EventBridge veya Google Cloud Scheduler ile sunucusuz tetikleme.
Zamanlama aralığını iş yükünüze göre ayarlayın: seyrek görev ekleniyorsa saatlik çalıştırma yeterli olurken, yoğun kuyruklarda daha kısa aralıklar token'ların tazeliğini korur.
Ölçeklendirme ve thread planlaması
Notion API dakika bazlı bir hız sınırı uygular ve worker her görev için en az bir okuma ile birkaç yazma yaptığından, yüzlerce görevlik kuyruklarda bu sınıra takılmak kolaydır. Kod, güncellemeler arasına koyduğu bir saniyelik gecikmeyle bunu hafifletir. Daha büyük hacimlerde görevleri gruplar hâlinde işleyin ve CaptchaAI tarafında eşzamanlılığı thread sayınıza göre planlayın — aynı anda kaç görev çözdüğünüz, toplam çözüm sayısından daha belirleyicidir.
Güvenlik ve yetkilendirme
Bu akış iki ayrı kimlik bilgisiyle çalışır; her ikisini de kod içine gömmek yerine ortam değişkeni olarak tutun:
- Notion entegrasyon token'ı — yalnızca ilgili veritabanıyla paylaşın, tüm çalışma alanına erişim vermeyin.
- CaptchaAI API anahtarı —
CAPTCHAAI_KEYortam değişkeninden okunur; depoya veya Notion'a asla yazmayın.
Kazınan sayfalarda kişisel veri toplanıyorsa bunun KVKK kapsamına girdiğini unutmayın; worker'ı yalnızca erişim yetkiniz olan kaynaklar ve yetkilendirilmiş QA veya veri toplama işleri için çalıştırın.
Sonucu hedef isteğe bağlama
Notion'a yazılan token, çözümün kanıtı değil kısa ömürlü bir girdidir. Kaydı Solved olarak işaretledikten sonra token'ı gerçek isteğe bağlamak için tipik akış şöyledir:
Solveddurumundaki kaydı okuyun veTokenalanını alın.- Token'ı hedef formun
g-recaptcha-responsealanına yerleştirip isteği gönderin. - Hedef sunucu token'ı reddederse kaydı
Pending'e geri çekip yeniden çözün; kabul ederse görev tamamlanmıştır.
reCAPTCHA v2 token'ları yaklaşık iki dakika geçerli olduğundan, Solved ile bu adım arasındaki gecikmeyi kısa tutun.
Sık karşılaşılan sorunlar
| Sorun | Sebep | Çözüm |
|---|---|---|
Notion 401 Unauthorized |
Entegrasyon veritabanına bağlı değil | Veritabanını Notion'daki entegrasyonunuzla paylaşın |
| Özellik adları eşleşmiyor | Büyük/küçük harf duyarlılığı | Notion özellik adları harfe duyarlıdır; koddaki adlarla tam eşleştirin |
| Token kısaltılıyor | Notion zengin metin 2000 karakter sınırı | reCAPTCHA token'ları genellikle <1000 karakterdir; pratikte sorun olmaz |
Notion 429 hız sınırı |
Çok fazla API çağrısı | Notion güncellemeleri arasına 1 saniyelik gecikme koyun |
Sık sorulan sorular
Token'ı neden Notion'a kaydediyoruz, doğrudan formda kullanamaz mıyım?
Token'ı Notion'a yazmak, çözüm ile kullanım adımını ayırıp yeniden denemeleri ve denetimi kolaylaştırır. Ancak reCAPTCHA token'ları kısa ömürlüdür (yaklaşık iki dakika); token'ı aldıktan hemen sonra hedef isteği göndermeniz gerekir, uzun süre bekletmeyin.
Notion'dan 429 hatası alırsam ne yapmalıyım?
Notion API dakikada ortalama istek sınırı uygular. Kod zaten güncellemeler arasına 1 saniyelik gecikme koyar; çok sayıda görev işliyorsanız bu gecikmeyi artırın veya güncellemeleri gruplayarak çağrı sayısını azaltın.
reCAPTCHA v2 dışındaki CAPTCHA türlerini de çözebilir miyim?
Evet. Notion veritabanına bir "CAPTCHA Type" özelliği ekleyin ve çözüm işlevini uygun CaptchaAI yöntemini (turnstile, geetest, base64 vb.) kullanacak şekilde uyarlayın. method parametresini türe göre değiştirmeniz yeterlidir.
Bu hacim için hangi plan yeterli olur?
CaptchaAI thread bazlı fiyatlandırır: eşzamanlı çözdüğünüz görev sayısı önemlidir, toplam çözüm sayısı değil. Notion'dan görevleri seri işleyen bu worker için BASIC ($15/ay, 5 thread) çoğu ekip için yeterlidir; USD sabit aylık fiyat, TL dalgalanmasından bağımsız öngörülebilir maliyet sağlar.
İlgili makaleler
- reCAPTCHA v2 callback sorununu API ile çözme
- Aynı sayfada reCAPTCHA v2 ve Turnstile işleme
- reCAPTCHA v2 callback mekanizması
Sonraki adımlar
Notion veritabanlarınızı otomatik CAPTCHA çözüm kuyruklarına dönüştürün; CaptchaAI API anahtarınızı alın.
İlgili kılavuzlar: