Udostępnij za pośrednictwem


Samouczek: tworzenie analizatora niestandardowego dla numerów telefonów

W rozwiązaniach wyszukiwania ciągi, które mają złożone wzorce lub znaki specjalne, mogą być trudne do pracy, ponieważ domyślny analizator usuwa lub błędnie interpretuje istotne części wzorca. Powoduje to słabe środowisko wyszukiwania, w którym użytkownicy nie mogą znaleźć oczekiwanych informacji. Numery telefonów to klasyczny przykład ciągów, które są trudne do przeanalizowania. Są one w różnych formatach i zawierają znaki specjalne ignorowane przez analizator domyślny.

Ten samouczek dotyczacy numerow telefonow pokazuje, jak rozwiązać problemy ze wzorzystymi danymi przy użyciu analizatora niestandardowego. Takie podejście może być używane w przypadku numerów telefonów lub dostosowane do pól o takich samych cechach (wzorzec ze znakami specjalnymi), takich jak adresy URL, wiadomości e-mail, kody pocztowe i daty.

W tym samouczku używasz klienta REST i REST API Azure AI Search do:

  • Omówienie problemu
  • Opracuj wstępny analizator niestandardowy do obsługi numerów telefonów
  • Testowanie analizatora niestandardowego
  • Iterowanie projektu analizatora niestandardowego w celu dalszego ulepszania wyników

Wymagania wstępne

Pobieranie plików

Kod źródłowy tego samouczka znajduje się w pliku custom-analyzer.rest w repozytorium GitHub Azure-Samples/azure-search-rest-samples .

Kopiowanie klucza administratora i adresu URL

Wywołania REST w tym samouczku wymagają punktu końcowego usługi wyszukiwania oraz klucza interfejsu API administratora. Te wartości można uzyskać w witrynie Azure Portal.

  1. Zaloguj się do witryny Azure Portal, przejdź do strony Przegląd i skopiuj adres URL. Przykładowy punkt końcowy może wyglądać podobnie jak https://mydemo.search.windows.net.

  2. W obszarze Klucze ustawień>skopiuj klucz administratora. Klucze administracyjne służą do dodawania, modyfikowania i usuwania obiektów. Istnieją dwa zamienne klucze administratora. Skopiuj jedną z nich.

    Zrzut ekranu przedstawiający adres URL i klucze interfejsu API w witrynie Azure Portal.

Prawidłowy klucz interfejsu API ustanawia relację zaufania dla poszczególnych żądań między aplikacją wysyłającą żądanie a usługą wyszukiwania, która go obsługuje.

Tworzenie indeksu początkowego

  1. Otwórz nowy plik tekstowy w programie Visual Studio Code.

  2. Przypisz zmiennym punkt końcowy wyszukiwania oraz klucz interfejsu API zebrany w poprzedniej sekcji.

    @baseUrl = PUT-YOUR-SEARCH-SERVICE-URL-HERE
    @apiKey = PUT-YOUR-ADMIN-API-KEY-HERE
    
  3. Zapisz plik z .rest rozszerzeniem pliku.

  4. Wklej poniższy przykład, aby utworzyć mały indeks o nazwie phone-numbers-index z dwoma polami: id i phone_number. Nie zdefiniowano jeszcze analizatora, więc standard.lucene analizator jest używany domyślnie.

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2024-07-01  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    
      {
        "name": "phone-numbers-index",  
        "fields": [
          {
            "name": "id",
            "type": "Edm.String",
            "key": true,
            "searchable": true,
            "filterable": false,
            "facetable": false,
            "sortable": true
          },
          {
            "name": "phone_number",
            "type": "Edm.String",
            "sortable": false,
            "searchable": true,
            "filterable": false,
            "facetable": false
          }
        ]
      }
    
  5. Wybierz pozycję Wyślij wniosek. Powinieneś mieć odpowiedź HTTP/1.1 201 Created, a treść odpowiedzi powinna zawierać schemat indeksu w formacie JSON.

  6. Załaduj dane do indeksu przy użyciu dokumentów zawierających różne formaty numerów telefonów. Są to dane testowe.

    ### Load documents
    POST {{baseUrl}}/indexes/phone-numbers-index/docs/index?api-version=2024-07-01  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    
      {
        "value": [
          {
            "@search.action": "upload",  
            "id": "1",
            "phone_number": "425-555-0100"
          },
          {
            "@search.action": "upload",  
            "id": "2",
            "phone_number": "(321) 555-0199"
          },
          {  
            "@search.action": "upload",  
            "id": "3",
            "phone_number": "+1 425-555-0100"
          },
          {  
            "@search.action": "upload",  
            "id": "4",  
            "phone_number": "+1 (321) 555-0199"
          },
          {
            "@search.action": "upload",  
            "id": "5",
            "phone_number": "4255550100"
          },
          {
            "@search.action": "upload",  
            "id": "6",
            "phone_number": "13215550199"
          },
          {
            "@search.action": "upload",  
            "id": "7",
            "phone_number": "425 555 0100"
          },
          {
            "@search.action": "upload",  
            "id": "8",
            "phone_number": "321.555.0199"
          }
        ]  
      }
    
  7. Spróbuj wykonać zapytania podobne do tego, co użytkownik może wpisać. Na przykład użytkownik może wyszukiwać (425) 555-0100 w dowolnej liczbie formatów i nadal oczekiwać wyników. Rozpocznij od wyszukania (425) 555-0100.

    ### Search for a phone number
    GET {{baseUrl}}/indexes/phone-numbers-index/docs/search?api-version=2024-07-01&search=(425) 555-0100  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    

    Zapytanie zwraca trzy z czterech oczekiwanych wyników, ale także zwraca dwa nieoczekiwane wyniki.

    {
        "value": [
            {
                "@search.score": 0.05634898,
                "phone_number": "+1 425-555-0100"
            },
            {
                "@search.score": 0.05634898,
                "phone_number": "425 555 0100"
            },
            {
                "@search.score": 0.05634898,
                "phone_number": "425-555-0100"
            },
            {
                "@search.score": 0.020766128,
                "phone_number": "(321) 555-0199"
            },
            {
                "@search.score": 0.020766128,
                "phone_number": "+1 (321) 555-0199"
            }
        ]
    }
    
  8. Spróbuj ponownie bez żadnego formatowania: 4255550100.

     ### Search for a phone number
     GET {{baseUrl}}/indexes/phone-numbers-index/docs/search?api-version=2024-07-01&search=4255550100  HTTP/1.1
       Content-Type: application/json
       api-key: {{apiKey}}
    

    To zapytanie działa jeszcze gorzej, zwracając tylko jedno z czterech pasujących wyników.

    {
        "value": [
            {
                "@search.score": 0.6015292,
                "phone_number": "4255550100"
            }
        ]
    }
    

Jeśli znajdziesz te wyniki mylące, nie jesteś sam. W następnej sekcji wyjaśniono, dlaczego otrzymujesz te wyniki.

Zapoznaj się ze sposobem działania analizatorów

Aby zrozumieć te wyniki wyszukiwania, musisz zrozumieć, co robi analizator. Z tego miejsca możesz przetestować domyślny analizator przy użyciu interfejsu API analizowania, zapewniając podstawę do projektowania analizatora, który lepiej spełnia Twoje potrzeby.

Analizator jest składnikiem wyszukiwarki pełnotekstowej odpowiedzialnej za przetwarzanie tekstu w ciągach zapytania i indeksowanych dokumentach. Różne analizatory manipulują tekstem na różne sposoby w zależności od scenariusza. W tym scenariuszu musimy utworzyć analizator dostosowany do numerów telefonów.

Analizatory składają się z trzech składników:

  • Filtry znaków, które usuwają lub zamieniają poszczególne znaki z tekstu wejściowego.
  • Tokenizator, który dzieli tekst wejściowy na tokeny, które stają się kluczami w indeksie wyszukiwania.
  • Filtry tokenów , które manipulują tokenami utworzonymi przez tokenizatora.

Na poniższym diagramie pokazano, jak te trzy składniki współpracują ze sobą w celu tokenizowania zdania.

Diagram procesu analizatora w celu tokenizowania zdania

Te tokeny są następnie przechowywane w odwróconym indeksie, co umożliwia szybkie wyszukiwanie pełnotekstowe. Odwrócony indeks umożliwia wyszukiwanie pełnotekstowe przez mapowanie wszystkich unikatowych terminów wyodrębnionych podczas analizy leksykalnej do dokumentów, w których występują. Przykład można zobaczyć na poniższym diagramie:

Przykład odwróconego indeksu

Wyszukiwanie sprowadza się do wyszukiwania terminów przechowywanych w odwróconym indeksie. Gdy użytkownik wystawia zapytanie:

  1. Zapytanie jest analizowane, a terminy zapytania są analizowane.
  2. Odwrócony indeks jest skanowany pod kątem dokumentów z zgodnymi terminami.
  3. Algorytm oceniania klasyfikuje pobrane dokumenty.

Diagram podobieństwa klasyfikacji procesów analizatora

Jeśli terminy zapytania nie są zgodne z terminami w odwróconym indeksie, wyniki nie są zwracane. Aby dowiedzieć się więcej o sposobie działania zapytań, zobacz Wyszukiwanie pełnotekstowe w usłudze Azure AI Search.

Uwaga

Zapytania dotyczące części terminów są ważnym wyjątkiem od tej reguły. W przeciwieństwie do zwykłych zapytań terminowych, te zapytania (zapytanie o prefiks, zapytanie z symbolem wieloznacznym i zapytanie rozszerzone o wyrażenia regularne) pomijają proces analizy leksykalnej. Częściowe terminy są pisane z małej litery tylko przed dopasowaniem do terminów w indeksie. Jeśli analizator nie jest skonfigurowany do obsługi tego typu zapytań, często otrzymujesz nieoczekiwane wyniki, ponieważ pasujące terminy nie istnieją w indeksie.

Testowanie analizatorów przy użyciu interfejsu API analizy

Usługa Azure AI Search udostępnia interfejs API analizowania, który umożliwia testowanie analizatorów w celu zrozumienia sposobu przetwarzania tekstu.

Wywołaj interfejs API analizowania przy użyciu następującego żądania:

POST {{baseUrl}}/indexes/phone-numbers-index/analyze?api-version=2024-07-01  HTTP/1.1
  Content-Type: application/json
  api-key: {{apiKey}}

  {
    "text": "(425) 555-0100",
    "analyzer": "standard.lucene"
  }

Interfejs API zwraca tokeny wyodrębnione z tekstu przy użyciu określonego analizatora. Standardowy analizator Lucene dzieli numer telefonu na trzy oddzielne tokeny.

{
    "tokens": [
        {
            "token": "425",
            "startOffset": 1,
            "endOffset": 4,
            "position": 0
        },
        {
            "token": "555",
            "startOffset": 6,
            "endOffset": 9,
            "position": 1
        },
        {
            "token": "0100",
            "startOffset": 10,
            "endOffset": 14,
            "position": 2
        }
    ]
}

Z drugiej strony numer 4255550100 telefonu sformatowany bez żadnej interpunkcji jest tokenizowany na jeden token.

{
  "text": "4255550100",
  "analyzer": "standard.lucene"
}

Reakcja:

{
    "tokens": [
        {
            "token": "4255550100",
            "startOffset": 0,
            "endOffset": 10,
            "position": 0
        }
    ]
}

Należy pamiętać, że zarówno terminy zapytania, jak i indeksowane dokumenty są analizowane. Po powrocie do wyników wyszukiwania z poprzedniego kroku możesz zacząć widzieć, dlaczego te wyniki są zwracane.

W pierwszym zapytaniu zwracane są nieoczekiwane numery telefonów, ponieważ jeden z ich tokenów jest 555zgodny z jednym z wyszukiwanych terminów. W drugim zapytaniu zwracana jest tylko jedna liczba, ponieważ jest to jedyny rekord, który ma token pasujący 4255550100.

Tworzenie analizatora niestandardowego

Gdy już zrozumiesz wyniki, które widzisz, stwórz analizator niestandardowy, aby ulepszyć logikę tokenizacji.

Celem jest zapewnienie intuicyjnego wyszukiwania numerów telefonów niezależnie od formatu zapytania lub indeksowanego ciągu. Aby osiągnąć ten wynik, określ filtr znaków, tokenizator i filtr tokenu.

Filtry znaków

Filtry znaków przetwarzają tekst, zanim zostanie on przekazany do tokenizatora. Typowe zastosowania filtrów znaków to filtrowanie elementów HTML i zastępowanie znaków specjalnych.

W przypadku numerów telefonów chcesz usunąć białe znaki i znaki specjalne, ponieważ nie wszystkie formaty numerów telefonów zawierają te same znaki specjalne i spacje.

"charFilters": [
    {
      "@odata.type": "#Microsoft.Azure.Search.MappingCharFilter",
      "name": "phone_char_mapping",
      "mappings": [
        "-=>",
        "(=>",
        ")=>",
        "+=>",
        ".=>",
        "\\u0020=>"
      ]
    }
  ]

Filtr usuwa -()+. oraz spacje z danych wejściowych.

Dane wejściowe Dane wyjściowe
(321) 555-0199 3215550199
321.555.0199 3215550199

Tokenizatory

Tokenizatory dzielą tekst na tokeny i odrzucają niektóre znaki, takie jak interpunkcja, po drodze. W wielu przypadkach celem tokenizacji jest podzielenie zdania na poszczególne wyrazy.

W tym scenariuszu użyj tokenizera słowa kluczowego , keyword_v2aby przechwycić numer telefonu jako pojedynczy termin. Nie jest to jedyny sposób rozwiązania tego problemu, jak wyjaśniono w sekcji Alternatywne podejścia .

Tokenizery słów kluczowych zawsze wyświetlają dokładnie ten sam tekst, który otrzymują jako pojedynczy termin.

Dane wejściowe Dane wyjściowe
The dog swims. [The dog swims.]
3215550199 [3215550199]

Filtry tokenów

Filtry tokenów modyfikują lub filtrują tokeny wygenerowane przez tokenizatora. Jednym z typowych zastosowań filtru tokenu jest zamiana wszystkich znaków na małe litery przy użyciu filtru tokenu zmieniającego na małe litery. Innym typowym zastosowaniem jest filtrowanie stopwords, takich jak the, andlub is.

Chociaż w tym scenariuszu nie trzeba używać jednego z tych filtrów, użyj filtru tokenu nGram, aby umożliwić częściowe wyszukiwanie numerów telefonów.

"tokenFilters": [
  {
    "@odata.type": "#Microsoft.Azure.Search.NGramTokenFilterV2",
    "name": "custom_ngram_filter",
    "minGram": 3,
    "maxGram": 20
  }
]

NGramTokenFilterV2

Filtr tokenów nGram_v2 dzieli tokeny na n-gramy o określonym rozmiarze na podstawie parametrów minGram i maxGram.

Dla analizatora telefonu minGram jest ustawione na 3 ponieważ jest to najkrótszy podciąg, który użytkownicy oczekują wyszukiwać. maxGram jest ustawiona w taki sposób, 20 aby wszystkie numery telefonów, nawet z dodatkami, mogły się zmieścić w jednym n-gramie.

Niepożądany efekt uboczny n-gramów polega na tym, że zwracane są niektóre fałszywe wyniki dodatnie. Ten problem można rozwiązać w późniejszym kroku, tworząc oddzielny analizator wyszukiwania, który nie zawiera filtru tokenu n-gram.

Dane wejściowe Dane wyjściowe
[12345] [123, 1234, 12345, 234, 2345, 345]
[3215550199] [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

Analizator

Dzięki filtrom znaków, tokenizatorowi i filtrom tokenów możesz zdefiniować analizator.

"analyzers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
    "name": "phone_analyzer",
    "tokenizer": "keyword_v2",
    "tokenFilters": [
      "custom_ngram_filter"
    ],
    "charFilters": [
      "phone_char_mapping"
    ]
  }
]

W interfejsie API analizowania, biorąc pod uwagę następujące dane wejściowe, dane wyjściowe z analizatora niestandardowego są następujące:

Dane wejściowe Dane wyjściowe
12345 [123, 1234, 12345, 234, 2345, 345]
(321) 555-0199 [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

Wszystkie tokeny w kolumnie wyjściowej istnieją w indeksie. Jeśli zapytanie zawiera dowolne z tych terminów, zwracany jest numer telefonu.

Ponowne kompilowanie przy użyciu nowego analizatora

  1. Usuń bieżący indeks.

     ### Delete the index
     DELETE {{baseUrl}}/indexes/phone-numbers-index?api-version=2024-07-01 HTTP/1.1
         api-key: {{apiKey}}
    
  2. Utwórz ponownie indeks przy użyciu nowego analizatora. Ten schemat indeksu dodaje definicję analizatora niestandardowego i przypisanie analizatora niestandardowego w polu numer telefonu.

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2024-07-01  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    
    {
        "name": "phone-numbers-index-2",  
        "fields": [
          {
              "name": "id",
              "type": "Edm.String",
              "key": true,
              "searchable": true,
              "filterable": false,
              "facetable": false,
              "sortable": true
          },
          {
              "name": "phone_number",
              "type": "Edm.String",
              "sortable": false,
              "searchable": true,
              "filterable": false,
              "facetable": false,
              "analyzer": "phone_analyzer"
          }
        ],
        "analyzers": [
            {
              "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
              "name": "phone_analyzer",
              "tokenizer": "keyword_v2",
              "tokenFilters": [
              "custom_ngram_filter"
            ],
            "charFilters": [
              "phone_char_mapping"
              ]
            }
          ],
          "charFilters": [
            {
              "@odata.type": "#Microsoft.Azure.Search.MappingCharFilter",
              "name": "phone_char_mapping",
              "mappings": [
                "-=>",
                "(=>",
                ")=>",
                "+=>",
                ".=>",
                "\\u0020=>"
              ]
            }
          ],
          "tokenFilters": [
            {
              "@odata.type": "#Microsoft.Azure.Search.NGramTokenFilterV2",
              "name": "custom_ngram_filter",
              "minGram": 3,
              "maxGram": 20
            }
          ]
        }
    

Testowanie analizatora niestandardowego

Po ponownym utworzeniu indeksu przetestuj analizator przy użyciu następującego żądania:

POST {{baseUrl}}/indexes/tutorial-first-analyzer/analyze?api-version=2024-07-01  HTTP/1.1
  Content-Type: application/json
  api-key: {{apiKey}} 

  {
    "text": "+1 (321) 555-0199",
    "analyzer": "phone_analyzer"
  }

Powinna zostać wyświetlona kolekcja tokenów wynikających z numeru telefonu.

{
    "tokens": [
        {
            "token": "132",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        {
            "token": "1321",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        {
            "token": "13215",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        ...
        ...
        ...
    ]
}

Poprawianie analizatora niestandardowego w celu obsługi wyników fałszywie dodatnich

Po użyciu niestandardowego analizatora do wykonywania przykładowych zapytań w odniesieniu do indeksu, powinno być widoczne, że poprawiła się trafność wyszukiwania, a teraz zwracane są wszystkie odpowiadające numery telefonów. Jednak filtr tokenu n-gram powoduje również zwrócenie niektórych wyników fałszywie dodatnich. Jest to typowy efekt uboczny filtru tokenu n-gram.

Aby zapobiec wynikom fałszywie dodatnim, utwórz oddzielny analizator do wykonywania zapytań. Ten analizator jest identyczny z poprzednim, z tą różnicą, że pomija element custom_ngram_filter.

    {
      "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
      "name": "phone_analyzer_search",
      "tokenizer": "custom_tokenizer_phone",
      "tokenFilters": [],
      "charFilters": [
        "phone_char_mapping"
      ]
    }

W definicji indeksu określ zarówno indexAnalyzer i searchAnalyzer.

    {
      "name": "phone_number",
      "type": "Edm.String",
      "sortable": false,
      "searchable": true,
      "filterable": false,
      "facetable": false,
      "indexAnalyzer": "phone_analyzer",
      "searchAnalyzer": "phone_analyzer_search"
    }

Dzięki tej zmianie wszystko jest gotowe. Oto następne kroki do wykonania:

  1. Usuń indeks.

  2. Utwórz ponownie indeks po dodaniu nowego analizatora niestandardowego (phone_analyzer-search) i przypisz ten analizator do pola właściwości phone-numbersearchAnalyzer.

  3. Załaduj ponownie dane.

  4. Ponownie przetestuj zapytania, aby sprawdzić, czy wyszukiwanie działa zgodnie z oczekiwaniami. Jeśli używasz przykładowego pliku, ten krok tworzy trzeci indeks o nazwie phone-number-index-3.

Podejścia alternatywne

Analizator opisany w poprzedniej sekcji został zaprojektowany w celu zmaksymalizowania elastyczności wyszukiwania. Jednak odbywa się kosztem przechowywania wielu potencjalnie nieważnych terminów w indeksie.

W poniższym przykładzie przedstawiono alternatywny analizator, który jest bardziej wydajny w tokenizacji, ale ma wady.

Biorąc pod uwagę dane wejściowe 14255550100, analizator nie może logicznie fragmentować numeru telefonu. Na przykład nie może oddzielić kodu kraju , 1od kodu 425obszaru , . Ta rozbieżność prowadzi do braku zwracanego numeru telefonu, jeśli użytkownik nie uwzględni kodu kraju w wyszukiwaniu.

"analyzers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
    "name": "phone_analyzer_shingles",
    "tokenizer": "custom_tokenizer_phone",
    "tokenFilters": [
      "custom_shingle_filter"
    ]
  }
],
"tokenizers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.StandardTokenizerV2",
    "name": "custom_tokenizer_phone",
    "maxTokenLength": 4
  }
],
"tokenFilters": [
  {
    "@odata.type": "#Microsoft.Azure.Search.ShingleTokenFilter",
    "name": "custom_shingle_filter",
    "minShingleSize": 2,
    "maxShingleSize": 6,
    "tokenSeparator": ""
  }
]

W poniższym przykładzie numer telefonu jest podzielony na części, których użytkownik zwykle szuka.

Dane wejściowe Dane wyjściowe
(321) 555-0199 [321, 555, 0199, 321555, 5550199, 3215550199]

W zależności od wymagań może to być bardziej wydajne podejście do problemu.

Wnioski

W tym poradniku przedstawiono, jak zbudować i przetestować niestandardowy analizator. Utworzyłeś indeks, zindeksowałeś dane, a następnie wykonałeś zapytanie względem indeksu, aby zobaczyć, jakie wyniki wyszukiwania zostały otrzymane. W tym miejscu użyłeś interfejsu API analizy, aby zobaczyć proces analizy leksykalnej w praktyce.

Chociaż analizator zdefiniowany w tym samouczku oferuje łatwe rozwiązanie do wyszukiwania numerów telefonów, ten sam proces może służyć do tworzenia analizatora niestandardowego dla dowolnego scenariusza, który ma podobne cechy.

Czyszczenie zasobów

Gdy pracujesz we własnej subskrypcji, dobrym pomysłem jest usunięcie zasobów, które nie są już potrzebne po zakończeniu projektu. Pozostawione bez nadzoru uruchomione zasoby mogą generować koszty. Zasoby możesz usuwać pojedynczo lub jako grupę zasobów, usuwając cały zestaw zasobów.

Zasoby można znaleźć w witrynie Azure Portal i zarządzać nimi, korzystając z linku Wszystkie zasoby lub Grupy zasobów w okienku nawigacji po lewej stronie.

Następne kroki

Teraz, gdy wiesz już, jak utworzyć analizator niestandardowy, przyjrzyj się wszystkim różnym filtrom, tokenizatorom i analizatorom dostępnym do tworzenia zaawansowanego środowiska wyszukiwania: