قارن الأسواق جنبًا إلى جنب
متوسط
أنشئ جدول مقارنة بين مدن الخليج ومصر واحفظه بصيغة CSV.
الإعداد
يستخدم هذا الدرس الأداة المساعدة jokerprice.mjs من درس البدء السريع.
// jokerprice.mjs: a small client for the JokerPrice API (Node 18+).
const BASE = "https://jokerprice.com";
const KEY = process.env.JOKERPRICE_API_KEY;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
export async function api(path, { method = "GET", query = {}, body } = {}) {
const url = new URL(BASE + path);
for (const [name, value] of Object.entries(query)) url.searchParams.set(name, String(value));
for (;;) {
const res = await fetch(url, {
method,
headers: { Authorization: "Bearer " + KEY, ...(body ? { "Content-Type": "application/json" } : {}) },
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (res.status === 429 && data.error.code === "RATE_LIMIT") { await sleep(Number(res.headers.get("Retry-After") || 60) * 1000); continue; }
if (!res.ok) throw new Error(res.status + " " + data.error.code + ": " + data.error.message);
return data;
}
}اختر الأسواق
يغطي طلب واحد إلى /api/v1/markets العرض والأسعار وملخص التتبّع لآخر 90 ليلة لكل وجهة. اجلب التقرير الكامل فقط للمدن التي تريد تفاصيل غرف النوم فيها.
import { api } from "./jokerprice.mjs";
const SLUGS = ["riyadh", "jeddah", "dubai", "doha", "cairo"];
const { markets } = await api("/api/v1/markets");
const known = new Set(markets.map(m => m.slug));
const chosen = SLUGS.filter(slug => known.has(slug));
console.log("Comparing", chosen.join(", "));اجلب التقارير
const reports = [];
for (const slug of chosen) reports.push((await api("/api/v1/markets/" + slug)).market);
const twoBed = m => m.bedrooms.find(b => b.bedrooms === 2)?.median ?? null;
const live = m => m.tracker && m.tracker.maturity === "estimating" ? m.tracker : null;
const rows = reports.map(m => ({
market: m.name,
listings: m.listings,
median: Math.round(m.nightly.median ?? 0),
spread: m.nightly.p75 && m.nightly.p25 ? +(m.nightly.p75 / m.nightly.p25).toFixed(2) : null,
twoBedMedian: twoBed(m),
topType: m.types[0]?.type ?? null,
occupancy: live(m) ? +(live(m).occupancy * 100).toFixed(1) : null,
adr: live(m) ? Math.round(live(m).adr) : null,
revenuePerListing: live(m) ? Math.round(live(m).monthlyRevenuePerListing) : null,
}));
console.table(rows);يمثل spread سعر المئين الخامس والسبعين مقسومًا على سعر المئين الخامس والعشرين، والقيمة المرتفعة تعني سوقًا بفئات أسعار متباينة جدًا.
احفظها بصيغة CSV
import { writeFileSync } from "node:fs";
const cols = Object.keys(rows[0]);
const cell = v => (v === null || v === undefined ? "" : '"' + String(v).replace(/"/g, '""') + '"');
writeFileSync("markets.csv", [cols.join(","), ...rows.map(r => cols.map(c => cell(r[c])).join(","))].join("\n"));قارن بإنصاف
- لا تقارن الإشغال إلا بين الأسواق التي تكون قيمة
trackerفيهاestimating. - تُطلب جميع الأسعار من المنصات بالريال السعودي، لذا تُقارن المدن في الدول المختلفة مباشرة؛ لكنها تبقى أسعارًا معروضة لا أسعارًا محققة.
- توزيع أنواع العقارات مهم: فالمدينة التي تغلب عليها الشاليهات سيختلف وسيطها عن مدينة تغلب عليها الاستوديوهات. استخدم الوسيط حسب غرف النوم أو
typesلمقارنة المتشابه بالمتشابه.