استفاده از کتابخانه لوسین برای پیاده سازی جستجوی سریع و انجام AutoComplete

دوشنبه 17 آذر 1393

در این مقاله تصمیم داریم جست و جو سایت و امکان AutoComplete را با استفاده از کتابخانه لوسین پیاده سازی کنیم شایان ذکر است که در این مثال عبارت مورد جستجو Highlight میشود

استفاده از کتابخانه لوسین برای پیاده سازی جستجوی سریع  و انجام  AutoComplete

این کتابخانه قدرتمند متعلق به آپاچی می باشد که دارای امکانات زیادی از جمله:

1-AutoComplete

2- غلط گیر املایی

3-انجام محاسبات آماری بر روی متون

4-امکان یافتن مطالبی مشابه

5-Highlight کردن عبارت مورد جستجو

می باشد که برای دات نت نیز امکان استفاده آن فراهم شده است.

موتور لوسین امکان جستجوی سریع رو مطالب حجیم بوسیله ایندکس گذاری فراهم می آورد .

ابتدا باید از طریق آدرس زیر 2 کتابخانه لازم را دریافت و نصب نمایید:

وارد منوی Tools شوید سپس NuGet Package Manager و Manage NuGet Package For Solution را انتخاب کنید.

قسمت  online را انتخاب کنید و سپس کلمه Lucene.NET  را جستجو کنید وآنرا نصب کنید سپس کتابخانه Lucene.Net Contrib را نیز نصب کنید.

کتابخانه Lucene مستقل از منبع داده است و تنها اطلاعاتی با فرمت شیء Document معرفی شده به آن‌را می‌شناسد.

پس باید ابتدا هر رکورد از اطلاعات خود را به شیء Document تبدیل کنیم.

لوسین برای انجام این عمل از متد زیر استفاده میکند:
 

        static Document MapPostToDocument(Products post)
        {
            var postDocument = new Document();

            postDocument.Add(new Field("ProductID", post.ProductID.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            var ProductName =
                new Field("ProductName", post.ProductName, Field.Store.YES, Field.Index.ANALYZED,
                    Field.TermVector.WITH_POSITIONS_OFFSETS);
            ProductName.Boost = 3;
            postDocument.Add(ProductName);
            return postDocument;
        }

این متد یک شی Products دریافت میکند و آنرا تبدیل به سند لوسین می کند.

ابتدا به کلاس Field نام فیلد مورد نظر را میدهیم سپس مقدار متناظر(دقت کنید که مقدار متناظر باید به صورت رشته باشد)

نکته مهم در آرگومان سوم می باشد   Field.Store.YES به این معنی می باشد که اصل اطلاعات نیز علاوه بر ایندکس شدن در فایل های لوسین ذخیره شود زمانی از Yes استفاده میکنیم ،که میخواهیم اطلاعاتی را به کاربر نمایش دهیم .

در غیر اینصورت از Field.Store.No استفاده میکنیم.

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

در آرگومان چهارم مشخص میکنیم که اطلاعات فیلد مورد نظر ایندکس شوند یا خیر.

چون روی ProductID جستجو صورت نمیگیرد از Field.Index.NOT_ANALYZED  استفاده میکنیم.

حال برای Highlighte کردن عبارت مورد جستجو باید از آرگومان Field.TermVector.WITH_POSITIONS_OFFSETS استفاده کرد.

حالا باید اطلاعاتی را که به اسناد لوسین تبدیل کردیم به فایل های Full text search لوسین تبدیل کنیم برای این کار از متد زیر استفاده میکنیم:

        static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_30;
        public static void CreateIdx(IEnumerable<Products> dataList)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(HttpRuntime.AppDomainAppPath + @"App_Data\LuceneIndex"));
            var analyzer = new StandardAnalyzer(_version);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                foreach (var post in dataList)
                {
                    writer.AddDocument(MapPostToDocument(post));
                }

                writer.Optimize();
                writer.Commit();
                writer.Dispose();
                directory.Dispose();
            }
        }

ابتدا محل ذخیره سازی فایل‌های full text search مشخص میکنیم که در اینجا من مسیر رو به شکلی قرار دادم که در داخل پروژه درون پوشه App_Data یک پوشه با نام  LuceneIndex بسازد و داخل آن فایل های خود را کپی کند.

لوسین برای انجام ادامه کار خود نیاز به یک آنالیز کننده اطلاعات نیاز دارد که ازکلاس StandardAnalyzer لوسین استفاده میکنیم.

اسناد لوسین را به شی indexWriter اضافه میکنیم.

دلیل ذکر ورژن این است که ایندکس ها با نگارش یعدی سازگار باشند.

شما باید دو نکته مهم را در کد های خود رعایت کنید 1- بروزرسانی ایندکس ها می باشد  2- محل قرار گیری متد های انجام این کار(باید در کنار متدهای به روز رسانی اطلاعات اصلی در بانک اطلاعاتی برنامه به کار برده شود)

لوسین برای انجام این کار از3 متد زیر استفاده می کند:

        public static void UpdateIndex(Products post)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(HttpRuntime.AppDomainAppPath + @"App_Data\LuceneIndex"));
            var analyzer = new StandardAnalyzer(_version);
            using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                var newDoc = MapPostToDocument(post);

                indexWriter.UpdateDocument(new Term("ProductID", post.ProductID.ToString()), newDoc);
                indexWriter.Commit();
                indexWriter.Dispose();
                directory.Dispose();
            }
        }

        public static void DeleteIndex(Products post)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(HttpRuntime.AppDomainAppPath + @"App_Data\LuceneIndex"));
            var analyzer = new StandardAnalyzer(_version);
            using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                indexWriter.DeleteDocuments(new Term("ProductID", post.ProductID.ToString()));
                indexWriter.Commit();
                indexWriter.Dispose();
                directory.Dispose();
            }
        }

        public static void AddIndex(Products post)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(HttpRuntime.AppDomainAppPath + @"App_Data\LuceneIndex"));
            var analyzer = new StandardAnalyzer(_version);
            using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                var searchQuery = new TermQuery(new Term("ProductID", post.ProductID.ToString()));
                indexWriter.DeleteDocuments(searchQuery);

                var newDoc = MapPostToDocument(post);
                indexWriter.AddDocument(newDoc);
                indexWriter.Commit();
                indexWriter.Dispose();
                directory.Dispose();
            }
        }

حال برای اجرای AutoComplete یک کلاس به برنامه خود اضافه میکنیم و کدهای زیر را به آن اضافه میکنیم:

    public static class AutoComplete
    {
        private static IndexSearcher _searcher;

        /// <summary>
        /// Get terms starting with the given prefix
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public static IList<Products> GetTermsScored(string indexPath, string prefix, int maxItems = 10)
        {
            if (_searcher == null)
                _searcher = new IndexSearcher(FSDirectory.Open(new DirectoryInfo(indexPath)), true);

            var resultsList = new List<Products>();
            if (string.IsNullOrWhiteSpace(prefix))
                return resultsList;

            prefix = prefix.ApplyCorrectYeKe();

            var results = _searcher.Search(new PrefixQuery(new Term("ProductName", prefix)), null, maxItems);


            foreach (var doc in results.ScoreDocs)
            {
                resultsList.Add(new Products
                {
                    ProductName = _searcher.Doc(doc.Doc).Get("ProductName"),
                    ProductID = int.Parse(_searcher.Doc(doc.Doc).Get("ProductID"))
                });
            }

            return resultsList;
        }
    }

در این قسمت توسط PrefixQuery به تعدادی که خودمان تعیین میکنیم (توسط maxItems در ورودی تابع GetTermsScored ) رکورد های یافت شده را بازگشت خواهیم داد خروجی لیستی است از SearchResultها شامل عنوان مطلب و Id آن که عنوان را به کاربر نمایش خواهیم دادو از Id برای هدایت اواستفاده خواهیم کرد.

 

حال در کنترلر خود قبل از تابع index کد زیر را بنویسید:

 static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\LuceneIndex";

کد بالا مسیر فایل های ایندکس شده می باشد.

برای نمایش از کد زیر استفاده میکنیم :

        public ActionResult ScoredTerms(string q)
        {
            if (string.IsNullOrWhiteSpace(q))
                return Content(string.Empty);

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = this.Url.Action(actionName: "Index", controllerName: "Home", routeValues: new { id = item.ProductID }, protocol: "http");
                result.AppendLine(item.ProductName + "|" + postUrl);
            }

            return Content(result.ToString());
        }

 

ابتدا ارجاعاتی را به jQuery، افزونه Auto-Complete و اسکریپت سفارشی تهیه شده، در فایل layout پروژه تعریف خواهیم کرد.

    <script src="~/Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.autocomplete.js" type="text/javascript"></script>
    <script src="~/Scripts/custom.js" type="text/javascript"></script>

به ویو خود رفته وبه ویو را به شکل زیر تغییر دهید:

@{
    ViewBag.Title = "جستجو";
    var scoredTermsUrl = Url.Action(actionName: "ScoredTerms", controllerName: "Home");
    var bulletImage = Url.Content("~/Content/Images/bullet_shape.png");
}
<h2>
    جستجو
</h2>

<div align="center">
    @Html.TextBox("term", "", htmlAttributes: new { dir = "ltr" })
    <br />
    جهت آزمايش او را وارد نمائيد
</div>

@section scripts
{
    <script type="text/javascript">
    EnableSearchAutocomplete('@scoredTermsUrl', '@bulletImage');
    </script>
}

قسمت مهم کدهای کنترلر :

result.AppendLine(item.Title + "|" + postUrl);
return Content(result.ToString());

توضیحات لازم:

مطابق نیاز افزونه انتخاب شده در مثال جاری، فرمت خروجی مدنظر باید شامل سطرهایی حاوی متن قابل نمایش باشد.

return Content هم سبب بازگشت این اطلاعات به افزونه خواهد شد.

نکات مهم :

بدنه متد EnableSearchAutocomplete در custom.js قر دارد تنظیم آن به اینصورت است که بعد از تایپ 2 کاراکتر شروع به جستجو میکند برای تغییر آن میتوانید minChars:2 عدد دلخواه را وارد کنید.

نام textbox دارای اهمیت می باشد به دلیل استفاده در متد جاوا اسکریپتی EnableSearchAutocomplete

نحوه مقدار دهی آدرس دسترسی به اکشن متد ScoredTerms نیز مهم می‌باشد.

توضیحات اسکریپت:

- در متد EnableSearchAutocomplete نحوه فراخوانی افزونه autocomplete را ملاحظه می‌کنید.
جهت آن، به راست به چپ تنظیم شده است. با 2 کاراکتر ورودی فعال خواهد شد با وقفه‌ای کوتاه. نیازی نیست تا انتخاب کاربر از لیست ظاهر شده حتما با عبارت جستجو شده صد در صد یکی باشد. حداکثر 20 آیتم در لیست ظاهر خواهند شد. اسکرول بار لیست را حذف کرده‌ایم. عرض آن به 300 تنظیم شده است و نحوه فرمت دهی نمایشی آن‌را نیز ملاحظه می‌کنید. برای این منظور از متد formatItem استفاده شده است. آرایه row در اینجا در برگیرنده اعضای Title و Id ارسالی به افزونه است. اندیس صفر آن به عنوان دریافتی اشاره می‌کند.
همچنین نحوه نشان دادن عکس العمل به عنصر انتخابی را هم ملاحظه می‌کنید (در متد result مقدار دهی شده).  window.location را به عنصر دوم آرایه row هدایت خواهیم کرد. این عنصر دوم مطابق کدهای اکشن متد تهیه شده، به آدرس یک صفحه اشاره می‌کند.

 

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

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

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید