أتمتة التقارير: تحويل بيانات الـ API إلى ملفات PDF احترافية.


يا هلا والله بالجميع! اليوم بنغوص في موضوع مره مهم وحيوي لأي مطور أو محلل بيانات: أتمتة التقارير. بنشوف كيف نقدر نسحب بيانات من API ونحولها لملفات PDF احترافية بشكل آلي. يعني انسى الشغل اليدوي المتكرر!

الخطوة الأولى: سحب البيانات من الـ API

أول شي نحتاجه هو البيانات. غالبًا البيانات هذي تكون جاية من خدمة خارجية عن طريق API. بنستخدم مكتبة requests في بايثون، لأنها الأفضل والأسهل للتعامل مع طلبات الـ HTTP.

ملاحظة: تأكد إنك مثبت مكتبة requests عندك. إذا ما كانت مثبتة، شغل الأمر هذا في الطرفية: pip install requests.

شوف المثال البسيط هذا عشان تسحب بيانات من API (بنفترض إن عندنا API وهمي هنا):

import requests
import json

def fetch_data_from_api(api_url):
    try:
        response = requests.get(api_url)
        response.raise_for_status() # بترمي خطأ لو كان فيه مشكلة في الطلب (مثلاً 404 أو 500)
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"خطأ أثناء جلب البيانات من الـ API: {e}")
        return None

# مثال على استخدام الدالة
api_endpoint = "https://jsonplaceholder.typicode.com/posts" # API تجريبي
data = fetch_data_from_api(api_endpoint)

if data:
    print(f"تم جلب {len(data)} عنصر من الـ API.")
    # ممكن نطبع أول 3 عناصر عشان نشوف شكل البيانات
    # for item in data[:3]:
    #     print(item)
else:
    print("لم يتم جلب أي بيانات.")

الخطوة الثانية: تجهيز البيانات لملف الـ PDF

بعد ما سحبنا البيانات، غالبًا نحتاج نجهزها شوي قبل ما نحطها في الـ PDF. ممكن نحتاج نصفيها، نعدل تنسيقها، أو نختار أعمدة معينة. لغرض هذا الدرس، بنفترض إننا بنعرض بعض الحقول من البيانات اللي سحبناها.

مثلاً، لو كان الـ API يرجع لنا منشورات (posts) وفيها title و body، ممكن نختار هذي الحقول ونجهزها كقائمة من القوائم عشان تكون جاهزة للجدول في الـ PDF.

def prepare_data_for_pdf(api_data):
    if not api_data:
        return []

    # رأس الجدول
    header = ["ID", "العنوان", "المحتوى"]
    table_data = [header]

    for item in api_data:
        # تأكد إن الحقول موجودة قبل لا تحاول توصل لها
        item_id = item.get("id", "N/A")
        title = item.get("title", "N/A")
        body = item.get("body", "N/A")
        
        # ممكن نختصر المحتوى الطويل عشان ما يشوه شكل الـ PDF
        if len(body) > 100:
            body = body[:97] + "..."
            
        table_data.append([str(item_id), title, body])
    return table_data

# استخدام الدالة مع البيانات اللي سحبناها
prepared_pdf_data = prepare_data_for_pdf(data)

if prepared_pdf_data:
    print(f"تم تجهيز {len(prepared_pdf_data) - 1} صف للـ PDF.")
    # print(prepared_pdf_data[:3])

الخطوة الثالثة: توليد ملف الـ PDF باستخدام ReportLab

الآن نجي للجزء الممتع، توليد الـ PDF. بنستخدم مكتبة ReportLab في بايثون، لأنها قوية وتعطيك تحكم كبير في تصميم الـ PDF. تحتاج تثبتها أول شي:

ملاحظة: لتثبيت ReportLab، شغل الأمر هذا: pip install reportlab.

ملاحظة هامة: ReportLab لا يدعم اللغة العربية بشكل افتراضي. ستحتاج إلى توفير ملف خط (مثل Amiri أو Noto Naskh Arabic) بصيغة .ttf ووضعه في نفس مجلد سكربت البايثون، أو تحديد المسار الكامل له في الكود.

هذا كود شامل بيسوي تقرير PDF بسيط من البيانات اللي جهزناها:

from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os # لاستخدام os.path.exists للتحقق من وجود ملف الخط

# تسجيل خط يدعم اللغة العربية
# تحتاج ملف خط .ttf يدعم العربية، مثلاً 'Amiri-Regular.ttf' أو 'NotoNaskhArabic-Regular.ttf'
# تأكد من وجود ملف الخط في نفس مجلد السكربت أو حدد المسار الكامل له
ARABIC_FONT_PATH = 'Amiri-Regular.ttf' # اسم ملف الخط
ARABIC_FONT_NAME = 'ArabicFont' # اسم الخط اللي بنستخدمه في ReportLab

if not os.path.exists(ARABIC_FONT_PATH):
    print(f"تحذير: ملف الخط '{ARABIC_FONT_PATH}' غير موجود. يرجى توفيره أو تغيير اسم الخط في الكود.")
    print("قد لا يظهر النص العربي بشكل صحيح في ملف الـ PDF. سيتم استخدام خط احتياطي.")
    # Fallback to a default font if Arabic font is not found
    pdfmetrics.registerFont(TTFont(ARABIC_FONT_NAME, 'Helvetica'))
else:
    try:
        pdfmetrics.registerFont(TTFont(ARABIC_FONT_NAME, ARABIC_FONT_PATH))
    except Exception as e:
        print(f"خطأ في تسجيل الخط '{ARABIC_FONT_PATH}': {e}. قد لا تظهر النصوص العربية بشكل صحيح.")
        pdfmetrics.registerFont(TTFont(ARABIC_FONT_NAME, 'Helvetica'))
        print("سيتم استخدام خط Helvetica كبديل.")


def generate_pdf_report(filename, title, data_for_table):
    doc = SimpleDocTemplate(filename, pagesize=A4)
    styles = getSampleStyleSheet()
    story = []

    # تنسيق الخط العربي
    # ReportLab يستخدم 'alignment = 2' للمحاذاة من اليمين لليسار (RTL)
    styles['Normal'].fontName = ARABIC_FONT_NAME
    styles['Normal'].alignment = 2
    styles['h1'].fontName = ARABIC_FONT_NAME
    styles['h1'].alignment = 2
    styles['h2'].fontName = ARABIC_FONT_NAME
    styles['h2'].alignment = 2
    styles['h3'].fontName = ARABIC_FONT_NAME
    styles['h3'].alignment = 2

    # عنوان التقرير
    p_title = Paragraph(title, styles['h1'])
    story.append(p_title)
    story.append(Spacer(1, 0.2 * inch))

    # إذا كانت فيه بيانات لعرضها في جدول
    if data_for_table and len(data_for_table) > 1: # رأس الجدول + صف واحد على الأقل
        # تحديد عرض الأعمدة (ممكن تضبطها حسب احتياجك)
        # مجموع العرض لازم يكون أقل من عرض الصفحة (A4 حوالي 8.27 inch)
        # مع الهوامش الافتراضية، العرض المتاح حوالي 6.5-7 inch
        col_widths = [0.5 * inch, 2.5 * inch, 3.5 * inch] # ID, العنوان, المحتوى. تم تعديل العرض الكلي ليناسب A4.

        table = Table(data_for_table, colWidths=col_widths)
        table.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#0056b3')), # خلفية رأس الجدول
            ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), # لون نص رأس الجدول
            ('ALIGN', (0, 0), (-1, -1), 'RIGHT'), # محاذاة كل النص لليمين (للغة العربية)
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
            ('FONTNAME', (0, 0), (-1, 0), ARABIC_FONT_NAME), # خط رأس الجدول
            ('FONTSIZE', (0, 0), (-1, 0), 12),
            ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
            ('BACKGROUND', (0, 1), (-1, -1), colors.white), # الصفوف الفردية (أول صف بيانات)
            ('BACKGROUND', (0, 2), (-1, -1), colors.beige), # الصفوف الزوجية (ReportLab تتناوب، لذا هذا سيكون الصف الثاني، الرابع، وهكذا)
            ('GRID', (0, 0), (-1, -1), 1, colors.black), # خطوط الشبكة للجدول
            ('FONTNAME', (0, 1), (-1, -1), ARABIC_FONT_NAME), # خط جسم الجدول
            ('FONTSIZE', (0, 1), (-1, -1), 10),
            ('WORDWRAP', (2, 1), (2, -1)), # تفعيل التفاف الكلمات لعمود 'المحتوى'
            ('LEFTPADDING', (0,0), (-1,-1), 5), # إضافة هوامش داخلية
            ('RIGHTPADDING', (0,0), (-1,-1), 5),
            ('TOPPADDING', (0,0), (-1,-1), 5),
            ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ]))
        story.append(table)
    else:
        story.append(Paragraph("لا توجد بيانات لعرضها في التقرير.", styles['Normal']))

    doc.build(story)
    print(f"تم إنشاء ملف PDF: {filename}")

# ##############################################################################
# ######################### الجزء التنفيذي الرئيسي ############################
# ##############################################################################

# 1. جلب البيانات من API
api_endpoint = "https://jsonplaceholder.typicode.com/posts"
raw_data = fetch_data_from_api(api_endpoint)

# 2. تجهيز البيانات
if raw_data:
    pdf_table_data = prepare_data_for_pdf(raw_data)
else:
    pdf_table_data = [] # قائمة فارغة إذا لم يتم جلب أي بيانات

# 3. توليد التقرير
report_filename = "تقرير_بيانات_الـ_API.pdf"
report_title = "تقرير بيانات المنشورات من الـ API"

generate_pdf_report(report_filename, report_title, pdf_table_data)

خاتمة

كذا نكون سوينا نظام بسيط ومتكامل لسحب البيانات من API وتحويلها لتقرير PDF احترافي بشكل آلي. تقدر تبني على هذا الأساس وتضيف تعقيدات أكثر مثل رسوم بيانية، صور، أو تنسيقات شرطية. الشغل اليدوي صار من الماضي يا جماعة!

أتمنى إن الدرس كان مفيد وواضح. إذا عندك أي سؤال أو استفسار، لا تتردد!