تخطَّ إلى المحتوى

نظرة عامة على حزمة تطوير برامج Python

نظرة عامة على حزمة تطوير برامج ⁨Python⁩

قسم بعنوان «نظرة عامة على حزمة تطوير برامج ⁨Python⁩»

حزمة تطوير برامج ⁨Python⁩‏ (⁨SDK⁩) من ⁨NextPDF⁩ مخصّصة لتطبيقات ⁨Python⁩ التي تحتاج إلى استخراج محتوى ملفات تنسيق المستندات المنقولة‏ (⁨PDF⁩) مع تتبّع المصدر. تُعيد كتلًا مهيكلة مزوّدة بمراسي استشهاد، وتتضمّن فهرس الصفحة، ودرجة الثقة، والمربّعات المحيطة الاختيارية، ومعرّفات العُقد الدلالية عندما يكشف ملف ⁨PDF⁩ المصدر عن تلك البنية.

استخدم حزمة ⁨SDK⁩ عندما يحتاج مسار المعالجة لديك إلى الإجابة عن أسئلة مثل “من أي صفحة أتى هذا النص؟”، و”أي جدول يدعم هذه القيمة؟”، أو “ما الذي تغيّر بين ملفي ⁨PDF⁩ هذين؟” من دون التعامل مع ناتج استخراج ⁨PDF⁩ كنص عادي مجهول المصدر.

  • عميل NextPDF متزامن للنصوص البرمجية، والمهام المُجمّعة، ودفاتر الملاحظات.
  • عميل AsyncNextPDF غير متزامن لـ asyncio، و ⁨FastAPI⁩، وبيئات التشغيل غير المتزامنة الأخرى.
  • واجهة سطر أوامر‏ (⁨CLI⁩) باسم nextpdf للاستخراج بطلب واحد من مسار ملف أو من الإدخال القياسي، مع الكتابة إلى الإخراج القياسي أو إلى ملف.
  • خادم اختياري لبروتوكول سياق النموذج‏ (⁨MCP⁩) كي يتمكّن وكلاء الذكاء الاصطناعي‏ (⁨AI⁩) من استدعاء أدوات استخراج ⁨PDF⁩ مباشرةً.
  • خلفية بعيدة للاستخدام في بيئة الإنتاج مع ⁨NextPDF Connect.⁩
  • خلفية محلية للاستخراج دون اتصال، باستخدام المكتبة فقط، عبر pypdf.

تُرسل الخلفية البعيدة بايتات ⁨PDF⁩ إلى خادم ⁨NextPDF Connect.⁩ وهذا هو مسار الإنتاج المُوصى به لأنه يجعل سلوك الاستخراج، والمصادقة، والحصص، وعناصر التحكم التشغيلية مُمركزة.

تعمل الخلفية المحلية داخل عملية ⁨Python⁩ وتقرأ ملفات ⁨PDF⁩ عبر pypdf. وهي مفيدة للتطوير دون اتصال ولملفات ⁨PDF⁩ الموسومة، لكنها لا تستطيع توفير مربّعات محيطة دقيقة، وتستخدم استخراجًا استدلاليًا على مستوى الفقرة لملفات ⁨PDF⁩ غير الموسومة. الخلفية المحلية متاحة عبر المكتبة فقط: مرّر LocalBackend إلى AsyncNextPDF لاستخدامها. لا يمكن لواجهة سطر الأوامر nextpdf ولا لخادم ⁨MCP⁩ استخدامها. راجع مصفوفة اختيار الخلفية للمقارنة الكاملة.

لا تنفّذ حزمة ⁨SDK⁩ التعرّف الضوئي على الحروف‏ (⁨OCR⁩). تحتاج ملفات ⁨PDF⁩ الممسوحة ضوئيًا أو المكوّنة من صور فقط إلى خطوة ⁨OCR⁩ قبل أن يتمكّن ⁨NextPDF⁩ من استخراج النص المُضمّن. كما يمكن للتخطيطات المعقّدة، والنصوص المتداخلة، ومُنتِجات ⁨PDF⁩ غير المعتادة أن تقلّل من جودة الاستخراج.

واجهة سطر الأوامر nextpdf بعيدة فقط وليست واجهة بثّ. يقرأ كل أمر ملف ⁨PDF⁩ بالكامل إلى الذاكرة (من مسار ملف أو من الإدخال القياسي)، ويُرسله إلى خادم ⁨NextPDF Connect⁩، ويبني النتيجة الكاملة في الذاكرة، ويُسلسِلها في عملية كتابة واحدة. يمكنك إعادة توجيه ذلك الإخراج إلى ملف باستخدام --output (أو -o) أو إلى الإخراج القياسي، لكن النتيجة تُحفظ مؤقتًا في الذاكرة بالكامل ولا تُنتَج تدريجيًا. لا تستطيع واجهة سطر الأوامر استخدام خلفية pypdf المحلية.

اختيار عميل: متزامن مقابل غير متزامن

قسم بعنوان «اختيار عميل: متزامن مقابل غير متزامن»

يشترك العميلان في مساحة أسماء واحدة لأساليب ast الخاصة بعمليات شجرة بناء الجملة المجرّدة‏ (⁨AST⁩)، ويُعيدان نماذج ⁨Pydantic⁩ نفسها. ولا يختلفان إلا في نموذج التزامن.

سياقكالاستخدامالسبب
النصوص البرمجية والمهام المُجمّعةNextPDF (متزامن)تدفّق تحكّم خطّي؛ لا حلقة أحداث لإدارتها.
دفاتر ملاحظات ⁨Jupyter⁩NextPDF (متزامن)يكتشف run_sync حلقة الأحداث قيد التشغيل ويوزّع العمل إلى مؤشّر ترابط عامل، فتعمل الاستدعاءات الحاجبة داخل الخلايا.
واجهة سطر الأوامر nextpdfNextPDF (متزامن، داخلي)تبني واجهة سطر الأوامر عميلًا متزامنًا نيابةً عنك.
خدمات asyncioAsyncNextPDFawait أصلي؛ من دون تحويل العمل إلى مؤشّر ترابط.
⁨FastAPI⁩، و ⁨Starlette⁩، وواجهة بوابة الخادم غير المتزامنة‏ (⁨ASGI⁩)AsyncNextPDFيشارك حلقة أحداث الطلب وتجمّع الاتصالات نفسه.
التوزيع عالي التزامنAsyncNextPDFشغّل عمليات استخراج كثيرة بالتزامن باستخدام asyncio.gather عبر عميل واحد مُجمّع.

يغلّف NextPDF نسخة AsyncNextPDF داخلية ويُشغّل كل استدعاء عبر run_sync. داخل حلقة أحداث قيد التشغيل، مثل دفتر ملاحظات، يوزّع run_sync الإجراء المتزامن إلى مؤشّر ترابط عامل واحد بحلقته الخاصة، فلا تواجه خطأ asyncio.run المتداخل. في خدمة asyncio أو ⁨ASGI⁩، استدعِ AsyncNextPDF مباشرةً بدلًا من تحمّل تكلفة تحويل العمل إلى مؤشّر ترابط في كل استدعاء.

يمتلك العميل غير المتزامن نسخة httpx.AsyncClient لتجميع الاتصالات، لذا أعد استخدام نسخة AsyncNextPDF واحدة وأغلقها مرّة واحدة. لا يكشف العميل المتزامن NextPDF أسلوب close(). لأحمال العمل غير المتزامنة طويلة الأمد، فضّل AsyncNextPDF وأدِر دورة حياته صراحةً (راجع النموذج التشغيلي للإنتاج).

تنفّذ كل خلفية بروتوكول PdfBackend. تُحدَّد الخلفية البعيدة (RemoteBackend) تلقائيًا عندما تمرّر base_url و api_key. ويجب حقن الخلفية المحلية (LocalBackend) صراحةً عبر معامل backend= الخاص بـ AsyncNextPDF؛ فهي غير مُصدَّرة من حزمة nextpdf العليا وغير قابلة للوصول من واجهة سطر الأوامر أو من خادم ⁨MCP.⁩

القدرةبعيدة (RemoteBackend)محلية (LocalBackend)
تُحدَّد بواسطةbase_url + api_keyAsyncNextPDF(backend=LocalBackend(...))
الشبكة⁨NextPDF Connect⁩ عبر بروتوكول نقل النص التشعّبي الآمن‏ (⁨HTTPS⁩)لا شيء؛ تعمل ضمن العملية
المصادقة، والحصص، والقياسمُمركزة على الخادملا شيء
إمكانية المراقبة وعناصر التحكم التشغيليةمن جانب الخادملا شيء
استخراج ⁨PDF⁩ الموسوم (⁨StructTree⁩)نعمنعم
استخراج ⁨PDF⁩ غير الموسوممحرّك الخادمتقسيم فقرات استدلالي، درجة الثقة 0.5
المربّعات المحيطةنعم (عندما يوفّرها الخادم)لا (bbox يساوي None)
استخراج الجداول من ملفات ⁨PDF⁩ غير الموسومةمحرّك الخادملا تُعيد أي جداول
قابلة للوصول من واجهة سطر الأوامر / خادم ⁨MCP⁩نعملا (عبر المكتبة فقط)
مُوصى بها لـالإنتاجالتطوير دون اتصال، واختبارات ملفات ⁨PDF⁩ الموسومة

استخدم الخلفية البعيدة في الإنتاج لأنها المسار الوحيد المزوّد بمصادقة مُمركزة، وفرض للحصص، وقياس، وإمكانية مراقبة. استخدم الخلفية المحلية للتطوير دون اتصال وللاختبارات على ملفات ⁨PDF⁩ الموسومة، مع قبول النتائج الاستدلالية، وغياب المربّعات المحيطة، وغياب الجداول في المُدخلات غير الموسومة.

"""Inject the local backend for offline, library-only extraction."""
from nextpdf import AsyncNextPDF
from nextpdf.backends.local import LocalBackend
async def extract_offline(pdf_bytes: bytes) -> None:
"""Extract cited text without a NextPDF Connect server."""
async with AsyncNextPDF(backend=LocalBackend()) as client:
blocks = await client.ast.extract_cited_text(pdf_bytes)
for block in blocks:
# Heuristic blocks on untagged PDFs report confidence 0.5.
print(block.citation.confidence, block.text)

في الإنتاج، شغّل الخلفية البعيدة مع ⁨NextPDF Connect.⁩ تغطّي الأنماط أدناه إعادة استخدام العميل، ومعالجة الأخطاء، وإعادة المحاولات، ومعالجة الحصص، والمُهَل. كل رمز برمجي مستخدَم هنا موجود في حزمة ⁨SDK.⁩ لا تُعيد حزمة ⁨SDK⁩ المحاولة نيابةً عنك، لذا فإن حلقة إعادة المحاولة من مسؤوليتك.

أعد استخدام العميل وجمّع الاتصالات

قسم بعنوان «أعد استخدام العميل وجمّع الاتصالات»

يحتفظ RemoteBackend بنسخة httpx.AsyncClient دائمة واحدة لتجميع الاتصالات. أنشئ AsyncNextPDF مرّة واحدة، وشاركه عبر الطلبات، وأغلقه عند إيقاف التشغيل. لا تُنشئ عميلًا لكل طلب.

"""Reuse one pooled async client for the lifetime of the process."""
import asyncio
import os
from pathlib import Path
from nextpdf import AsyncNextPDF
async def main() -> None:
"""Run several extractions over a single pooled client."""
base_url = os.environ["NEXTPDF_BASE_URL"]
# Treat the API key as a secret; read it from the environment, never hard-code it.
api_key = os.environ["NEXTPDF_API_KEY"]
async with AsyncNextPDF(base_url=base_url, api_key=api_key) as client:
pdf_paths = (Path("a.pdf"), Path("b.pdf"), Path("c.pdf"))
tasks = [
client.ast.get_document_ast(path.read_bytes())
for path in pdf_paths
]
documents = await asyncio.gather(*tasks)
for document in documents:
print(document.page_count, document.estimated_tokens)
if __name__ == "__main__":
asyncio.run(main())

يستدعي مدير السياق غير المتزامن close() عند الخروج، ما يُغلق وسيلة النقل الأساسية. من دون مدير سياق، استدعِ await client.close() بنفسك.

عالج الأخطاء باستخدام التسلسل الهرمي للاستثناءات

قسم بعنوان «عالج الأخطاء باستخدام التسلسل الهرمي للاستثناءات»

تطرح حزمة ⁨SDK⁩ استثناءات ضمن تسلسل هرمي ذي أنواع محدّدة. تشتق جميع الأخطاء من NextPDFError؛ وتشتق إخفاقات مستوى بروتوكول نقل النص التشعّبي‏ (⁨HTTP⁩) من NextPDFAPIError وتحمل status_code. التقط الأنواع المحدّدة التي يمكنك التعامل معها، واستخدم النوع الأساسي كحلّ احتياطي.

الاستثناءيُطلق عندالسمات الأساسية
NextPDFErrorالنوع الأساسي لكل خطأ في حزمة ⁨SDK⁩status_code
NextPDFAPIErrorأي خطأ ⁨HTTP⁩ من الخادمstatus_code، error_code
NextPDFLicenseError⁨HTTP 402⁩؛ الميزة تتطلّب فئة خادم أعلىstatus_code (402)
QuotaExceededError⁨HTTP 429⁩؛ تجاوز حدّ المعدّل أو الحصةretry_after
AstNoStructTreeError⁨HTTP 422⁩؛ ملف ⁨PDF⁩ غير موسوم مع إيقاف الوضع الاستدلاليstatus_code (422)
AstBuildTimeoutError⁨HTTP 504⁩؛ انتهت مهلة بناء ⁨AST⁩status_code (504)
"""Map SDK exceptions to caller-facing outcomes."""
from nextpdf import (
AstBuildTimeoutError,
AstNoStructTreeError,
AsyncNextPDF,
NextPDFAPIError,
NextPDFError,
NextPDFLicenseError,
QuotaExceededError,
)
async def safe_extract(client: AsyncNextPDF, pdf_bytes: bytes) -> str:
"""Extract text, translating known failures into a stable status string."""
try:
blocks = await client.ast.extract_cited_text(pdf_bytes)
except QuotaExceededError as exc:
# exc.retry_after holds the server Retry-After value in seconds, or None.
return f"rate-limited; retry after {exc.retry_after}s"
except NextPDFLicenseError:
return "feature requires a higher server tier"
except AstNoStructTreeError:
return "untagged PDF; enable heuristic mode or use a tagged PDF"
except AstBuildTimeoutError:
return "build timed out; reduce the page range"
except NextPDFAPIError as exc:
return f"server error (status {exc.status_code})"
except NextPDFError:
return "extraction failed"
return "\n".join(block.text for block in blocks)

أعد محاولة الإخفاقات العابرة مع التراجع التدريجي

قسم بعنوان «أعد محاولة الإخفاقات العابرة مع التراجع التدريجي»

لا تُعيد حزمة ⁨SDK⁩ المحاولة تلقائيًا. غلّف الاستدعاءات ضمن حلقة خاصة بك تُعيد المحاولة عند إخفاقات ⁨HTTP⁩ العابرة وتلتزم بقيمة Retry-After من الخادم، التي يكشفها QuotaExceededError باسم retry_after (عدد صحيح من الثواني، أو None). استخدم التراجع الأسّي لحالات ⁨HTTP⁩ العابرة الأخرى، ولا تُعِد محاولة NextPDFLicenseError.

"""Retry transient failures with exponential backoff and Retry-After support."""
import asyncio
from collections.abc import Awaitable, Callable
from typing import TypeVar
from nextpdf import NextPDFAPIError, QuotaExceededError
_RETRYABLE_STATUS = frozenset({500, 502, 503, 504})
_T = TypeVar("_T")
async def with_retry(
coro_factory: Callable[[], Awaitable[_T]],
*,
max_attempts: int = 4,
) -> _T:
"""Call coro_factory() with bounded retries on transient server errors.
Args:
coro_factory: A zero-argument callable returning a fresh awaitable.
max_attempts: Maximum number of attempts before giving up.
Returns:
The awaited result of the first successful attempt.
Raises:
NextPDFAPIError: When all attempts fail or the error is not retryable.
"""
delay = 1.0
for attempt in range(1, max_attempts + 1):
try:
return await coro_factory()
except QuotaExceededError as exc:
if attempt == max_attempts:
raise
await asyncio.sleep(exc.retry_after if exc.retry_after is not None else delay)
delay *= 2.0
except NextPDFAPIError as exc:
if attempt == max_attempts or exc.status_code not in _RETRYABLE_STATUS:
raise
await asyncio.sleep(delay)
delay *= 2.0
raise RuntimeError("unreachable")

أدِر الحصص، وحدود المعدّل، والمُهَل

قسم بعنوان «أدِر الحصص، وحدود المعدّل، والمُهَل»

يفرض الخادم الحصص وحدود المعدّل. عند ⁨HTTP 429⁩، تُطلق حزمة ⁨SDK⁩ QuotaExceededError وتحلّل ترويسة Retry-After إلى retry_after. كما تكشف الخلفية البعيدة ترويسات X-RateLimit-* على استجابات العرض، فيمكنك التحكّم في المعدّل بشكل استباقي قبل بلوغ حدّ صارم.

تستخدم مُهَل الطلبات قيمة افتراضية ثابتة قدرها 60 ثانية إجمالًا مع مهلة اتصال 10 ثوانٍ (httpx.Timeout(60.0, connect=10.0)). لتقييد عمليات بناء ⁨AST⁩ الطويلة، ضيّق نطاق العمل باستخدام page_range_start، أو page_range_end، أو token_budget بدلًا من الاعتماد على المهلة وحدها؛ فعملية البناء المفرطة في الطول تُعيد AstBuildTimeoutError (⁨HTTP 504⁩).

يقرأ عامل المهام المُجمّعة ملفات ⁨PDF⁩، ويستخرج النص المُستشهَد به، ويكتب مخرجات مهيكلة. أعد استخدام عميل مُجمّع واحد، وقيّد التزامن بإشارة منع، وطبّق أداة إعادة المحاولة المساعدة الموضّحة أعلاه.

"""Batch-extract a directory of PDFs over one pooled async client."""
import asyncio
import os
from pathlib import Path
from nextpdf import AsyncNextPDF
async def run_batch(input_dir: Path, concurrency: int = 8) -> None:
"""Extract cited text for every PDF in input_dir, bounded by concurrency."""
semaphore = asyncio.Semaphore(concurrency)
async def worker(client: AsyncNextPDF, path: Path) -> None:
async with semaphore:
blocks = await client.ast.extract_cited_text(path.read_bytes())
out = path.with_suffix(".txt")
out.write_text("\n".join(b.text for b in blocks), encoding="utf-8")
async with AsyncNextPDF(
base_url=os.environ["NEXTPDF_BASE_URL"],
api_key=os.environ["NEXTPDF_API_KEY"],
) as client:
await asyncio.gather(*(worker(client, p) for p in input_dir.glob("*.pdf")))

تشارك خدمة ⁨FastAPI⁩ نسخة AsyncNextPDF واحدة عبر الطلبات طوال عمر التطبيق، فيُعيد كل طلب استخدام تجمّع الاتصالات. اقرأ بيانات الاعتماد من البيئة وتعامل مع مفتاح ⁨API⁩ كسرّ.

"""FastAPI service that shares one pooled NextPDF client across requests."""
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, UploadFile
from nextpdf import AsyncNextPDF
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Create the pooled client on startup and close it on shutdown."""
app.state.client = AsyncNextPDF(
base_url=os.environ["NEXTPDF_BASE_URL"],
api_key=os.environ["NEXTPDF_API_KEY"],
)
try:
yield
finally:
await app.state.client.close()
app = FastAPI(lifespan=lifespan)
@app.post("/extract")
async def extract(file: UploadFile) -> dict[str, list[str]]:
"""Return cited text blocks for an uploaded PDF."""
pdf_bytes = await file.read()
blocks = await app.state.client.ast.extract_cited_text(pdf_bytes)
return {"text": [block.text for block in blocks]}

لوكلاء الذكاء الاصطناعي، شغّل خادم ⁨MCP.⁩ يوفّر أدوات ⁨PDF⁩ (على سبيل المثال nextpdf_extract_text، و nextpdf_extract_tables، و nextpdf_get_ast، و nextpdf_info، و nextpdf_search، و nextpdf_get_outline، و nextpdf_diff، و nextpdf_health) عبر الإدخال والإخراج القياسيين. يقرأ الخادم NEXTPDF_BASE_URL و NEXTPDF_API_KEY من البيئة، لذلك فهو مدعوم عن بُعد؛ وكواجهة سطر الأوامر، لا يستطيع استخدام الخلفية المحلية. ثبّت الإضافة الاختيارية وشغّل الوحدة.

Terminal window
pip install "nextpdf[mcp]"
python -m nextpdf.mcp

راجع خادم ⁨Python MCP⁩ لجولة تكامل الوكيل، و واجهة سطر أوامر ⁨Python⁩ لاستخدام الطرفية، و مرجع واجهة ⁨Python⁩ البرمجية لكامل واجهة العميل والنموذج والاستثناءات.