صفحه‌بندی API گیت هاب (GitHub API Pagination) یکی از آن موضوعاتی است که تا زمانی که با آن برخورد نکنید، جدی نمی‌گیرید. وقتی با مخزنی روبرو شوید که هزاران کامیت یا صدها Pull Request دارد، درک عمیق این مکانیزم به یک ضرورت تبدیل می‌شود. GitHub API به‌صورت پیش‌فرض تنها بخشی از داده‌ها را برمی‌گرداند و کنترل کامل بر پیمایش، به عهده توسعه‌دهنده است. این مقاله، سازوکار، انواع روش‌ها و نکات عملی صفحه‌بندی در GitHub API را بررسی می‌کند.

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

چرا صفحه‌بندی در GitHub API ضروری است؟

GitHub API (رابط برنامه‌نویسی گیت هاب) به‌صورت پیش‌فرض برای کاهش بار سرور، داده‌ها را در صفحات کوچک برمی‌گرداند. این یعنی حتی اگر شما درخواست همه کامیت‌های یک مخزن را بدهید، API تنها بخشی از آن‌ها را در پاسخ قرار می‌دهد.

در مفهوم API و کاربردهای آن، این مکانیزم به‌عنوان یکی از اصول طراحی REST API شناخته می‌شود. صفحه‌بندی، نه یک محدودیت، بلکه یک ویژگی حفاظتی است.

سه دلیل اصلی برای صفحه‌بندی در GitHub API وجود دارد:

  • کاهش بار سرور: ارسال همه داده‌ها در یک درخواست، بار زیادی ایجاد می‌کند.
  • کاهش زمان پاسخ: پاسخ‌های کوچک، سریع‌تر ارسال می‌شوند.
  • کاهش مصرف پهنای باند: درخواست‌های بیش از حد بزرگ، پهنای باند را هدر می‌دهند.

صفحه‌بندی در GitHub API، یک محدودیت نیست؛ یک قرارداد است که به شما کنترل کامل می‌دهد.

هدر Link (Link Header) مکانیزم اصلی صفحه‌بندی در GitHub API است. در این بخش، این مکانیزم بررسی می‌شود.

ساختار هدر Link

هدر Link شامل چهار لینک اصلی است: first، prev، next و last. هر لینک، URL مربوط به همان صفحه را مشخص می‌کند.

Link: <https://api.github.com/repos/owner/repo/issues?page=2>; rel="next",
      <https://api.github.com/repos/owner/repo/issues?page=10>; rel="last"

این ساختار، امکان پیمایش دقیق بین صفحات را فراهم می‌کند.

پیمایش مبتنی بر هدر Link

بهترین رویکرد برای صفحه‌بندی در GitHub API، استفاده از هدر Link است، نه ساخت دستی URL. دلیل این است که ساختار URL ممکن است در نسخه‌های بعدی API تغییر کند، اما هدر Link پایدار است.

relمعناکاربرد
firstصفحه اولبازگشت به ابتدا
prevصفحه قبلیپیمایش معکوس
nextصفحه بعدیپیمایش پیشرو
lastصفحه آخرتشخیص پایان لیست

این جدول، ساده‌سازی‌شده یک منطق پیچیده‌تر است. در پیاده‌سازی واقعی، ترکیب این لینک‌ها با اصول طراحی REST API نیازمند توجه است.

پیمایش برنامه‌نویسی‌شده

برای پیمایش برنامه‌نویسی‌شده، باید در هر درخواست، هدر Link را تجزیه کرده و URL صفحه بعدی را استخراج کنید. این فرآیند تا زمانی که هدر rel="next" وجود داشته باشد، ادامه می‌یابد.

import requests

url = "https://api.github.com/repos/owner/repo/issues"
while url:
    response = requests.get(url)
    data = response.json()
    # پردازش داده‌ها
    url = response.links.get("next", {}).get("url")

این کد، ساده‌ترین الگوی پیمایش است. در راهنمای احراز هویت REST API، اصول احراز هویت برای این درخواست‌ها بررسی شده است.

پارامترهای page و per_page

پارامترهای page و per_page امکان کنترل دستی صفحه‌بندی را فراهم می‌کنند. در این بخش، این پارامترها بررسی می‌شوند.

پارامتر per_page

پارامتر per_page تعداد آیتم‌های هر صفحه را مشخص می‌کند. مقدار پیش‌فرض ۳۰ و مقدار حداکثر ۱۰۰ است. استفاده از مقادیر بالاتر، تعداد درخواست‌ها را کاهش می‌دهد، اما زمان پاسخ هر درخواست را افزایش می‌دهد.

پارامتر page

پارامتر page شماره صفحه مورد نظر را مشخص می‌کند. ترکیب این دو پارامتر، امکان پیمایش دقیق را فراهم می‌کند.

https://api.github.com/repos/owner/repo/issues?page=2&per_page=50

این URL، صفحه دوم را با ۵۰ آیتم در هر صفحه درخواست می‌کند.

محدودیت‌های استفاده از page و per_page

در GitHub API، پارامتر page تنها تا ۱۰۰۰ صفحه پشتیبانی می‌شود. این یعنی با per_page=100، حداکثر ۱۰۰,۰۰۰ آیتم قابل دسترسی است. برای فراتر از این محدوده، باید از فیلترهای دیگر (مانند بازه زمانی) استفاده کرد.

صفحه‌بندی مبتنی بر Cursor در GraphQL

GitHub API نسخه ۴ (GraphQL) از صفحه‌بندی مبتنی بر Cursor (مکان‌نما) استفاده می‌کند. در این بخش، این مکانیزم بررسی می‌شود.

تفاوت REST و GraphQL در صفحه‌بندی

در REST API، صفحه‌بندی بر اساس شماره صفحه انجام می‌شود. در GraphQL، صفحه‌بندی بر اساس Cursor انجام می‌شود که یک شناسه یکتا برای هر آیتم است.

ساختار Cursor

در GraphQL، هر اتصال (Connection) دارای دو فیلد اصلی است: edges و pageInfo. فیلد pageInfo شامل hasNextPage، hasPreviousPage، startCursor و endCursor است.

query {
  repository(owner: "owner", name: "repo") {
    issues(first: 50, after: "cursor_value") {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          title
        }
      }
    }
  }
}

این ساختار، پیمایش دقیق‌تر و پایدارتری را فراهم می‌کند. در تفاوت REST و GraphQL، این موضوع به‌تفصیل بررسی شده است.

صفحه‌بندی مبتنی بر Cursor در GraphQL، برای داده‌های در حال تغییر مناسب‌تر است، زیرا مشکل «پرش آیتم‌ها» را حل می‌کند.

مدیریت Rate Limit و تأثیر صفحه‌بندی

مدیریت Rate Limit (محدودیت نرخ درخواست) یکی از چالش‌های اصلی کار با GitHub API است. در این بخش، این چالش بررسی می‌شود.

محدودیت‌های پیش‌فرض

GitHub API برای درخواست‌های احراز هویت‌نشده، محدودیت ۶۰ درخواست در ساعت دارد. برای درخواست‌های احراز هویت‌شده با Personal Access Token، این محدودیت ۵۰۰۰ درخواست در ساعت است.

تأثیر صفحه‌بندی بر Rate Limit

هر درخواست صفحه‌بندی، یک درخواست مستقل محسوب می‌شود. بنابراین، پیمایش یک لیست بلند با per_page=30، درخواست‌های بیشتری نسبت به per_page=100 مصرف می‌کند. در امنیت API، این موضوع به‌عنوان یکی از اصول مدیریت منابع شناخته می‌شود.

استراتژی‌های کاهش مصرف

برای کاهش مصرف Rate Limit، راهکارهای زیر پیشنهاد می‌شود:

  • استفاده از per_page=100 به‌جای مقادیر کوچک‌تر.
  • استفاده از فیلترها برای کاهش تعداد نتایج.
  • ذخیره‌سازی موقت نتایج در حافظه محلی.
  • استفاده از Conditional Requests با هدر If-None-Match.

پیاده‌سازی عملی در پروژه‌های واقعی

پیاده‌سازی عملی صفحه‌بندی در پروژه‌های واقعی، نیازمند درکی دقیق از ابزارها و کتابخانه‌های موجود است. در این بخش، این پیاده‌سازی بررسی می‌شود.

استفاده از کتابخانه PyGithub

کتابخانه PyGithub در پایتون، صفحه‌بندی را به‌صورت خودکار مدیریت می‌کند. با استفاده از این کتابخانه، نیازی به مدیریت دستی هدر Link ندارید.

from github import Github

g = Github("token")
repo = g.get_repo("owner/repo")
for issue in repo.get_issues():
    print(issue.title)

این کد، تمام issue‌ها را با صفحه‌بندی خودکار پیمایش می‌کند.

استفاده از Octokit در JavaScript

کتابخانه Octokit در JavaScript نیز صفحه‌بندی را به‌صورت خودکار مدیریت می‌کند. این کتابخانه، برای پروژه‌های Node.js بسیار مناسب است.

const { Octokit } = require("@octokit/rest");
const octokit = new Octokit({ auth: "token" });

for await (const response of octokit.paginate.iterator(
  octokit.rest.issues.listForRepo,
  { owner: "owner", repo: "repo", per_page: 100 }
)) {
  response.data.forEach(issue => console.log(issue.title));
}

این کد، با استفاده از iterator، تمام issue‌ها را با صفحه‌بندی خودکار پیمایش می‌کند.

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

خطاهای رایج در صفحه‌بندی GitHub API

در پروژه‌های واقعی، خطاهای مشخصی تکرار می‌شوند که هرکدام می‌توانند فرآیند پیمایش را مختل کنند.

  1. نادیده گرفتن هدر Link: ساخت دستی URL به‌جای استفاده از هدر Link.
  2. مصرف بیش از حد Rate Limit: استفاده از per_page کوچک و درخواست‌های متعدد.
  3. عدم مدیریت خطای ۴۰۳: عدم مدیریت خطای Rate Limit در کد.
  4. نادیده گرفتن محدودیت ۱۰۰۰ صفحه: تلاش برای پیمایش بیش از ۱۰۰۰ صفحه با پارامتر page.
  5. عدم استفاده از Conditional Requests: درخواست‌های تکراری که پهنای باند را هدر می‌دهند.
  6. عدم مدیریت Cursor در GraphQL: نادیده گرفتن endCursor در پیمایش.
  7. عدم ذخیره‌سازی نتایج: درخواست‌های تکراری برای داده‌های ثابت.

در اشتباهات رایج در REST API، بسیاری از این موارد با شدت کمتری دیده می‌شوند، اما در GitHub API، اثر آن‌ها چند برابر است.

پرسش‌های پرتکرار درباره صفحه‌بندی API گیت هاب

چرا GitHub API همه داده‌ها را در یک درخواست برنمی‌گرداند؟

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

تفاوت صفحه‌بندی در REST و GraphQL چیست؟

در REST API، صفحه‌بندی بر اساس شماره صفحه انجام می‌شود. در GraphQL، صفحه‌بندی بر اساس Cursor انجام می‌شود که یک شناسه یکتا برای هر آیتم است. صفحه‌بندی مبتنی بر Cursor برای داده‌های در حال تغییر مناسب‌تر است.

چگونه می‌توان مصرف Rate Limit را کاهش داد؟

سه راهکار اصلی: استفاده از per_page=100، استفاده از Conditional Requests و ذخیره‌سازی موقت نتایج. در بهینه‌سازی عملکرد REST API، این راهکارها بررسی شده است.

آیا محدودیت ۱۰۰۰ صفحه در GitHub API قابل افزایش است؟

خیر. برای فراتر از این محدوده، باید از فیلترهای دیگر (مانند بازه زمانی یا نوع رویداد) استفاده کرد. برای مثال، می‌توان issueها را بر اساس بازه زمانی فیلتر کرد و سپس صفحه‌بندی نمود.

آیا استفاده از کتابخانه‌های آماده توصیه می‌شود؟

بله. کتابخانه‌هایی مانند PyGithub و Octokit صفحه‌بندی را به‌صورت خودکار مدیریت می‌کنند و خطاها را کاهش می‌دهند. اما درک مکانیزم پایه، برای عیب‌یابی ضروری است.

نتیجه‌گیری

صفحه‌بندی API گیت هاب، یک حوزه تخصصی است که نیازمند درکی عمیق از مکانیزم REST و GraphQL است. موفقیت در این حوزه، نه با ساخت دستی URL، بلکه با استفاده از هدر Link و کتابخانه‌های آماده به دست می‌آید. صفحه‌بندی API، ماهیتی قراردادی دارد و پیاده‌سازی باید بر همین اساس شکل گیرد. ترکیب هدر Link، مدیریت Rate Limit، Conditional Requests و ذخیره‌سازی موقت، چهار رکن اصلی موفقیت در این حوزه هستند.

اگر در پروژه‌ای با چالش صفحه‌بندی GitHub API روبرو شده‌اید، تجربه خود را در دیدگاه‌ها به اشتراک بگذارید. به‌خصوص اگر راه‌حل خلاقانه‌ای برای کاهش مصرف Rate Limit یا مدیریت Cursor پیدا کرده‌اید، می‌تواند برای خواننده بعدی مفید باشد.

🔗 برای مطالعه بیشتر درباره API، مفهوم REST API را از ابتدا بشناسید.

📚 همچنین اصول طراحی REST API می‌تواند نقطه شروع مناسبی برای درک بهتر این حوزه باشد.

برای مطالعه تخصصی‌تر درباره صفحه‌بندی، منابع معتبر خارجی نیز قابل استفاده هستند.