جوه المحرك · فصل ٣

تحليل النص قبل ما يتخزن

Character filters · tokenizer · token filters · synonyms
🎯 الهدف: تمشي النص في خط التحليل، وتبني analyzer ينفع متجر مصري بعربي وإنجليزي من غير ما تبوّظ أرقام الموديلات.

الكلمة اللي اليوزر كتبها مش هي اللي بتتخزن

Apple iPhone 15 Pro لو اتسجلت جملة واحدة، البحث عن iphone ممكن ميلقاش حاجة. التحليل بيكسر النص ويوحّده وقت الـ indexing ووقت السؤال، عشان الطرفين يتكلموا نفس اللغة.

الناتج توكنز زي apple و iphone و 15 و pro. من هنا تقدر تعمل lowercase، أو تشيل كلمات مالهاش معنى، أو توحّد المرادفات. القواعد دي قرار بزنس ولغة، مش زرار واحد اسمه “بحث عربي”.

كل مرحلة بتقرر إيه

Character filters

بتشتغل على النص وهو لسه جملة. زي ما تحوّل ١٥ إلى 15، أو تشيل حركة، قبل التقسيم.

Tokenizer

يقص الجملة كلمات. الـ standard tokenizer مناسب لأسماء المنتجات المختلطة. بيفصل على المسافات وعلامات الترقي.

بعد التقسيم تيجي token filters بالترتيب:

  • lowercase: iPhone و iphone يبقوا نفس التوكن.
  • stop words: كلمات زي with و for و “من” و “في” بتتشال لو مش بتفرّق في البحث. متشلّهاش من غير ما تجرب، لأن “pro” مثلًا مش stop word ولو اتشالت هتبوّظ موديلات.
  • stemming: يرجع الكلمة لجذرها. مفيد في الوصف الطويل، وخطر على اسم المنتج. stemmer عربي قوي ممكن يقرّب كلمات مش عايزها تتقرب.
  • synonyms: آيفون و ايفون و iphone يبقوا حاجة واحدة. و لابتوب و laptop. المرادف الغلط بيوسّع البحث ويزوّد نتائج مش مقصودة.

Analyzer للمتجر

src/index-products.js
import { es } from "./es.js";const index = "products";export const settings = {  analysis: {    char_filter: {      indic_digits: {        type: "mapping",        mappings: ["٠ => 0", "١ => 1", "٢ => 2", "٣ => 3", "٤ => 4", "٥ => 5", "٦ => 6", "٧ => 7", "٨ => 8", "٩ => 9"],      },    },    filter: {      product_synonyms: {        type: "synonym_graph",        synonyms: [          "iphone, آيفون, ايفون",          "laptop, لابتوب, لاب توب, notebook",          "phone, موبايل, تليفون",        ],      },    },    analyzer: {      product_text: {        type: "custom",        char_filter: ["indic_digits"],        tokenizer: "standard",        filter: ["lowercase", "arabic_normalization", "product_synonyms"],      },    },  },};export const mappings = {  properties: {    sku: { type: "keyword" },    name: { type: "text", analyzer: "product_text" },    description: { type: "text", analyzer: "product_text" },    brand: { type: "keyword" },    category: { type: "keyword" },    price: { type: "float" },    rating: { type: "float" },    stock: { type: "integer" },    ram_gb: { type: "integer" },    storage_gb: { type: "integer" },  },};export async function ensureProductsIndex() {  const exists = await es.indices.exists({ index });  if (exists) return;  await es.indices.create({ index, settings, mappings });}

brand من نوع keyword لأن الفلتر “Apple” تطابق كامل، مش كلمات. هتحتاجه في الـ facets. arabic_normalization بيلم أشكال حروف زي الألف، فمتكررش نفس الشغل بفلتر تاني من غير ما تقرا هو بيعمل إيه. المرادف لازم يتكتب بالشكل اللي طالع من الفلتر اللي قبله. لو التطبيع غيّر الألف، حط الشكل الجديد في القائمة، و_analyze هو الحكم.

تغيير الـ analyzer بعد ما الإندكس اتملّى مش بيرجع يحلّل المستندات القديمة. هتحتاج reindex. عشان كده جرّب المرادفات على عينة قبل ما تفتحها على عشر مليون منتج.

🧪 Lab

بعد ما الإندكس يتخلق، اسأل المحرك هو شايف الجملة إزاي:

curl -s localhost:9200/products/_analyze \  -H 'Content-Type: application/json' \  -d '{"analyzer":"product_text","text":"آيفون ١٥ برو"}'

المفروض تشوف توكنز قريبة من iphone و 15 و برو (أو pro لو ضفت المرادف). لو ١٥ فضلت زي ما هي، الـ char filter مش واصل للـ analyzer. لو آيفون مفترق عن iphone، مرادفات الـ synonym لسه مش في الخط.

✅ الخلاصة — Checklist

🧠 اختبر نفسك
١) ليه iPhone و iphone بيلاقوا بعض من غير ما تكتب الاتنين في الكويري؟
فلتر lowercase بيوحّد التوكن وقت التخزين ووقت السؤال.
٢) SKU يتخزن text ولا keyword؟
keyword. عايز القيمة كاملة، مش توكنز متقسمة.
٣) ضفت مرادف جديد والإندكس القديم مبيستخدمهوش. ليه؟
المستندات القديمة اتسجلت بالتحليل القديم. المرادف الجديد محتاج إعادة index.