نتایج گوگل

صفحه‌بندی

صفحه‌بندی کرسری (keyset) در سرویس نتایج گوگل؛ معنای total و next_cursor، چرا کرسر را نباید ساخت یا تفسیر کرد، و محدودهٔ limit.

همهٔ اندپوینت‌های فهرست در این سرویس به شکل یکسان صفحه‌بندی می‌شوند: هر پاسخ تعداد محدودی ردیف دارد، به‌علاوهٔ یک کرسر (cursor) برای صفحهٔ بعد.

شکل پاسخ

{"ok": true, "rows": [...], "total": 412, "next_cursor": "eyJpZCI6NDl9.8f2c…"}
فیلدیعنی چه
rowsردیف‌های همین صفحه
totalتعداد همهٔ ردیف‌های پیدا شده، نه اندازهٔ صفحه
next_cursorفقط وقتی صفحهٔ دیگری ممکن باشد
total اندازهٔ صفحه نیست. اگر rows شما ۵۰ تا دارد ولی total ۴۱۲ است، یعنی ۴۱۲ ردیف منطبق وجود دارد و شما ۵۰ تای اول را می‌بینید. برای نوار پیشرفت از total استفاده کنید، نه از طول rows.

قاعدهٔ next_cursor

next_cursor فقط وقتی هست که صفحهٔ دیگری وجود داشته باشد — و در دو حالت نیست:

حالتچرا
صفحهٔ آخرچیزی باقی نمانده
صفحهٔ ناقصآن‌قدر ردیف نبود که صفحهٔ دیگری لازم باشد
«نبودن» را یعنی «تمام شد» بخوانید، نه خطا. پایان پیمایش را نبودن next_cursor اعلام می‌کند، نه پاسخ خالی. حلقهٔ درست تا وقتی next_cursor هست ادامه می‌دهد و وقتی نیست تمام می‌شود.

استفاده از کرسر

کرسر را همان‌طور که گرفتید در درخواست بعدی بفرستید:

NEXT=$(curl -s -H "X-API-Key: $KEY" \
  "https://serp.rasaad.ir/api/v1/operator/results?domain=example.ir&keyword=shoes" \
  | jq -r '.next_cursor')

curl -s -H "X-API-Key: $KEY" \
  "https://serp.rasaad.ir/api/v1/operator/results?domain=example.ir&keyword=shoes&cursor=$NEXT"
کرسر مبهم و امضاشده است. آن را تفسیر نکنید، از درونش چیزی بیرون نکشید و خودتان نسازید. یک کرسر جعلی، منقضی یا خراب 400 می‌گیرد — هرگز بی‌صدا به صفحهٔ اول برنمی‌گردد. اگر کد شما 400 را با «شروع از اول» مدیریت می‌کند، درست کار می‌کند؛ اگر آن را نادیده می‌گیرد، بی‌نهایت حلقه می‌زنید.

چرا keyset و نه offset

صفحه‌بندی از نوع کرسری (keyset) است، نه مبتنی بر offset.

اگر وسط پیمایش ردیفی اضافه یا حذف شود:

روشچه اتفاقی می‌افتد
OFFSETممکن است ردیفی را رد کنید یا تکرار کنید
کرسرردیفی را جا نمی‌اندازد و تکرار نمی‌کند
اگر داده‌ها مرتب اضافه و کم می‌شوند، این تفاوت واقعی است. با OFFSET در صفحهٔ پنجم ممکن است ردیفی را که در صفحهٔ چهارم دیده‌اید دوباره ببینید، چون ترتیب فهرست عوض شده. کرسر این مشکل را ندارد.

limit

مسیرپیش‌فرضبازه
همهٔ اندپوینت‌های فهرست501 تا 200
GET /targets/{id}/history2001 تا 1000

تاریخچهٔ هدف سقف بزرگ‌تری دارد چون معمولاً برای رسم نمودار بلندمدت خوانده می‌شود، نه برای فهرست تعاملی.

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

الگوی کامل پیمایش

def paginate(session, url, key, limit=200):
    """همهٔ ردیف‌ها را با دنبال کردن next_cursor می‌خواند."""
    cursor = None
    while True:
        params = {"limit": limit}
        if cursor:
            params["cursor"] = cursor
        r = session.get(url, headers={"X-API-Key": key}, params=params)
        r.raise_for_status()
        body = r.json()
        yield from body["rows"]
        cursor = body.get("next_cursor")
        if not cursor:
            return

سه نکته که این الگو رعایت می‌کند:

نکتهچرا
body.get("next_cursor")نبودن کلید هم حالت «تمام شد» است
raise_for_status()400 یعنی کرسر خراب است، نه پایان فهرست
429 را مدیریت کنیداگر 429 گرفتید، صبر کنید و همان صفحه را دوباره بخواهید
کلید next_cursor را با .get() بخوانید، نه با براکت ([...]). در صفحهٔ آخر کلید وجود ندارد و دسترسی با براکت KeyError می‌دهد. نبودن کلید، پایان عادی فهرست است.
کرسر را نسازید، تفسیر نکنید، فقط پس بفرستید