Screener.in Search API: یک بررسی عملکرد! 🔎

هی همه! بنابراین، اخیراً عمیقاً در تحلیل بنیادی غوطه ور شده ام و از یک وب سایت محبوب غربالگری سهام برای کمک به یافتن شرکت های امیدوارکننده استفاده می کنم. من به درک “چرا” پشت هر عدد اعتقاد زیادی دارم، و این به ابزارهایی که استفاده می کنم نیز تسری پیدا می کند. شما می دانید که چگونه است – شما با تصویر بزرگ شروع می کنید، اما توسعه دهنده در من همیشه می خواهد لایه ها را جدا کند و ببیند سوسیس چگونه ساخته می شود.
همه چیز به اندازه کافی معصومانه شروع شد. من از قابلیت جستجوی غربالگر استفاده می کردم و شرکت هایی را پیدا می کردم که معیارهای خاص من را برآورده می کردند. همه چیز در ظاهر خوب، تمیز و کارآمد به نظر می رسید. اما کنجکاوی من را بیشتر کرد. شروع به تایپ عبارت جستجوی خود کردم و از روی عادت، برگه شبکه مرورگرم را برای تماشای تماسهای API باز کردم. آن وقت بود که متوجه یک چیز عجیب شدم. به ازای هر ضربه کلید، یک تماس API جدید به سرور ارسال شد! این به این معنی است که اگر «KPI Green Energy» را تایپ کردم، غربالگر درخواستهای جداگانهای برای «k»، «kp»، «kpi»، «kpig» و در نهایت «kpigreen» ارسال کرد. هر پرس و جو بدون هیچ گونه تاخیر یا بهینه سازی به باطن ارسال می شود که منجر به درخواست های اضافی شبکه می شود.
من یک تحلیل زمان بندی کوچک انجام دادم تا ببینم چه اتفاقی در حال رخ دادن است، و این چیزی است که پیدا کردم:
-
مدت زمان تماس API: خود سرور بسیار سریع به نظر می رسد. زمان پاسخ 164 میلیثانیه (ThrottlingURLLLoader::OnReceiveResponse)، 88 میلیثانیه (MojoURLLoaderClient::OnReceiveResponse) و 81 میلیثانیه (ResourceRequestSender::OnReceivedResponse) را دیدم. این نشان میدهد که باطن درخواستها را به نحو احسن مدیریت میکند.
-
رویدادهای XHR: مقصر واقعی در سمت مشتری (مرورگر من) بود. توالی سریع رویدادهای XHRReadyStateChange و XHRLoad شک من را تأیید کرد: هیچ مکانیزم دریچه گاز یا تأخیر هوشمند وجود نداشت. اساساً، مرورگر هر ضربه کلید را بلافاصله بدون هیچ دوره انتظاری به سرور ارسال می کرد.
من مشکلات جدی در نحوه عملکرد جستجوی نمایشگر سهام پیدا کردم. این مانند یک شیر آب نشتی است که آب (پهنای باند و قدرت سرور) را هدر می دهد. اینجاست که اشکال دارد:
⚠️ مشکلات:
-
بدون انحراف: هر بار که نامه ای را تایپ می کنید، جستجو پیامی را به سرور ارسال می کند. این پیام ها خیلی زیاد است! مانند ارسال یک پیام متنی برای هر حرف در یک کلمه به جای ارسال یکباره کل کلمه. این باعث می شود سرور بیش از حد کار کند و سرعت اینترنت را هدر دهد.
-
بدون دریچه گاز: هیچ محدودیتی برای سرعت ارسال این پیام ها وجود ندارد. به اندازه کافی سریع تایپ کنید، و سرور را با درخواست ها پر خواهید کرد.
-
تماس های اضافی: بسیاری از پیام های ارسال شده برای کلمات ناقص (مانند “k”، “kr”، “kri”) هستند که پس از اتمام تایپ اهمیتی ندارند. مثل این است که هر جمله ناتمامی را که شروع می کنید قبل از اینکه در نهایت یکی را تمام کنید یادداشت کنید.
-
بدون حافظه پنهان سمت کلاینت: اگر چیزی را اشتباه تایپ کنید و به عقب برگردید، به جای به خاطر سپردن آنچه از قبل می دانسته است، دوباره همان پیام ها را ارسال می کند. مثل اینه که یادت رفته چی گفتی
📊 اثرات:
- بار بیش از حد سرور: سرور با حجم بالایی از درخواست ها مواجه می شود که بیشتر آنها برای درخواست های ناقص هستند.
- فعالیت غیر ضروری شبکه: شبکه مملو از انتقال داده های اضافی است که پهنای باند را مصرف می کند.
- زمان پاسخ آهسته: جریان مداوم درخواست ها می تواند منجر به پاسخ های کندتر شود، زیرا سرور مشغول رسیدگی به تماس های غیر ضروری است.
💡 توصیه ها:
انحراف مانند داشتن یک رویکرد “انتظار و دیدن” است. به جای اینکه به هر ضربه کلید فورا پاسخ دهد، قبل از اجرای جستجو برای مدت مشخصی از عدم فعالیت منتظر می ماند.
برای به تاخیر انداختن تماسهای API تا زمانی که کاربر تایپ را متوقف کند (مثلاً 300 میلیثانیه) یک تایمر بازتاب معرفی کنید.
let debounceTimer;
const searchInput = document.getElementById("input");
searchInput.addEventListener("input", (event) => {
const inputValue = event.target.value;
const delay = 500; // Set delay to 500ms (0.5 seconds)
// 1. Clear Any Existing Timer:
// If the user is still typing, we cancel any pending search.
clearTimeout(debounceTimer);
// 2. Start a New Timer:
// After the delay, execute the search only if the user stopped typing.
debounceTimer = setTimeout(() => {
// 3. Perform the Search Action:
// The searchTasks function is called only when no input has been received for 500ms
console.log("Performing search for:", inputValue);
searchTasks(inputValue); // Replace this line with actual search call
}, delay);
});
// Dummy search function - Replace this with your actual search implementation
function searchTasks(query) {
// Here you would perform the actual search logic
// Example: Fetch data from API using 'query'
console.log("Searching for: ", query);
}
چگونه در عمل کار می کند
- کاربر شروع به تایپ در ورودی می کند.
- هر کاراکتر وارد شده باعث ایجاد رویداد ورودی می شود.
- با هر رویداد، مهلت زمانی برنامه ریزی شده قبلی پاک می شود و یک تایمر جدید تنظیم می شود.
- اگر کاربر به مدت 500 میلی ثانیه مکث کند، بازگشت فراخوان setTimeout اجرا می شود و تابع searchTasks را با مقدار ورودی فعلی فراخوانی می کند.
- اگر کاربر به تایپ کردن ادامه دهد، مهلت زمانی پاک میشود و بازنشانی میشود و تا زمانی که تایپ برای 500 میلیثانیه متوقف شود، از فراخوانی SearchTasks جلوگیری میشود.
- کاهش سرعت تماس ها با محدودیت نرخ
گلوله زدن مانند اجرای یک “دروازه بان” است. سرعت اجرای یک تابع را محدود می کند. حتی اگر کاربر به سرعت تایپ خود را ادامه دهد، درخواست جستجوی جدید تنها پس از تأخیر مشخص شده ارسال می شود و تعداد درخواست ها را محدود می کند.
تعداد تماس های API را در یک دوره زمانی خاص محدود کنید (مثلاً یک تماس در ثانیه).
let lastSearchTime = 0; // Timestamp of the last search
const searchInput = document.getElementById("input");
searchInput.addEventListener("input", (event) => {
const currentTime = Date.now();
const throttleDelay = 700; // Throttle delay (700ms)
const inputValue = event.target.value.trim();
// 1. Check if enough time has passed since the last search
if (currentTime - lastSearchTime >= throttleDelay) {
// 2. Perform Search Action
console.log("Throttled search for: ", inputValue);
searchTasks(inputValue); // Execute the search logic
lastSearchTime = currentTime; // Update last search time
} else {
console.log("Throttled, not searching for: ", inputValue);
}
});
// Dummy search function - Replace this with your actual search implementation
function searchTasks(query) {
// Here you would perform the actual search logic
// Example: Fetch data from API using 'query'
console.log("Searching for: ", query);
}
چگونه در عمل کار می کند
- کاربر شروع به تایپ در ورودی می کند.
- هر کاراکتر وارد شده باعث ایجاد رویداد ورودی می شود.
- با هر رویداد، زمان فعلی با زمانی که جستجو برای آخرین بار آغاز شد (lastSearchTime) مقایسه می شود.
- اگر اختلاف زمانی بیشتر از throttleDelay باشد، منطق جستجو (عملکرد searchTasks) اجرا می شود و lastSearchTime به روز می شود.
- اگر اختلاف زمانی کمتر از throttleDelay باشد، جستجو نادیده گرفته میشود و مانع از فراخوانی چندباره تابع searchTasks میشود. این باعث میشود هنگام تایپ کردن کاربر، سرور تحت تأثیر درخواستهای جستجو قرار نگیرد و بار سرور را کاهش میدهد.
حافظه پنهان سمت مشتری نتایج جستجوهای قبلی را مستقیماً در مرورگر کاربر ذخیره می کند. اگر کاربر همان عبارت (یا عبارت مشابهی را که می تواند از یک نتیجه ذخیره شده در حافظه پنهان استفاده کند) جستجو کند، سیستم می تواند به جای برقراری تماس سرور جدید، نتیجه را مستقیماً از مرورگر بازیابی کند.
مثال: اگر کاربر کریشنا را تایپ میکند، نتیجه را برای کرش و kri را برای بازیابی سریعتر در حافظه پنهان ذخیره کنید.
let searchCache = {}; // Initialize the cache
const searchInput = document.getElementById("input");
searchInput.addEventListener("input", async (event) => {
const query = event.target.value.trim();
if (!query) {
// If empty query clear the results and return early.
updateSearchResults([]);
return;
}
const cachedResult = getCachedResult(query);
if (cachedResult) {
console.log("Using cache for query:", query);
updateSearchResults(cachedResult);
return;
}
console.log("Making API call for query:", query);
// Simulate API call. In reality this would be an actual network call
const results = await fetchSearchResults(query);
if(results) {
cacheSearchResults(query, results);
updateSearchResults(results);
}
});
function getCachedResult(query) {
if(searchCache[query]) return searchCache[query];
for (let i = query.length - 1; i > 0; i--) {
const prefix = query.substring(0, i);
if (searchCache[prefix]) {
console.log("Using cached results for prefix: ", prefix);
return searchCache[prefix];
}
}
return null;
}
function cacheSearchResults(query, results) {
// Store results for current query and all of its prefixes.
for (let i = 1; i <= query.length; i++) {
const prefix = query.substring(0, i);
searchCache[prefix] = results;
}
console.log("Cache updated: ", searchCache);
}
// Simulate fetching results from an API (replace with actual API call).
async function fetchSearchResults(query) {
return new Promise(resolve => {
// Simulate API delay
setTimeout(() => {
const mockResults = generateMockResults(query);
resolve(mockResults);
}, 500);
});
}
function updateSearchResults(results) {
const resultsContainer = document.getElementById("results");
resultsContainer.innerHTML = ""; // clear previous results
if (results && results.length > 0) {
results.forEach(result => {
const resultItem = document.createElement("li");
resultItem.textContent = result;
resultsContainer.appendChild(resultItem);
});
} else {
const noResults = document.createElement("li");
noResults.textContent = "No results found";
resultsContainer.appendChild(noResults);
}
}
function generateMockResults(query) {
const results = [];
for (let i = 1; i <= 5; i++) {
results.push(`${query} Result ${i}`);
}
return results;
}
چگونه کار می کند
- کاربر در ورودی جستجو تایپ می کند.
- شنونده رویداد ورودی فعال می شود.
- تابع getCachedResult برای بررسی کش فراخوانی می شود.
- اگر نتیجه در حافظه پنهان باشد، مستقیماً بازیابی و نمایش داده می شود.
- اگر نتیجه در کش نباشد، تابع fetchSearchResults برای واکشی نتایج جستجو از API، که در کد مثال ما شبیهسازی شده است، فراخوانی میشود.
- سپس نتایج برای پرس و جو و همه پیشوندهای آن با استفاده از cacheSearchResults ذخیره می شوند و با تابع updateSearchResults به کاربر نمایش داده می شوند.
نکات کلیدی
- پیشوند کش: کد نه تنها برای پرس و جو کامل بلکه برای هر یک از پیشوندهای آن نیز نتایج را در حافظه پنهان ذخیره می کند. این به پاسخگویی بیشتر جستجو با تایپ کاربر کمک می کند.
- Mock API: برای نمایش، من تماس های API را با استفاده از setTimeout شبیه سازی کرده ام. شما باید آن را با تابع تماس API واقعی خود جایگزین کنید.
- کش ساده: این یک پیاده سازی بسیار ابتدایی است. برای یک برنامه دنیای واقعی، در نظر بگیرید:
- انقضای حافظه پنهان: نتایج باید پس از یک دوره مشخص منقضی شوند.
- حداکثر اندازه: برای جلوگیری از مشکلات حافظه، اندازه حافظه پنهان را محدود کنید.
- استراتژی های عدم اعتبار کش: خط مشی هایی را در مورد چگونگی و زمان حذف موارد از حافظه پنهان ایجاد کنید.
- Empty Query Handling: یک بررسی اضافی برای رسیدگی به درخواست های خالی با پاک کردن نتایج اضافه شد
برای استفاده
- تابع fetchSearchResults ساختگی را با منطق فراخوانی API واقعی خود جایگزین کنید.
- مطمئن شوید که یک عنصر HTML با ورودی شناسه برای فیلد ورودی و یک عنصر با نتایج شناسه برای نمایش نتایج جستجو وجود دارد.
-
ارسال پیام های بزرگ
در صورت امکان، به جای تعداد زیادی از افراد کوچک، یک پیام بزرگ با تمام اطلاعات جستجو ارسال کنید.
بهینه سازی قابلیت های جستجو برای ساخت برنامه های کارآمد حیاتی است. Debouncing، throttling، caching سمت مشتری، و درخواست های دسته ای همگی تکنیک های قدرتمندی هستند که می توانند عملکرد سیستم جستجوی شما را به طور چشمگیری بهبود بخشند.
این هم افزایی منجر به یک تجربه کاربری پاسخگوتر، کارآمدتر و به طور کلی بهتر می شود. بار روی سرور و شبکه را کاهش می دهد و در نهایت تجربه ای روان و کارآمد را به کاربران می دهد.
امیدوارم توسعه دهندگان Screener.in این مقاله را ببینند. به نظر می رسد که آنها برخی از ترفندهای مهم وب سایت را که جستجو را بسیار سریعتر و آسان تر می کند، از دست داده اند. آنها باید آن را بررسی کنند و اوضاع را بهبود بخشند.
نظر شما در مورد این بینش چیست؟ به من خبر بده! 💭