نتایج گوگل

مسیرهای قدیمی

مسیرهای /api در سرویس نتایج گوگل؛ چه تفاوتی با API منابع دارند، چه چیزی موقع مهاجرت شما را غافلگیر می‌کند، و GET /selector-health.

مسیرهای قدیمی /api/* هنوز کار می‌کنند، منسوخ نشده‌اند، محدودیت نرخ ندارند و کوکی نشست داشبورد را هم می‌پذیرند.

این‌ها برای کلاینت‌هایی هستند که همین حالا نوشته شده‌اند. برای اتصال تازه از API منابع /api/v1/operator/* استفاده کنید.

نگاشت مسیرها

مسیر قدیمیمعادل نسخه‌دار
POST /api/queryPOST /api/v1/operator/query
GET /api/results/{task_id}/serpapi|markdown|rawGET /api/v1/operator/results/{task_id}/serpapi|markdown|raw
GET /api/selector-healthGET /api/v1/operator/selector-health
یک استثنای مهم: GET /api/results هیچ معادلی در v1 ندارد. چون /api/v1/operator/results کامل‌تر است و این دو یک پرس‌وجو نیستند. جدول مقایسه در پایین همین صفحه آمده.

دو نام متفاوت برای یک کار

POST /api/query (قدیمی) و POST /api/v1/operator/query (معادل) هر دو همان رفتار قدیمی را دارند — یعنی همان دو تفاوت پنهان. POST /api/v1/operator/queries (با s جمع) جزو API منابع است و رفتار متفاوتی دارد.

/api/query قدیمی/queries در API منابع
هدف می‌سازد؟اگر cadence_hours > 0، بله و بیسروصداهرگز
فیلد dedupedندارددارد
از کجا بفهمید تکراری بودenqueued: false به‌علاوهٔ reasondeduped: true

ثبت در مسیر قدیمی

POST /api/query — مثل بقیهٔ مسیرها به scope نیاز دارد.

city اختیاری است (null یا "" یعنی کل ایران). محدود شدن pages و expand_answers مثل POST /queries است.

نمونهٔ درخواست:

curl -s -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"domain":"example.ir","keyword":"shoes"}' \
  https://serp.rasaad.ir/api/query

نمونهٔ پاسخ:

{"ok": true, "task_id": "…", "keyword": "…", "domain": "…", "city": "",
 "location": "iran", "recurring": false, "pages": 1, "expand_answers": 0,
 "enqueued": true, "reason": "created"}

مقادیر reason: created، already_active، recently_done.

تفاوت اول: پاسخ فیلد deduped ندارد. نشانهٔ معادل، enqueued: false به‌علاوهٔ reason برابر با already_active یا recently_done است.

cadence_hours > 0 بی‌سروصدا یک هدف تکرارشونده می‌سازد — یعنی یک upsert پنهان داخل درخواست ثبت. اگر یک خزش یک‌باره می‌خواستید و cadence_hours را از یک تنظیم پیش‌فرض یا یک متغیر محیطی گرفته‌اید، ناخواسته یک زمان‌بندی دائمی ساخته‌اید. POST /queriesهرگز این کار را نمی‌کند.

خواندن نتیجه در مسیر قدیمی

GET /api/results — به ?task_id= یا ?domain=&keyword= (با city اختیاری) نیاز دارد.

نمونهٔ درخواست:

curl -s -H "X-API-Key: $KEY" \
  "https://serp.rasaad.ir/api/results?domain=example.ir&keyword=shoes"

نمونهٔ پاسخ:

{"ok": true, "task_id": "…", "result": { … }}

یا برای قالب تاریخچه‌ای:

{"ok": true, "domain": "…", "keyword": "…", "city": "",
 "count": 1, "results": [ { … }, … ]}

قالب تاریخچه‌ای، تاریخچهٔ رتبه است و اسناد را جدیدترین اول، کامل و بدون حلقهٔ صفحه‌بندی برمی‌گرداند.

مقایسه با API منابع

GET /api/results/api/v1/operator/results
جست‌وجو باtask_id، یا domain+keyword(+city)target_id، یا domain/keyword/city
فیلترهاهیچtarget_id، domain، keyword، city، since، until
صفحه‌بندیفقط limit (حداکثر ۱۰۰)limit + کرسر امضاشده، با total
کلید بدنهresultsrows
احراز هویتکلید یا کوکی داشبوردفقط کلید API
حالت domain+keyword روی هر دو کار می‌کند و همان ردیف‌ها را برمی‌گرداند. یعنی کلاینتی که فقط همین فرم را استفاده کرده بدون تغییر می‌تواند مهاجرت کند. فقط کلید بدنه از results به rows عوض می‌شود و total اضافه می‌شود.

کوکی نشست داشبورد

مسیرهای قدیمی کلید API یا کوکی نشست داشبورد را می‌پذیرند. API منابع فقط کلید API.

برای اتصال از سمت سرور از کلید API استفاده کنید، نه کوکی. کوکی وابسته به مرورگر است و در درخواست‌های بین‌سروری کار نمی‌کند؛ جایش در کد سمت سرور نیست.

محدودیت نرخ ندارند

مسیرهای قدیمی محدودیت نرخ ندارند و هدرهای X-RateLimit-* را نمی‌فرستند.

اگر در حال مهاجرت هستید، فرض نکنید این هدرها را می‌گیرید. کدی که X-RateLimit-Remaining را می‌خواند باید نبودن هدر را مدیریت کند — نه اینکه KeyError بدهد و نه اینکه عدد ساختگی بگذارد.

سلامت استخراج

GET /selector-health — نیازمند results:read.

گزارش سلامت استخراج: می‌گوید نتایج اخیر هنوز درست خوانده می‌شوند یا نه. پارامتر ?limit= (از ۱۰ تا ۱۰۰۰، پیش‌فرض ۲۰۰) سقف می‌گذارد چند اجرای اخیر بررسی شوند.

نمونهٔ درخواست:

curl -s -H "X-API-Key: $KEY" \
  https://serp.rasaad.ir/api/selector-health?limit=200

نمونهٔ پاسخ:

{
  "ok": true,
  "verdict": {
    "status": "ok",
    "runs_considered": 42, "runs_total": 51, "blocked": 9,
    "rotated": [
      {"selector": "display_domain", "runs_seen": 42, "runs_hit": 36,
       "hit_rate": 0.857, "distinct_bots": 6,
       "last_hit": "2026-10-02T08:14:02Z", "runs_since_hit": 6,
       "selectors": ["span.VuuXrf"]}
    ],
    "partial": [], "stale_versions": {"0.5.11": ["display_domain"]},
    "healthy": ["title_h3", "breadcrumb", "root"]
  }
}

status یکی از ok، partial یا rotated است و تعیین می‌کند چه کنید:

مقداریعنی چهکار کنید
okهمه‌چیز سالم استکاری لازم نیست
partialبعضی فیلدها ناقص‌انداحتیاط کنید و به پشتیبانی خبر دهید
rotatedبخشی از فیلدها دیگر قابل اعتماد نیستبه آن فیلدها اعتماد نکنید و گزارش دهید

اجراهای بلاک‌شده و خطاخورده در runs_considered شمرده نمی‌شوند، چون صفحهٔ کپچا نتیجه‌ای برای سنجش ندارد. برای همین runs_considered می‌تواند کمتر از runs_total باشد.

این گزارش به یک مالک خاص محدود نیست. وضعیت کلی سرویس را نشان می‌دهد، پس فقط یک کلید معتبر لازم دارد و به کارهای شما محدود نمی‌شود. برای فراخوانی دوره‌ای مناسب است، نه داخل هر درخواست.
اگر status چیزی غیر از ok بود، خودتان نمی‌توانید درستش کنید. به پشتیبانی خبر دهید و صبر کنید. در نتایج توضیح داده شده کدام فیلدهای سند به آن وابسته‌اند.

چک‌لیست مهاجرت

  1. POST /api/query را با POST /queries جایگزین کنید.
  2. enqueued را به deduped تبدیل کنید.
  3. results را به rows تغییر دهید.
  4. کوکی نشست را کنار بگذارید و فقط کلید API بفرستید.
  5. رفتار 429 را اضافه کنید و هدرهای X-RateLimit-* را بخوانید.