واجهة API لأسعار الذهب في Python: الجلب والتخزين المؤقت والرسم
استخدم واجهة API لأسعار الذهب في Python: اجلب الأسعار اللحظية عبر requests، وعالج الأخطاء، وخزّن الاستجابات مؤقتًا، وارسم سلسلة زمنية بـ pandas وmatplotlib.
واجهة API لأسعار الذهب في Python: ما تحتاج إليه
يوضّح هذا الدرس كيفية استخدام واجهة API لأسعار الذهب في Python، من الطلب الأول إلى عميل مزوّد بتخزين مؤقت ورسم بياني للأشهر العشرة الأخيرة. ويستخدم MetalAPI، وهي واجهة REST بصيغة JSON للمعادن الثمينة والمعادن الصناعية والطاقة والعملات والعملات المشفرة، مع ثلاث مكتبات معروفة: requests لطلبات HTTP، وpandas للسلسلة الزمنية، وmatplotlib للرسم البياني. وتعمل كل المقتطفات على Python 3.9 أو أحدث.
أنشئ حسابًا مجانيًا وأنشئ مفتاح API ضمن API keys في لوحة التحكم. تبدأ المفاتيح بـ mk_live_ ولا تُعرض إلا مرة واحدة، لذا انسخ مفتاحك مباشرةً إلى متغير بيئة. لا تتطلب خطة Free بطاقة ائتمان، وتتضمن 100 طلب شهريًا مع تحديث يومي للأسعار، وهذا يكفي لكل ما في هذا المقال.
pip install requests pandas matplotlib
# macOS and Linux
export METALAPI_KEY="mk_live_your_key_here"
# Windows PowerShell
$env:METALAPI_KEY = "mk_live_your_key_here"اقرأ المفتاح باستخدام os.environ["METALAPI_KEY"]، الذي يفشل بخطأ واضح حين يكون المتغير غير موجود، بدلًا من لصقه في السكربت. وإذا كنت تفضّل ملف .env، فإن الحزمة python-dotenv تحمّله باستدعاء واحد لـ load_dotenv(). وأضف .env إلى .gitignore حتى لا يصل المفتاح أبدًا إلى مستودعك.
كيف تحصل على سعر الذهب في Python
كل نقطة نهاية في MetalAPI هي طلب GET تحت https://metalapi.com/api/v1. ويمكن وضع المفتاح في معامل الاستعلام api_key، أو في ترويسة Authorization: Bearer، أو في ترويسة X-API-Key. استخدم ترويسة: فعناوين URL الكاملة ينتهي بها المطاف في السجلات، وبعض استثناءات requests تطبع عنوان URL مع سلسلة الاستعلام. تُرجع نقطة النهاية /latest الأسعار الحالية، ويحدد base العملة، ويقصر currencies الاستجابة على الرموز التي تحتاجها:
import os
import requests
resp = requests.get(
"https://metalapi.com/api/v1/latest",
params={"base": "USD", "currencies": "XAU"},
headers={"X-API-Key": os.environ["METALAPI_KEY"]},
timeout=10,
)
data = resp.json()
if not data["success"]:
raise SystemExit(f"MetalAPI error {data['error']['code']}: {data['error']['info']}")
price = data["rates"][data["base"] + "XAU"]
print(f"Gold: {price:,.2f} {data['base']} per troy ounce")تبدو الاستجابة على النحو التالي. والأرقام هي القيم النموذجية المستخدمة في توثيق MetalAPI، وليست أسعارًا لحظية:
{
"success": true,
"timestamp": 1792834892,
"date": "2026-10-24",
"base": "USD",
"rates": { "XAU": 0.0002501319, "USDXAU": 3997.89 },
"rate_limit": { "limit": 100, "remaining": 99, "used": 1, "plan": "Free", "...": "..." }
}بهذه القيم يطبع السكربت Gold: 3,997.89 USD per troy ounce. وrates["USDXAU"] هو سعر أونصة تروي واحدة من الذهب بعملة الأساس. أما rates["XAU"] فهو مقلوبه، أي عدد أونصات الذهب لكل دولار، وقراءته عن طريق الخطأ هي أكثر الأخطاء شيوعًا في شيفرة أسعار الذهب. وبناء المفتاح من data["base"] يُبقي السكربت صحيحًا حين تنتقل إلى base=EUR (المفتاح EURXAU) أو base=INR. أضف unit=gram أو unit=kilogram للحصول على الأسعار لكل غرام أو كيلوغرام. وtimestamp هو وقت بيانات السعر بثواني Unix، لا وقت طلبك.
معالجة أخطاء واجهة API لأسعار الذهب في Python
يُرجع الاستدعاء الفاشل حالة خطأ HTTP وجسم JSON يتضمن success: false وكائن error يحتوي على code رقمي وtype ورسالة info. ابنِ منطقك على الرمز، لا على نص الرسالة أبدًا. ولا تستحق إعادة المحاولة سوى رموز قليلة:
104(too_many_requests، HTTP 429): الحد لكل دقيقة. انتظر عدد الثواني المحدد في ترويسةRetry-After.500و503وأخطاء الشبكة: أعد المحاولة مع تراجع أسّي.- كل ما عدا ذلك، مثل
101أو102(مفتاح مفقود أو غير صالح)، و105(استُنفدت الحصة الشهرية)، و106(نقطة النهاية غير مشمولة في خطتك)، وأخطاء التحقق2xx: أصلح الطلب أو المفتاح أو الخطة بدلًا من ذلك.
تغلّف هذه الوحدة الصغيرة ذلك المنطق. وهي تعيد استخدام requests.Session واحدة، فتبقى الاتصالات مفتوحة بين الاستدعاءات، وتطلق استثناءً يمكنك فحصه:
import os
import time
import requests
API_URL = "https://metalapi.com/api/v1"
session = requests.Session()
session.headers["X-API-Key"] = os.environ["METALAPI_KEY"]
class MetalAPIError(Exception):
def __init__(self, code, type_, info, retry_after=None):
super().__init__(f"MetalAPI {code} ({type_}): {info}")
self.code = code
self.type = type_
self.retry_after = retry_after
def _get(path, params):
r = session.get(f"{API_URL}/{path}", params=params, timeout=10)
try:
data = r.json()
except ValueError: # not JSON, e.g. an HTML error page from a proxy
raise MetalAPIError(r.status_code, "http_error", r.reason) from None
if not data.get("success"):
err = data["error"]
retry_after = r.headers.get("Retry-After")
raise MetalAPIError(err["code"], err["type"], err["info"], int(retry_after) if retry_after else None)
return data
def call(path, params=None, retries=3):
"""GET an endpoint. Retries rate limits, server errors and network failures only."""
for attempt in range(retries + 1):
try:
return _get(path, params or {})
except (MetalAPIError, requests.RequestException) as exc:
code = getattr(exc, "code", None) # None for network errors
if attempt == retries or (code is not None and code != 104 and code < 500):
raise
time.sleep(getattr(exc, "retry_after", None) or 2**attempt)ينتظر التراجع 1 ثم 2 ثم 4 ثوانٍ قبل الاستسلام. ولا تكلّف إعادة المحاولة شيئًا هنا، لأن الطلبات المرفوضة بالخطأ 104 أو بخطأ في الخادم لا تُحتسب. أما أخطاء التحقق فتُحتسب، وهذا سبب إضافي لعدم إعادة محاولتها في حلقة أبدًا. ويسرد دليل معالجة الأخطاء كل رمز مع حالة HTTP الخاصة به.
تخزين أسعار الذهب مؤقتًا لتوفير الطلبات
تحدّث كل خطة الأسعار على فترات ثابتة: مرة يوميًا في Free، وكل 30 دقيقة في Lite، وكل 10 دقائق في Standard، وكل 60 ثانية في Advanced وPro. وتقع التحديثات على مضاعفات تلك الفترة محسوبة من 00:00 UTC، لذا فإن الطلب مجددًا قبل التحديث التالي يُرجع الأرقام نفسها ويستهلك الحصة دون فائدة. تحتفظ ذاكرة التخزين المؤقت هذه بكل استجابة حتى التحديث التالي، وتقدّم آخر أسعار صالحة إذا تعذّر الوصول إلى الواجهة:
import math
import time
import requests
from metalapi import MetalAPIError, call
# Seconds. Free: 86_400, Lite: 3_600 (hourly fits its 1,000 requests), Standard: 600
UPDATE_INTERVAL = 600
_cache = {} # (base, symbols, unit) -> (expires_at, data)
def latest(base="USD", symbols="XAU,XAG", unit="troy_oz"):
key = (base, symbols, unit)
now = time.time()
hit = _cache.get(key)
if hit and now < hit[0]:
return hit[1]
try:
data = call("latest", {"base": base, "currencies": symbols, "unit": unit})
except (MetalAPIError, requests.RequestException):
if not hit:
raise
_cache[key] = (now + 60, hit[1]) # serve the last good prices, try again in a minute
return hit[1]
next_update = (math.floor(now / UPDATE_INTERVAL) + 1) * UPDATE_INTERVAL
_cache[key] = (next_update + 5, data) # expire a few seconds after the next update
return dataيُرجع latest("EUR", "XAU", "gram")["rates"]["EURXAU"] سعر الغرام باليورو. اطلب كل الرموز التي تحتاجها في استدعاء واحد: يكلّف XAU,XAG,XPT,XPD طلبًا واحدًا، لا أربعة. وتذكّر أن ذاكرة التخزين المؤقت هذه تقيم في الذاكرة. فالسكربت الذي يشغّله cron يبدأ بذاكرة فارغة في كل مرة، لذا يكون الجدول الزمني هناك هو ذاكرة التخزين المؤقت: شغّله مرة واحدة على الأكثر في كل فترة تحديث. ولمراقبة حصتك، استدعِ /usage، التي لا تُحتسب منها أبدًا.
تحميل سلسلة زمنية لأسعار الذهب في pandas
للبيانات التاريخية، تُرجع نقطة النهاية /timeframe (واسمها البديل /timeseries) أسعار الإغلاق اليومية بين start_date وend_date، مفهرسة بالتاريخ. يغطي الطلب الواحد حتى 30 يومًا في خطة Free و365 يومًا في الخطط المدفوعة، ويفشل النطاق الأطول بالخطأ 207. تقسّم هذه الدالة أي نطاق إلى أجزاء مسموح بها وتُرجع Series في pandas:
from datetime import date, timedelta
import pandas as pd
from metalapi import call
def gold_history(start, end, base="USD", max_days=30): # max_days=365 on paid plans
rates = {}
chunk_start = start
while chunk_start <= end:
chunk_end = min(chunk_start + timedelta(days=max_days - 1), end)
data = call("timeframe", {
"start_date": chunk_start.isoformat(),
"end_date": chunk_end.isoformat(),
"currencies": "XAU",
"base": base,
})
rates.update(data["rates"])
chunk_start = chunk_end + timedelta(days=1)
df = pd.DataFrame.from_dict(rates, orient="index")
df.index = pd.to_datetime(df.index)
return df[f"{base}XAU"].sort_index().rename(f"gold_{base.lower()}")
gold = gold_history(date(2026, 1, 1), date(2026, 10, 23))
gold.to_csv("gold_usd.csv")
print(f"{len(gold)} trading days, high {gold.max():,.2f}, low {gold.min():,.2f}")في خطة Free، تستغرق هذه الأشهر العشرة 10 طلبات. وتحتوي الـ Series على صف واحد لكل يوم تداول: فعطلات نهاية الأسبوع وعطلات السوق مفقودة لا معبّأة، لذا توقّع صفوفًا أقل من أيام التقويم. وإذا احتجت إلى قيمة لكل يوم، فإن gold.asfreq("D").ffill() يمدّ سعر إغلاق الجمعة على عطلة نهاية الأسبوع. ومن هنا تنطبق أدوات pandas المعتادة، مثل gold.pct_change() للعوائد اليومية أو gold.rolling(20).mean() للمتوسط المتحرك.
أسعار الإغلاق السابقة لا تتغير أبدًا، لذا فإن ملف CSV هو ذاكرة التخزين المؤقت للبيانات التاريخية: حمّله في المرة التالية بدلًا من طلب النطاق نفسه مجددًا. ولملء قاعدة بيانات بالسجل الكامل منذ عام 2000، راجع أسعار الذهب والفضة التاريخية عبر API.
رسم سعر الذهب باستخدام matplotlib
يقرأ الرسم البياني ملف CSV، لذا لا تكلّف إعادة رسمه أي طلبات. ويرسم سعر الإغلاق اليومي مع متوسط متحرك لـ 20 يومًا:
import matplotlib.pyplot as plt
import pandas as pd
gold = pd.read_csv("gold_usd.csv", index_col=0, parse_dates=True)["gold_usd"]
sma20 = gold.rolling(20).mean()
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(gold.index, gold, linewidth=1, label="Gold, USD per troy oz")
ax.plot(sma20.index, sma20, linewidth=2, label="20-day moving average")
ax.set_title(f"Gold price, {gold.index[0]:%d %b %Y} to {gold.index[-1]:%d %b %Y}")
ax.yaxis.set_major_formatter("{x:,.0f}")
ax.grid(alpha=0.3)
ax.legend()
fig.autofmt_xdate()
fig.tight_layout()
fig.savefig("gold.png", dpi=150)لأن الفهرس يحتوي على تواريخ حقيقية، يوزّع matplotlib النقاط بحسب الزمن، ويبدأ المتوسط المتحرك في يوم التداول العشرين. ولرسم الذهب بعملة أخرى، استدعِ gold_history مع base="EUR": تحافظ مفاتيح الأسعار على النمط نفسه، فتعمل الدالة دون تغيير.
الخطوات التالية
لديك الآن ما تحتاجه معظم عمليات التكامل في Python: طلب مُصادَق، ومعالجة أخطاء مع إعادة محاولة معقولة، وذاكرة تخزين مؤقت تتبع فترة التحديث في خطتك، وبيانات تاريخية في pandas. ابنِ التكامل واختبره على خطة Free. وحين تحتاج إلى أسعار أحدث، اختر خطة بحسب فترة التحديث وحجم الطلبات: فالمهمة التي تعمل كل 10 دقائق تستهلك نحو 4,500 طلب شهريًا، وهذا يقع ضمن حد Standard البالغ 10,000. قارن الخطط في صفحة الأسعار.
إذا كانت الأسعار ستُعرض على موقع ويب، فإن مقال واجهة API لأسعار الذهب في JavaScript يوضّح كيفية تقديمها من مسار Node.js دون كشف مفتاحك.
الأسئلة الشائعة
هل توجد واجهة API مجانية لأسعار الذهب يمكن استخدامها في Python؟
نعم. تتضمن خطة Free في MetalAPI مئة طلب شهريًا مع تحديث يومي للأسعار ولا تتطلب بطاقة ائتمان، وكل مقتطف في هذا المقال يعمل عليها. وأحدث أسعارها هي أسعار إغلاق اليوم السابق، ويغطي طلب /timeframe الواحد حتى 30 يومًا. ويشرح مقال واجهة API مجانية لأسعار الذهب الخطة المجانية بمزيد من التفصيل.
لماذا سعر XAU رقم صغير إلى هذا الحد؟
rates["XAU"] هو السعر المقلوب: عدد أونصات التروي من الذهب لكل وحدة واحدة من عملة الأساس. أما السعر فموجود تحت المفتاح ذي البادئة: rates["USDXAU"] لعملة الأساس USD أو rates["EURXAU"] لعملة الأساس EUR. ومع القيم النموذجية، يعطي 1 ÷ 0.0002501319 القيمة 3997.89 مجددًا.
كيف أحصل على سعر غرام الذهب في Python؟
أضف unit=gram إلى الطلب، مثلًا call("latest", {"base": "EUR", "currencies": "XAU", "unit": "gram"}). عندها يكون rates["EURXAU"] هو السعر باليورو لكل غرام، وتتضمن الاستجابة "unit": "gram". وتجنّب unit=ounce: فهي أونصة الأفوارديبوا، لا أونصة التروي التي يُسعَّر بها الذهب.
إلى أي مدى يمتد السجل التاريخي لأسعار الذهب؟
تبدأ أسعار الذهب اليومية في عام 2000، ويمكن لكل خطة الاستعلام عن السجل الكامل. ولا تختلف الخطط إلا في عدد الأيام التي يغطيها طلب /timeframe الواحد: 30 في Free و365 في الخطط المدفوعة. ومع max_days=365، يستغرق كل شيء من عام 2000 حتى اليوم 27 طلبًا.
كم مرة ينبغي لسكربت Python أن يستدعي الواجهة؟
ليس أكثر من وتيرة تحديث الأسعار في خطتك: مرة يوميًا في Free، بعد 00:00 UTC؛ وكل ساعة في Lite، لأن الاستدعاء كل 30 دقيقة يستهلك نحو 1,490 طلبًا شهريًا مقابل حصة قدرها 1,000؛ وكل 10 دقائق في Standard. وتوقّع ثبات الأسعار في عطلة نهاية الأسبوع، حين لا يُتداول الذهب الفوري من الجمعة 17:00 حتى الأحد 17:00 بتوقيت نيويورك.
جرّبه مع بياناتك الخاصة
أنشئ حسابًا مجانيًا وأرسل طلبك الأول في أقل من دقيقتين. 100 طلب شهريًا، دون بطاقة ائتمان.
تابع القراءة
معنى XAU/USD: ماذا تعني الرموز XAU وXAG وXPT وXPD
شرح معنى XAU/USD: لماذا يملك الذهب رمز عملة في ISO 4217، وماذا تعني XAG وXPT وXPD، وكيف تقرأ اتجاه التسعير في الأسعار وفي بيانات واجهة API.
واجهة API مجانية لأسعار الذهب: ماذا تقدّم 100 طلب شهريًا
شرح صريح لواجهة API مجانية لأسعار الذهب: ما الذي تتضمنه خطة Free في MetalAPI، وكيف تكفي 100 طلب شهريًا بالتخزين المؤقت والتجميع، ومتى تنتقل إلى خطة مدفوعة.
واجهة API لأسعار الذهب في JavaScript: Node.js وTypeScript وNext.js
سعر الذهب في JavaScript عبر واجهة API لأسعار الذهب: fetch مع async/await، وأنواع TypeScript، ومسار Express أو Next.js مخزَّن مؤقتًا، واستخدام آمن لمفتاح API.