source.unsplash.com از بین رفته: تحلیل نهایی و همه راه‌های جایگزینی آن

این سرویس در سال ۲۰۲۱ با وعده «کاربردهای موجود همچنان کار خواهند کرد» منسوخ اعلام شد، در ژوئن ۲۰۲۴ کاملاً خاموش شد، و هنوز هم امروز در کدهای جدید نوشته می‌شود. تحلیل دقیق ماجرا — و سه جایگزین، همراه با پروکسی‌ای که تصادفی‌بودن بدون کلید را بازمی‌گرداند.

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

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

پاسخ‌های HTTP، دو ورودی changelog که کلمه به کلمه نقل شده‌اند، محدودیت‌های API، ردیاب‌های مسائل پروژه‌هایی که شکستند و شمارش‌های GitHub، npm و Stack Overflow همگی از منابع اصلی آمده‌اند، به‌همراه روش استخراج هرکدام در پانویس‌ها. اگر فقط راه‌حل را می‌خواهید، مستقیم بروید سراغ جدول مهاجرت.

امروز، اگر هنوز آن را درخواست کنید چه می‌گیرید

یک دستور، بدون کلید، از هر ماشینی قابل بازتولید:

ترمینال
curl -I https://source.unsplash.com/random

HTTP/2 503
cache-control: no-cache, no-store
content-type: text/html; charset=utf-8
server: Heroku
via: 2.0 heroku-router
# بدنه: یک iframe اشاره‌کننده به herokucdn.com/error-pages/application-error.html

هیچ‌کدام از این‌ها یک خرابی DNS نیست. source.unsplash.com هنوز resolve می‌شود — این یک CNAME به یک هاست herokudns.com است — بنابراین درخواست پاسخ داده می‌شود، فقط نه توسط یک اپلیکیشن. این جزئیات مهم‌تر از آن است که به نظر می‌رسد: مرورگری که یک 503 سریع با یک بدنه HTML دریافت می‌کند، جای‌نگهدارِ تصویر خراب رسم می‌کند، و هر کدی که response.ok یا یک هندلر onerror را می‌خواند که هرگز ننوشته‌اید، مسیر شکستی را طی می‌کند که هرگز تست‌اش نکرده‌اید.

الگوی آدرس قبلاً چه چیزی برمی‌گرداند امروز
source.unsplash.com/randomیک عکس تصادفی، در هر اندازه503
source.unsplash.com/random/1600x900یک عکس تصادفی، برش‌خورده به اندازه دلخواه503
source.unsplash.com/1600x900/?apple,deskیک عکس تصادفی که با کلمات جست‌وجو منطبق است503
source.unsplash.com/featured/1600x900?natureیک عکس تصادفیِ برجسته503
source.unsplash.com/collection/190727/800x600یک عکس تصادفی از یک مجموعه503
source.unsplash.com/user/scottwebb/1600x900یک عکس تصادفی از یک عکاس مشخص503
source.unsplash.com/dailyعکس روز503

هرکدام جداگانه با curl -o /dev/null -w "%{http_code}" بررسی شد. ویژگی جست‌وجو همان‌طور که اعلام شده بود ابتدا خاموش شد؛ امروز کل اپلیکیشن خاموش است، پس این تفاوت دیگر وجود ندارد.

سه سال بین «منسوخ‌شده» و «خاموش»

هر دو اعلامیه هنوز در یک مکان قابل خواندن هستند، در unsplash.com/documentation/changelog. به‌طور کامل نقل شده، چون خودِ عبارت‌بندی کل داستان است:

25 نوامبر 2021 — «منسوخ‌شدن Unsplash Source»
«Unsplash Source در حال منسوخ شدن است. استفاده‌های موجود به کار خود ادامه خواهند داد، اما برای پروژه‌های جدید از API کامل Unsplash استفاده کنید.»

11 ژوئن 2024 — «خاموشی Unsplash Source»
«Unsplash Source از زمان منسوخ‌شدنش در سال 2021 به‌طور رسمی پشتیبانی نشده است. به‌عنوان بخشی از خاموشی نهایی، ابتدا با غیرفعال کردن ویژگی جست‌وجو شروع می‌کنیم، و در هفته‌های آینده کل اپلیکیشن را خاموش می‌کنیم. استفاده‌های موجود از Source — به‌خصوص در سطح تولید — باید هرچه سریع‌تر به API کامل Unsplash مهاجرت کنند.»

آن‌ها را به ترتیب بخوانید و حالت شکست واضح می‌شود. اطلاعیه 2021 حاوی یک وعده بود (استفاده‌های موجود به کار خود ادامه خواهند داد) و بدون تاریخ. توسعه‌دهنده‌ای که آن را در سال 2021 خوانده بود هر دلیلی داشت که کد کارکردنش را دست‌نخورده رها کند؛ توسعه‌دهنده‌ای که در سال 2022 پیوسته بود اصلاً آن را نخوانده بود. اطلاعیه 2024 «هفته‌های آینده» را اعلام کرد، سه سال بعد، در صفحه‌ای که کسی آن را بوکمارک نکرده بود.

Unsplash واقعاً یک سیاست منسوخ‌سازی منتشر می‌کند، و سیاست معقولی هم هست — مستندات می‌گوید برای فیلدها و اندپوینت‌های مستندشده عمومی، تغییرات در changelog با حداقل 3 هفته اطلاع‌رسانی قبلی اعلام می‌شوند، و اندپوینت‌ها در دوره منسوخ‌سازی یک هدر Warning برمی‌گردانند. همان پاراگراف جمله‌ای دارد که توضیح می‌دهد چرا هیچ‌کدام از این‌ها از Source محافظت نکردند: «برای هر فیلد یا اندپوینت مستندشده غیرعمومی، ممکن است بدون هیچ هشداری تغییرات را اعمال کنیم.» Source هرگز یک اندپوینت از API مستندشده نبود. خارج از سیاستی نشسته بود که می‌توانست پوشش‌اش دهد.

  • درس عملی این نیست که «Unsplash بی‌دقت بود». این است که آدرسی که می‌توانید بدون خواندن هیچ مستنداتی استفاده کنید، آدرسی است که سیاست منسوخ‌سازی‌اش را هم نخوانده‌اید.
  • شکست قبل از اعلامیه شروع شد. یک issue در Drupal که در 28 نوامبر 2022 ثبت شده بود از قبل گزارش داده «همیشه خطای اپلیکیشن Heroku می‌گیرم»، هجده ماه قبل از ورودی خاموشی. این‌طور است که این سرویس‌ها می‌میرند: کند، بعد در اطلاعیه‌ای که هرگز نمی‌بینید.

اعلامیه سخت‌تر از خودِ قطعی سرویس پیدا می‌شود

منسوخ‌سازی 2021 در changelog.unsplash.com منتشر شده بود، و این آدرسی است که هر گزارش باگ هم‌زمانی به آن لینک می‌دهد — از جمله همان مورد Drupal که در بالا آمد. سه اندازه‌گیری:

  1. اندپوینت HTTPS خراب است. openssl s_client -connect changelog.unsplash.com:443 پیام tlsv1 alert internal error برمی‌گرداند — handshake پیش از ارائه هر گواهی‌ای شکست می‌خورد. بنابراین هر لینک از عصر 2021، که https:// بود، در مرورگر مرده است.
  2. روی HTTP ساده ریدایرکت می‌کند، اما نه به شکل مفید. دنبال‌کردن http://changelog.unsplash.com/deprecations/2021/11/25/source-deprecation.html دو مرحله بعد به یک 400 در unsplash.com/@documentation/changelog/deprecations/2021/11/25/source-deprecation/html ختم می‌شود — مسیر توسط روت نام‌کاربری سایت بلعیده شده است.
  3. آرشیو دقیقاً همان‌جایی که خاموشی رخ داده سوراخ دارد. آخرین اسنپ‌شات موفق Wayback Machine از changelog قدیمی 24 مارس 2024 است؛ اولین اسنپ‌شات آن از نسخه جدید 23 اوت 2024 است. خاموشی در 11 ژوئن 2024 اعلام شد — داخل همان شکاف پنج‌ماهه.

هیچ‌کدام از این‌ها توطئه نیست؛ یک مهاجرت معمولی CMS است. اما پیامدش واقعی است، و دلیل آن است که این مقاله هر دو ورودی را به‌طور کامل نقل می‌کند: سند اصلیِ یک منسوخ‌سازی باید بیشتر از خودِ چیزی که منسوخ کرده دوام بیاورد، و اینجا تقریباً چنین نبود.

آنچه واقعاً شکست

نه پروژه‌های جانبی. شکست‌های زیر ورودی‌های عمومی ردیاب issue هستند؛ عنوان‌ها، تاریخ‌ها و وضعیت‌ها از API‌های GitHub و drupal.org آمده‌اند.

پروژه Issue باز شده در چه می‌گوید
MUI (Material UI) #42736 24 ژوئن 2024 «[docs] آدرس تصادفی عکس Unsplash دیگر کار نمی‌کند» — قالب رسمی Sign-in side یک تصویر مرده ارسال کرده بود. سه روز بعد بسته شد.
Nextcloud #115 17 ژانویه 2023 «مهاجرت به API مربوط به Unsplash» — اپ پس‌زمینه روی URIهای Source ساخته شده بود. هجده ماه باز بود، در 16 ژوئیه 2024 بسته شد.
sindresorhus/Actions #248 28 می 2024 «دریافت عکس Unsplash: خطای 503» — یک اکشن Shortcuts در iOS/macOS، دو هفته قبل از اعلام خاموشی خراب شده بود.
Drupal — Gin Login #3324054 28 نوامبر 2022 «Unsplash سرویس source.unsplash.com را منسوخ کرده است — این باعث تأخیر در بارگذاری reCAPTCHA می‌شود و کاربران را از ورود بازمی‌دارد.»

آن ردیف آخر را دوباره بخوانید، چون همان است که ارزش به‌خاطرسپاری دارد. یک تصویر تزئینی کنار فرم ورود — بی‌ربط‌ترین دارایی روی صفحه — تبدیل به یک قطعی احراز هویت شد، چون یک درخواست کند شخص ثالث جلوی CAPTCHAیی نشسته بود که فرم ورود به آن نیاز داشت. هیچ‌کس آن را این‌طور ننوشته بود. این حالت از ترتیبی که مرورگر چیزها را بارگذاری می‌کند بیرون آمد.

دستیار کدنویسی شما این پیام را دریافت نکرد

اینجا بخشی است که یک قطعی سال 2024 را به یک مشکل سال 2026 تبدیل می‌کند. source.unsplash.com برای حدود هشت سال مستند، بلاگ، آموزش و کپی شده بود. تمام آن متن در داده‌های آموزشی مدل‌هایی است که اکنون کدهای استارتر ما را می‌نویسند — و متن منقضی نمی‌شود. سه شمارش:

اندازه‌گیریمقدار در 30 اوت 2026روش استخراج
فایل‌های حاوی source.unsplash.com 3,344 API جست‌وجوی کد GitHub، q=source.unsplash.com (فقط کدهای عمومی ایندکس‌شده — یک کف، نه یک مجموع کامل)
ریپازیتوری‌های ساخته‌شده بعد از خاموشی، در یک نمونه 100‌فایلی 12 از 77 همان کوئری، 100 نتیجه، تکینه‌سازی به 77 ریپازیتوری، created_at مقایسه‌شده با 11 ژوئن 2024
…و ریپازیتوری‌های همان نمونه که در 12 ماه گذشته پوش شده‌اند 20 از 77 ریپازیتوری‌های زنده، نه آرشیو — از جمله elastic/kibana که فایل دموی آن همچنان imageUrl: 'https://source.unsplash.com/64x64/?dingo' دارد
دانلود ماهانه unsplash-source-es6 23 API رجیستری npm — یک wrapper برای یک سرویس مرده، آخرین بار در 2022 منتشر شده، هنوز نصب می‌شود
پست‌های Stack Overflow که به آن اشاره کرده‌اند 1,459 API مربوط به Stack Exchange، مجموع /search/excerpts

مستقیم‌ترین شاهد اصلاً در کد اپلیکیشن نیست — در پرامپت‌هاست. نتیجه اول برای آن جست‌وجو یک کتابخانه از پرامپت‌های سیستمی GPT است که حاوی این خط است: «لطفاً از unsplash API استفاده کن( https://source.unsplash.com/1280x720/?<PUT YOUR QUERY HERE>». این دستورالعمل هنوز امروز هم در دستیارهای جدید کپی می‌شود. مدل آدرس را تأیید نمی‌کند؛ به آن گفته شده که از آن استفاده کند، و هر نمونه‌ای که تا به حال دیده هم همین را تأیید کرده.

پس شکست کدِ تولیدشده دو علت مستقل دارد، و رفع یکی دیگری را رفع نمی‌کند: داده‌های آموزشی قدیمی، و دستورالعمل‌های قدیمی که انسان‌ها روی آن‌ها نوشته‌اند. در هر صورت، نشانه یکسان است: خانواده‌ای از تصاویری که هرگز بارگذاری نمی‌شوند:

الگوهای تصویر مرده که ارزش grep زدن دارند
source.unsplash.com/random/1200x800   # 503 از اواسط 2024 — دیگر برنمی‌گردد
images.unsplash.com/photo-…           # CDN واقعی، اما شناسه‌های حفظ‌شده ممکن است وجود نداشته باشند
via.placeholder.com/400               # مستطیل خاکستری، به تولید ارسال شده
placehold.co/800x600                  # مستطیل خاکستری، عمدی
picsum.photos/800/600                 # یک عکس واقعی، بی‌ربط به صفحه شما
/placeholder.png                      # فایلی که هرگز به ریپازیتوری اضافه نشد

یک پانویس روی خط دوم آن فهرست: در زمان نوشتن این مقاله، via.placeholder.com هم از شبکه تست ما handshake مربوط به TLS را کامل نکرد، و روی HTTP ساده با 403 پاسخ داد. آن را از شبکه خودتان بررسی کنید پیش از اینکه به آن اعتماد کنید — فال‌بکی که این ابزارها به سراغش می‌روند ممکن است داستان قطعی خودش را داشته باشد.

فقط مورد اول واقعاً خراب است. بقیه به شکلی ظریف‌تر بدترند: بارگذاری می‌شوند، چیدمان کامل به نظر می‌رسد، و کسی متوجه نمی‌شود که صفحه با هیچ‌چیز خاصی تصویرسازی نشده. و هیچ‌کدام از این‌ها مختص تصاویر نیست — این شکل کلی مسئله است. تصویری که مدل از وب دارد یک اسنپ‌شات است، و اندپوینت‌ها، فلگ‌های CLI، نام‌های پکیج و پلن‌های رایگان همچنان بعد از بسته‌شدن شاتر تغییر می‌کنند.

جدول مهاجرت

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

آدرس قدیمی Source A. آدرس ثابت CDNبدون کلید · بدون تصادفی‌بودن B. API مربوط به Unsplashکلید · فراخوانی سمت سرور C. پروکسی خودتانکلید پنهان · تصادفی‌بودن برمی‌گردد
/random images.unsplash.com/photo-… — یک عکس که خودتان انتخاب کردید GET /photos/random /?w=1600
/random/1600x900 …?w=1600&h=900&fit=crop /photos/random + پارامترهای Imgix روی آدرس برگشتی /?w=1600&h=900&fit=crop
/1600x900/?apple,desk معادلی وجود ندارد — عکس را دستی انتخاب کنید /photos/random?query=apple,desk /?query=apple,desk&w=1600
/featured/1600x900?nature معادلی وجود ندارد /photos/random?query=nature «featured» جانشینی ندارد /?query=nature&w=1600
/collection/67920491/1600x900 معادلی وجود ندارد /photos/random?collections=67920491 /?collections=67920491&w=1600
/user/scottwebb/1600x900 معادلی وجود ندارد /photos/random?username=scottwebb /?username=scottwebb&w=1600
/daily یک عکس را ثابت کنید، در build خودتان چرخانده شود معادلی وجود ندارد — یک عکس تصادفی را خودتان به‌مدت 24 ساعت کش کنید همین‌طور، با کش داخل پروکسی

گزینه A همان چیزی است که اکثر افراد واقعاً می‌خواهند. اگر تصویر تزئینی بود — یک هیرو، یک پنل کناری صفحه ورود، پس‌زمینه یک کارت — هرگز واقعاً نیازی نداشتید که در هر درخواست عکس متفاوتی داشته باشید. یکی را انتخاب کنید، آدرس CDN را نگه دارید، و صفحه دیگر به چیزی تصادفی وابسته نیست:

یک آدرس ثابت و قابل تغییر اندازه Unsplash — بدون کلید، بدون فراخوانی API
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1600&h=900&fit=crop&auto=format"
     width="1600" height="900" alt="…">
# پارامترهای رسمی پشتیبانی‌شده: w, h, crop, fit, fm, auto=format, q, dpr.
# هر پارامتر ixid که API به شما داده را نگه دارید — همان چیزی است که بازدید را گزارش می‌دهد.

گزینه B مسیر رسمی است، و فراخوانی را به سمت سرور می‌برد، چون یک Client-ID در جاوااسکریپت فرانت‌اند یک اعتبارنامه منتشرشده حساب می‌شود. به دو قاعده‌ای که مردم را به دام می‌اندازد توجه کنید: collections/topics را نمی‌توان در یک درخواست با query ترکیب کرد، و count (حداکثر 30) شکل پاسخ را به یک آرایه تغییر می‌دهد حتی وقتی مقدارش 1 باشد.

جایگزین رسمی برای /random
curl "https://api.unsplash.com/photos/random?query=nature&orientation=landscape" \
  -H "Authorization: Client-ID YOUR_ACCESS_KEY" \
  -H "Accept-Version: v1"

# → JSON. عکس در .urls.regular / .urls.raw قرار دارد (w/h/fit را خودتان اضافه کنید).
# → X-Ratelimit-Limit: 1000   X-Ratelimit-Remaining: 999

گزینه C: بازسازی Source، در حدود چهل خط

اگر آنچه از دست دادید واقعاً همان رفتار بود — یک آدرس بدون کلید که هر بار عکسی متفاوت برمی‌گرداند، مستقیماً از یک تگ <img> قابل استفاده، در یک فیلد CMS، یا در یک سایت استاتیک که سروری وجود ندارد — پس باید خودتان آن اندپوینت را اجرا کنید. این یک worker کوچک در جلوی API است، و سه چیزی که آن را در برابر برخورد با production زنده نگه می‌دارد کش، بررسی referrer، و عبور دادن بقیه رشته کوئری به CDN است.

worker.js — یک اندپوینت شبیه Source و بدون کلید روی /photos/random
// Cloudflare Workers. جای دیگر (Deno Deploy، Val Town…) شکل یکسان است،
// اما یک کش نام‌دار را با caches.open() به‌جای caches.default باز کنید.
// UNSPLASH_KEY سمت سرور می‌ماند. فراخوانی‌کنندگان هرگز آن را نمی‌بینند.
const ALLOWED = ["example.com", "www.example.com"];   // فقط دامنه‌های شما
const API_PARAMS = ["query", "collections", "topics", "username", "orientation"];
const TTL = 60;                                       // ثانیه — سهمیه ساعتی را محافظت می‌کند

const host = (value) => { try { return new URL(value).hostname; } catch { return null; } };

export default {
  async fetch(req, env, ctx) {
    // 0. فقط GET: Cache API از ذخیره هرچیز دیگری امتناع می‌کند، و یک اندپوینت
    //    تصویر فعل دیگری برای پاسخ ندارد.
    if (req.method !== "GET")
      return new Response("Method not allowed", { status: 405 });
    const url = new URL(req.url);

    // 1. فقط صفحات خودتان مجازند آن را جای‌گذاری کنند — یک اندپوینت عکس تصادفی
    //    عمومی روی اینترنت باز، سهمیه rate limit شخص دیگری را می‌سوزاند.
    const ref = req.headers.get("referer");       // در بسیاری از کلاینت‌های قانونی وجود ندارد
    if (ref && !ALLOWED.includes(host(ref)))
      return new Response("Forbidden", { status: 403 });

    // 2. کش کردن براساس ترکیب پارامترها، تا صفحه‌ای با 12 تصویر
    //    هزینه یک فراخوانی API در دقیقه داشته باشد، نه دوازده در هر رندر.
    const cache = caches.default;
    const hit = await cache.match(req);
    if (hit) return hit;

    // 3. از API رسمی یک عکس تصادفی بخواهید.
    const api = new URL("https://api.unsplash.com/photos/random");
    for (const p of API_PARAMS)
      if (url.searchParams.has(p)) api.searchParams.set(p, url.searchParams.get(p));

    const r = await fetch(api, { headers: {
      Authorization: "Client-ID " + env.UNSPLASH_KEY,
      "Accept-Version": "v1",
    }});
    // 403 اینجا معمولاً یعنی سهمیه ساعتی، نه کلید خراب — TTL را بالا ببرید، نگران نشوید.
    if (!r.ok) return new Response("Upstream " + r.status, { status: 502 });
    const photo = await r.json();

    // 4. آدرس تصویر را بازسازی کنید: ixid را نگه دارید، پارامترهای اندازه فراخوان را اضافه کنید.
    const img = new URL(photo.urls.raw);          // .raw از قبل ixid را حمل می‌کند
    for (const [k, v] of url.searchParams)
      if (!API_PARAMS.includes(k)) img.searchParams.set(k, v);  // w, h, fit, q…

    const res = new Response(null, { status: 302, headers: {
      Location: img.toString(),
      "Cache-Control": "public, max-age=" + TTL,
      // اعتبار همراه با ریدایرکت سفر می‌کند؛ مقادیر هدر باید ASCII باشند، پس encode شده‌اند.
      "X-Photo-Credit": encodeURIComponent(photo.user.name + " on Unsplash"),
      "X-Photo-Link": photo.links.html,
    }});
    ctx.waitUntil(cache.put(req, res.clone()));
    return res;
  },
};

مهاجرت یک آدرس بعد از این یک عملیات جست‌وجو-و-جایگزینی است، که دقیقاً همان چیزی است که این گزینه را ارزش بیست دقیقه صرف‌شده می‌کند:

جایگزینی یک‌به‌یک
- https://source.unsplash.com/collection/67920491/1600x900
+ https://img.example.com/?collections=67920491&w=1600&h=900&fit=crop

دو نکته طراحی، که هرکدام باعث شدند کسی یک بعدازظهر بد را قبل از نوشته‌شدن‌شان بگذراند. ریدایرکت (302) به‌جای proxy کردن بایت‌ها شما را از مسئولیت پهنای باند بیرون نگه می‌دارد و باعث می‌شود بازدید در CDN مربوط به Unsplash شمرده شود، که همان چیزی است که راهنماها می‌خواهند. و بررسی Referer عمداً وقتی هدر غایب است سهل‌گیر است — بسیاری از کلاینت‌های قانونی آن را حذف می‌کنند — درحالی‌که همچنان جلوی حالت آشکاری را می‌گیرد که اندپوینت شما تبدیل به یک API تصویر رایگان برای شخص دیگری شود.

قوانینی که همان لحظه استفاده از API به ارث می‌برید

Source هیچ قانونی نداشت چون هیچ اکانتی نداشت. API پنج قانون دارد که نحوه معماری‌تان را تغییر می‌دهد، همه از مستندات فعلی:

  • محدودیت‌های نرخ ساعتی هستند، و در ابتدا کوچک. 50 درخواست در ساعت در حالت دمو؛ 1,000 در ساعت بعد از اینکه اپلیکیشن شما برای تولید تأیید شود. فقط فراخوانی‌های api.unsplash.com شمرده می‌شوند — درخواست‌های تصویر به images.unsplash.com شمرده نمی‌شوند. X-Ratelimit-Remaining را روی هر پاسخ بخوانید.
  • هات‌لینکینگ اجباری است، نه فقط مجاز. Unsplash می‌خواهد که آدرس‌های تصویری که API برمی‌گرداند مستقیماً جای‌گذاری شوند، تا بازدید عکس بتواند به عکاس نسبت داده شود. آینه‌کردن فایل روی CDN خودتان تنها بهینه‌سازی‌ای است که آزاد به انجامش نیستید.
  • پارامتر ixid را نگه دارید. تغییر اندازه و برش آدرس برگشتی انتظار می‌رود؛ حذف پارامتری که اپلیکیشن شما را شناسایی می‌کند خیر.
  • اعتباردهی و ردیابی دانلود بخشی از توافق است — عکاس و Unsplash اعتبار می‌گیرند، و یک «دانلود» از طریق اندپوینت دانلود عکس زمانی گزارش می‌شود که کاربر فایل را بگیرد، که رویدادی است که باید خودتان آن را شلیک کنید.
  • محصولات توزیع‌شده به ثبت داینامیک کلاینت نیاز دارند. اگر یک پلاگین، یک قالب یا یک CMS خودمیزبان ارسال می‌کنید، یک کلید مشترک هم نقض سیاست است هم یک نقطه شکست واحد؛ API دقیقاً برای همین مورد یک فرآیند ثبت دارد.

این لحظه‌ای است که درباره دامنه صادق باشیم: اگر به‌هرحال دارید یک API و یک کلید وصل می‌کنید، انتخاب اینکه کدام API تصویری، ناگهان باز می‌شود، و ارزش دارد پنج دقیقه پیش از نوشتن کلاینت روی آن وقت بگذارید. ما گزینه‌های رایگان را — سهمیه‌ها، قوانین، رفتار جست‌وجو و شکل پاسخ‌ها — در مقایسه APIهای رایگان استوک فوتو مقایسه کرده‌ایم.

اگر فقط یک جای‌نگهدار می‌خواستید، همین را بگویید

سهم بزرگی از استفاده از Source هرگز واقعاً درباره Unsplash نبود. «یک چیز تصویرشکل اینجا بگذار تا چیدمان را بسازم» بود. برای همین منظور، سرویس‌های بدون کلید هنوز وجود دارند و پاسخ درست هستند:

سرویسکلید؟چه چیزی می‌گیریدکجا متوقف می‌شود
Lorem Picsum خیر عکس‌های واقعی: picsum.photos/800/600، یک نسخه ثابت با /id/237/… یا /seed/xxx/…، به‌همراه ?grayscale و ?blur=1..10. اندپوینت /v2/list آن اعتبار صفحه Unsplash و نویسنده هر عکس را ذکر می‌کند. هیچ هدف‌گیری موضوعی وجود ندارد. عکس هیچ ربطی به صفحه شما نخواهد داشت.
placehold.co خیر مستطیل‌های برچسب‌دار در هر اندازه — پرکننده صادقانه برای wireframe. یک جعبه خاکستری است، و مثل همین در یک اسکرین‌شات که با مشتری به اشتراک گذاشته می‌شود دیده می‌شود.
Openverse خیر یک کاتالوگ با مجوز باز و یک API عمومی، اداره‌شده توسط WordPress.org. تطبیق کلیدواژه‌ای، و مجوزها به‌ازای هر آیتم متفاوت هستند — باید آن‌ها را بخوانید.

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

یک کلید به‌جای سه

اینجا بخشی از مهاجرت است که کسی برایش برنامه‌ریزی نمی‌کند: افرادی که Source را ترک می‌کنند به‌ندرت روی یک API فرود می‌آیند. یک صفحه به یک هیرو، دو تصویر بخش و چیزی برای یک grid کارت نیاز دارد، و پاسخ صادقانه معمولاً Unsplash به‌همراه Pexels به‌همراه Pixabay است — سه ثبت‌نام، سه شمای احراز هویت، سه شکل JSON، سه مدل صفحه‌بندی و سه مجموعه قانون اعتباردهی، همه برای پر کردن همان تگ‌های <img>. آن کار یکپارچه‌سازی هزینه واقعی از‌بین‌رفتن یک آدرس بدون کلید است، و هفته‌ها بعد از قطعی که آن را ایجاد کرد فرود می‌آید.

یکپارچه‌کردن این کار در یک ادغام دقیقاً همان چیزی است که Pexafy را برایش ساختیم: یک کلید واحد روی 9 کتابخانه با مجوز رایگان در یک شما، با جست‌وجوی معنایی در سطح جمله — پس یک توصیف کامل مثل «یک صفحه ترک‌خورده گوشی روی یک میز چوبی، از بالا گرفته‌شده» نتایج رتبه‌بندی‌شده برمی‌گرداند نه هیچ‌چیز. دو محدودیت، به‌روشنی بیان‌شده، چون کل این مقاله درباره غافلگیر نشدن برای دومین بار است: به کلید نیاز دارد، پس آنچه Source بود را بازنمی‌گرداند — در همان دسته API رسمی Unsplash در بالا قرار می‌گیرد؛ و عکاسی با مجوز رایگان دارد، نه تصاویر تحریریه یا برند.

بخشی که واقعاً جدید است هدفش همان دستیارهای بالاست: یک سرور MCP در mcp.pexafy.com/mcp یعنی مدلی که در غیر این صورت یک آدرس تصویر را از حافظه می‌خواند، می‌تواند یک کاتالوگ واقعی را جست‌وجو کند و عکسی برگرداند که واقعاً وجود دارد، به‌همراه خط اعتباردهی‌اش. این پاسخ بهتری به آدرس‌های مرده ماشین‌نوشته است تا هر قانون lint، و استدلالش را در زیرساخت جست‌وجوی تصویر برای عامل‌های هوش مصنوعی نوشته‌ایم.

دارایی خود را در ده دقیقه ممیزی کنید

به هرچه که مهاجرت می‌کنید، اول این بخش را انجام دهید — نمی‌توانید آدرس‌هایی را که پیدا نکرده‌اید تعمیر کنید. Source فقط نمونه امروز است؛ همان سه گام برای هر دارایی خارجی که جای‌گذاری می‌کنید صدق می‌کند.

هر ارجاع مرده را پیدا کنید، بعد آن‌ها را بیرون نگه دارید
# 1. هرچیزی در ریپازیتوری، شامل مستندات، تست‌ها، فیکسچرها و READMEها.
grep -rn --binary-files=without-match \
  -e "source.unsplash.com" -e "via.placeholder.com" -e "/placeholder.png" .

# 2. هرچیزی که پایگاه داده نگه می‌دارد — بدنه CMS جایی است که این‌ها طولانی‌ترین مدت پنهان می‌مانند.
psql -c "SELECT id FROM posts WHERE body LIKE '%source.unsplash.com%'"

# 3. هرچیزی که سایت ساخته‌شده واقعاً درخواست می‌کند: آن را crawl کنید و شکست‌ها را لیست کنید.
#    با ویژگی مطابقت دهید، نه پسوند فایل — آدرس‌های تصویر به‌ندرت به .jpg ختم می‌شوند.
grep -rhoE 'src="[^"]+"' dist/ \
  | cut -d'"' -f2 | grep -E '^https?://' | sort -u \
  | xargs -P8 -I{} curl -s -o /dev/null -w "%{http_code} {}\n" {} \
  | grep -v "^200"

# 503 https://source.unsplash.com/random/1200x800   ← همان چیزی که دنبالش می‌گردید

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

  1. در زمان build واکشی کنید، نه در زمان درخواست. تصویری که در طول build resolve می‌شود در CI شکست می‌خورد، جلوی چشم توسعه‌دهنده، نه ساعت 3 بامداد جلوی یک کاربر.
  2. هرگز اجازه ندهید یک دارایی تزئینی مسیر حیاتی را مسدود کند. هیچ چیز خارجی بالای فرم ورود را pre-load نکنید؛ به هر <img> شخص ثالث یک فال‌بک onerror و width/height صریح بدهید تا یک شکست هزینه‌اش یک جعبه خالی باشد، نه یک jump چیدمان یا یک اسکریپت متوقف‌شده.
  3. بررسی را به CI اضافه کنید. گام 3 در بالا، اجراشده روی خروجی ساخته‌شده‌تان، «کسی بالاخره متوجه شد» را به یک build قرمز تبدیل می‌کند. تنها گامی است که از تکرار جلوگیری می‌کند.
  4. وابستگی‌های خارجی خود را مثل هرچیز دیگر بودجه‌بندی کنید. بنویسید کدام هاست‌ها اجازه دارند صفحات‌تان به آن‌ها وابسته باشند و وقتی هرکدام از کار افتاد چه اتفاقی می‌افتد. آدرسی که مجبور نبودید برایش ثبت‌نام کنید همچنان یک وابستگی است — Source ثابت کرد که این صرفاً وابستگی‌ای است که هیچ‌کس مالکش نیست.

مراجع و پانویس‌ها

1 هر کد وضعیت، شمارش و خط نقل‌قول در این مقاله در تاریخ 30 اوت 2026 از منبع اصلی‌اش گرفته شده است. کدهای وضعیت HTTP با curl علیه هر الگوی آدرس گرفته شدند؛ همه 503 با server: heroku و بدنه‌ای شامل herokucdn.com/error-pages/application-error.html برگرداندند. resolve شدن DNS همان روز تأیید شد (یک CNAME به یک هاست herokudns.com).

2 هر دو ورودی changelog عیناً از unsplash.com/documentation/changelog نقل شده‌اند. عبارت‌بندی سیاست منسوخ‌سازی (3 هفته اطلاع‌رسانی قبلی، هدر Warning، و استثنای اندپوینت‌های غیرمستندشده عمومی) از unsplash.com/documentation، همان روز آمده است.

3 خرابی TLS با openssl s_client -connect changelog.unsplash.com:443 بازتولید شد (tlsv1 alert internal error). زنجیره ریدایرکت با curl -L دنبال شد. شکاف آرشیو از API مربوط به CDX Wayback گرفته شد: آخرین اسنپ‌شات 200 از changelog.unsplash.com در تاریخ 20240324، اولین اسنپ‌شات unsplash.com/documentation/changelog در تاریخ 20240823.

4 عنوان‌ها، تاریخ‌های ایجاد و بسته‌شدن issue از API مربوط به REST GitHub خوانده شدند (mui/material-ui#42736، nextcloud/unsplash#115، sindresorhus/Actions#248) و از API مربوط به JSON در drupal.org برای issue شماره 3324054 در gin_login، که در بدنه‌اش گزارش می‌دهد «همیشه خطای اپلیکیشن Heroku می‌گیرم» در نوامبر 2022.

5 شمارش‌ها: API جست‌وجوی کد GitHub (3,344 فایل؛ یک نمونه 100‌نتیجه‌ای تکینه‌سازی‌شده به 77 ریپازیتوری، که 12 مورد بعد از 11 ژوئن 2024 ساخته شده و 20 مورد در 12 ماه گذشته پوش شده بودند)؛ API دانلود رجیستری npm (unsplash-source-es6، 23 دانلود در 30 روز گذشته)؛ /search/excerpts مربوط به Stack Exchange (1,459 پست). جست‌وجوی کد فقط ریپازیتوری‌های عمومی ایندکس‌شده را پوشش می‌دهد، پس هر عدد یک کف است.

پرسش‌های پرتکرار

آیا source.unsplash.com فقط قطع شده یا برای همیشه تعطیل شده است؟
برای همیشه تعطیل شده است. Unsplash در تاریخ ۱۱ ژوئن ۲۰۲۴ پایان کار این سرویس را اعلام کرد — «ابتدا با غیرفعال کردن قابلیت جستجو شروع می‌کنیم و در هفته‌های آینده کل اپلیکیشن را کاملاً خاموش خواهیم کرد» — این اتفاق پس از منسوخ‌شدن سرویس در تاریخ ۲۵ نوامبر ۲۰۲۱ رخ داد. هر الگویی (/random، /1600x900/?query، /collection/…، /daily) اکنون HTTP 503 به همراه صفحه عمومی خطای Application Error مربوط به Heroku برمی‌گرداند. نام دامنه هنوز هم resolve می‌شود، بنابراین خطا به‌جای خطای شبکه، به‌صورت تصویر شکسته نمایش داده می‌شود.
جایگزین مستقیم برای source.unsplash.com/random چیست؟
هیچ جایگزین آماده و بدون کلیدی وجود ندارد، و بهتر است این موضوع را از همان ابتدا بپذیرید. سه جایگزین وجود دارد. یک آدرس ثابت CDN — images.unsplash.com/photo-…?w=1600&h=900&fit=crop — به کلید نیازی ندارد اما همیشه همان یک عکس ثابت را برمی‌گرداند، که در واقع برای بیشتر کاربردهای تزئینی همین کافی است. API رسمی، یعنی GET https://api.unsplash.com/photos/random همراه با هدر Authorization: Client-ID، قابلیت تصادفی‌بودن را برمی‌گرداند اما باید سمت سرور فراخوانی شود. یک پروکسی کوچک که خودتان می‌سازید و جلوی همان endpoint قرار می‌گیرد، تنها گزینه‌ای است که یک آدرس بدون کلید به شما می‌دهد که می‌توانید مستقیم در تگ <img> استفاده کنید.
چرا ابزارهای کدنویسی هوش مصنوعی هنوز در سال ۲۰۲۶ آدرس‌های source.unsplash.com تولید می‌کنند؟
چون این آدرس تقریباً هشت سال مستند، آموزش‌داده‌شده و کپی شده بود، و داده‌های آموزشی وقتی که یک سرویس از بین می‌رود، منقضی نمی‌شوند. طبق اندازه‌گیری در تاریخ ۳۰ اوت ۲۰۲۶: جستجوی کد GitHub هنوز ۳٬۳۴۴ فایل حاوی این آدرس را نشان می‌دهد، و در نمونه‌ای شامل ۱۰۰ فایل، ۱۲ مورد از ۷۷ مخزن پس از تعطیلی سرویس ساخته شده بودند. حتی کتابخانه‌های prompt نوشته‌شده توسط انسان هم این دستور را تکرار می‌کنند — یکی از پرامپت‌های پرکاربرد GPT هنوز به مدل می‌گوید «از unsplash API استفاده کن( https://source.unsplash.com/1280x720/?… )». هر آدرس تصویری که یک مدل تولید می‌کند را تأییدنشده در نظر بگیرید و کد وضعیت آن را در CI بررسی کنید.
آیا هنوز می‌توانم بدون کلید API یک عکس تصادفی از Unsplash بگیرم؟
نه، مستقیماً از Unsplash خیر — انتخاب تصادفی اکنون پشت /photos/random قرار دارد که به Client-ID نیاز دارد. دو راه بدون کلید برای شما وجود دارد: یک پروکسی که خودتان میزبانی می‌کنید، که در آن کلید سمت سرور باقی می‌ماند و آدرس عمومی شبیه به آدرس قدیمی به نظر می‌رسد، یا یک سرویس جایگزین شخص ثالث مانند Lorem Picsum (picsum.photos/800/600)، که عکس‌های واقعی بدون نیاز به کلید ارائه می‌دهد اما هیچ‌گونه هدف‌گیری موضوعی ندارد.
آیا API Unsplash اجازه دانلود و میزبانی خودِ تصاویر را می‌دهد؟
خیر. برخلاف بیشتر APIها، Unsplash استفاده از هات‌لینک را الزامی می‌کند: آدرس‌های تصویری که API برمی‌گرداند باید مستقیماً درون‌گنجانده شوند، تا بازدید عکس برای عکاس شمارش شود. سه تعهد با این موضوع همراه است — هنگام تغییر اندازه یا برش آدرس، پارامتر ixid را حفظ کنید، عکاس و Unsplash را نام ببرید، و هنگامی که کاربر فایل را دانلود می‌کند، endpoint دانلود عکس را فراخوانی کنید. آینه‌سازی فایل‌ها روی CDN خودتان تنها بهینه‌سازی‌ای است که اجازه انجامش را ندارید.
چرا اطلاعیه منسوخ‌شدن سال ۲۰۲۱ از کاربران فعلی محافظت نکرد؟
چون اطلاعیه و سیاست واقعی یک چیز را پوشش نمی‌دادند. ورودی changelog تاریخ ۲۵ نوامبر ۲۰۲۱ وعده داده بود که «کاربردهای موجود همچنان کار خواهند کرد» و هیچ تاریخ پایانی مشخص نکرده بود. سیاست رسمی منسوخ‌شدن Unsplash — که حداقل سه هفته اطلاع‌رسانی به‌همراه هدر Warning را شامل می‌شود — فقط برای فیلدها و endpointهای مستندشده عمومی اعمال می‌شود، و همان پاراگراف بیان می‌کند که هر چیز مستندنشده ممکن است بدون هشدار تغییر کند. Source هرگز یک endpoint مستند در API نبود، بنابراین خارج از سیاستی قرار داشت که می‌توانست از آن محافظت کند.
چگونه همه آدرس‌های مرده source.unsplash.com را در پروژه‌ام پیدا کنم؟
سه مرحله، ده دقیقه. مخزن را با grep بررسی کنید شامل مستندات، تست‌ها، fixtureها و READMEها — grep -rn "source.unsplash.com" . — چون این آدرس‌ها بیشترین عمر را در کدهای نمونه دارند. پایگاه داده را جستجو کنید، چون بدنه مقالات CMS جایی است که معمولاً این آدرس‌ها پنهان می‌شوند (WHERE body LIKE '%source.unsplash.com%'). سپس خروجی build خود را کراول کنید: هر آدرس تصویر را استخراج کرده و آن را درخواست دهید، و هر چیزی که 200 برنگرداند را فهرست کنید. این مرحله آخر را به CI اضافه کنید تا یک منبع مرده از شخص ثالث باعث شکست build شود، نه شکست صفحه در تولید.

دست از شکار کلمات کلیدی بردارید. آنچه را منظور دارید توصیف کنید.

9M+ تصویر رایگان را بر اساس معنا جست‌وجو کنید — به هر زبانی، در کمتر از ۱۰۰ میلی‌ثانیه.