Finding the right path: how faceted navigation affects SEO (translation). Faceted Navigation Solutions

We took a quick look at the installation and basic syntax of PINQ, which has been ported to PHP version LINQ. In this article, we'll look at how to use PINQ to simulate the faceted search feature in MySQL.

In this article we will not cover all aspects of faceted search. Interested people can search for suitable information on the Internet.

Typical facet search works like this:

  • User enters keyword, or several keywords to search for. For example, “router” to search for products in which the word “router” appears in the description, keywords, category name, tags, etc.
  • The site returns a list of products that match these criteria.
  • The site provides several links to customize your search terms. For example, it may allow you to specify specific router manufacturers, or set a price range, or other features.
  • The user can continue to specify additional criteria search in order to obtain the data set of interest.

Faceted search is very popular and is powerful tool, it can be observed on almost any e-commerce related website.

Unfortunately, faceted search is not built into MySQL. So what should we do if we still use MySQL, but want to give the user this opportunity?

With PINQ, which has a similar, powerful and simple approach, we can achieve the same behavior as if we were using other database engines.

Expanding the demo from the first part

Comment: All code from this part, and from the first part, can be found in the repository.

In this article, we'll expand on the demo from Part 1 with a significant improvement in the form of facet search.

Let's start with index.php by adding following lines:

$app->get("demo2", function () use ($app) ( global $demo; $test2 = new pinqDemo\Demo($app); return $test2->test2($app, $demo->test1 ($app)); $app->get("demo2/facet/(key)/(value)", function ($key, $value) use ($app) ( global $demo; $test3 = new pinqDemo\Demo($app); return $test3->test3($app, $demo->test1($app), $key, $value ));

The first route takes us to a page to view all posts that match the keyword search. To keep the example simple, we select all books from the book_book table. It will also display the resulting data set and a set of links to specify the search criteria.

IN real applications, after clicking on such links, all facet filters will adjust to the boundary values ​​of the resulting data set. The user will thus be able to sequentially add new search conditions, for example, first select a manufacturer, then specify a price range, etc.

But in this example we will not implement this behavior - all filters will reflect the boundary values ​​​​of the original data set. This is the first limitation and the first candidate for improvement in our demo.

As you can see in the code above, the actual functions are located in another file called pinqDemo.php. Let's take a look at the corresponding code that provides the faceted search feature.

Aspect class

The first step is to create a class that represents an aspect. In general, an aspect should contain several properties:

  • The data it operates on ( $data)
  • The key by which the grouping is performed ( $key)
  • Key type ($type). Can be one of the following:
    • specify the full string for an exact match
    • indicate part of the string (usually the initial one) to search by pattern
    • indicate a range of values, for grouping by range
  • if the key type is a range of values, you need to define a value step to determine the lower and upper bounds of the range; or if the type is part of a string, you must specify how many first letters will be used for grouping ($range)

Grouping- the most critical part of the aspect. All aggregated information that an aspect may be able to return depends on the grouping criteria. Typically the most used search terms are “ Full line”, “Part of a string”, or “Value range”.

Namespace classFacet ( use Pinq\ITraversable, Pinq\Traversable; class Facet ( public $data; // Original data set public $key; // field by which to group public $type; // F: entire row; S: start strings; R: range; public $range; // plays a role only if $type != F ... public function getFacet() ( $filter = ""; if ($this->type == "F") // entire line ( ... ) elseif ($this->type == "S") // start of line ( ... ) elseif ($this->type == "R") // range of values ​​( $ filter = $this->data ->groupBy(function($row) ( return floor($row[$this->key] / $this->range) * $this->range; )) ->select(function (ITraversable $data) ( return ["key" => $data->last()[$this->key], "count" => $data->count()]; )); return $filter; ) ) )

The main function of this class is to return a filtered dataset based on the original dataset and aspect properties. From the code it is clear that for various types accounts are used various ways grouping data. In the code above we showed what the code might look like if we group the data by a range of values ​​in increments specified in $range.

Setting aspects and displaying source data

Public function test2($app, $data) ( $facet = $this->getFacet($data); return $app["twig"]->render("demo2.html.twig", array("facet" = > $facet, "data" => $data)); private function getFacet($originalData) ( $facet = array(); $data = \Pinq\Traversable::from($originalData); // 3 creation examples different aspect objects, and return the aspects $filter1 = new \classFacet\Facet($data, "author", "F"); $filter2 = new \classFacet\Facet($data, "title", "S", 6) ; $filter3 = new \classFacet\Facet($data, "price", "R", 10); $facet[$filter1->key] = $filter1->getFacet(); ] = $filter2->getFacet(); $facet[$filter3->key] = $filter3->getFacet(); return $facet;

In the getFacet() method we do the following:

  • Convert the original data into a Pinq\Traversable object for further processing
  • We create three aspects. The 'author' aspect will group by the author field, and implement grouping by the entire row; aspect 'title' - by the title field with grouping by part of the line (by the first 6 characters); aspect 'price' - by the price field with grouping by range (in increments of 10)
  • Finally, we extract the aspects and return them to the test2 function so that they can be output to the template for display

Outputting aspects and filtered data

In most cases, filters will be displayed as a line, and will lead you to view the filtered result.

We've already created a route ("demo2/facet/(key)/(value)") to display faceted search results and filter links.

The route takes two parameters, depending on the key being filtered by and the value for that key. The test3 function that is bound to this route is shown below:

Public function test3($app, $originalData, $key, $value) ( ​​$data = \Pinq\Traversable::from($originalData); $facet = $this->getFacet($data); $filter = null; if ($key == "author") ( $filter = $data ->where(function($row) use ($value) ( ​​return $row["author"] == $value; )) ->orderByAscending( function($row) use ($key) ( return $row["price"]; )) ; ) elseif ($key == "price") ( ... ) else //$key==title ( .. . ) return $app["twig"]->render("demo2.html.twig", array("facet" => $facet, "data" => $filter) )

Basically, depending on the key, we apply filtering ( anonymous function in the where expression), according to the passed value, and we get the following set of filtered data. We can also set the order of data filtering.

Finally, we display the raw data (along with filters) in the template. This route uses the same pattern we used in "demo2".

Search Bar

    (% for k, v in facet %)
  • ((k|capitalize))
    • (% for vv in v %)
    • ((vv.count))((vv.key))
    • (%endfor%)
    (%endfor%)

We need to remember that the aspects generated by our application are nested arrays. At the first level, this is an array of all aspects, and, in our case, there are three of them (for author, title, price, respectively).

Each aspect has a key-value array, so we can iterate over it using normal methods.

Notice how we build the URLs for our links. We use both the outer loop key (k) and the inner loop keys (vv.key) as parameters for the route ("demo2/facet/(key)/(value)"). The size of the arrays (vv.count) is used for display in the template.

The first image shows the original data set, and the second image is filtered by price range from $0 to $10, and sorted by author.

Great, we were able to simulate faceted search in our application!

Before concluding this article, we need to take a final look at our example and determine what can be improved and what limitations we have.

Possible improvements

In general, this is a very basic example. We've just gone over the basic syntax and concepts and implemented them as a working example. As previously stated, we have several areas that could be improved for greater flexibility.

We need to implement “overlay” search criteria, since the current example limits us to the ability to apply search filtering only to the original data set; we cannot apply faceted search to an already filtered result. This is the biggest improvement I can imagine.

Restrictions

The facet search implemented in this article has serious limitations (which may also apply to other facet search implementations):

We fetch data from MySQL every time

This application uses the Silex framework. Like any single entry point framework like Silex, Symfony, Laravel, its index.php (or app.php) file is called every time a route is parsed and controller functions are executed.

If you look at the code in our index.php, you will notice that the following line of code:

$demo = new pinqDemo\Demo($app);

is called every time the application page is rendered, which means the following lines of code are executed each time:

Class Demo ( private $books = ""; public function __construct($app) ( $sql = "select * from book_book order by id"; $this->books = $app["db"]->fetchAll($sql ; )

Will it be better if we don't use a framework? Well, despite the fact that developing applications without frameworks is not a good idea, I can say that we will encounter the same problems: data (and state) are not saved between different HTTP requests. This is a fundamental characteristic of HTTP. This can be avoided by using caching mechanisms.

We saved some SQL queries using aspects. Instead of passing one select request to sample the data, and three group by queries with corresponding where conditions, we executed only one where query, and used PINQ to obtain the aggregated information.

Conclusion

In this part, we implemented the ability to facet search a collection of books. As I said, this is just a small example, which has room for improvement, and which has a number of limitations.

We released new book"Content marketing in in social networks: How to get into your subscribers’ heads and make them fall in love with your brand.”

Subscribe

Faceted navigation is a problem for all e-commerce sites. Excessive number of pages that are used for different options of the same element poses a threat to search efficiency. This can negatively impact SEO and user experience. Experts from the SEO Hacker blog explained what faceted navigation is and how to improve it.

Faceted Navigation: Definition

This type of navigation is usually found in the sidebars of websites. ecommerce, contains filters and facets - parameters that the user configures as desired. allows online store customers to search for the product they want using a combination of attributes that will filter products until users find what they need.

Facets and filters are different from each other. Here's the difference:

  • Facets are indexed categories. They help refine product listings and act as extensions of core categories. Facets are added unique value for every choice the user makes. Since facets are indexed, they must send relevant signals to the search engine, ensuring that the page contains all the important attributes.

  • Filters are used to sort and refine items within lists. They are necessary for users, but not for search engines. Filters are not indexed because they do not change the content of the page, but only sort it in a different order. This results in multiple URLs having duplicate content.

Potential problems

Each possible facet combination has its own unique URL. It can cause some problems from an SEO perspective. Here are the main ones:

  • Duplicate content.
  • Waste of budget on scanning.
  • Eliminate link differences.

As your site grows, so does the number of duplicate pages. Incoming links may go to various duplicate pages. This reduces the value of links and limits the ability of pages to rank.

The likelihood of keyword cannibalization also increases. Multiple pages try to rank for the same keywords, resulting in less consistent and lower rankings. This problem could be avoided if each keyword was targeted only to a single page.

Faceted Navigation Solutions

When choosing a solution for faceted navigation, consider your end goal: increasing the number of pages you index or reducing the number of pages you don't want indexed. Here are some solutions that may be useful for you:

AJAX

If you use AJAX, a new URL is not created when the user clicks on a facet or filter. Since there will be no unique URLs for every possible facet combination, the problem of duplicate content, keyword cannibalization, and wasted indexing costs is potentially eliminated.

AJAX can only be effective before the e-commerce site is launched. It is not used to solve problems of existing resources. This method also requires certain expenses on your part.

noindex tag

The noindex tag is used to exclude bots specific page from the index. This way it won't show up in the results. Google search. This helps reduce the amount of duplicate content that appears in the index and search results.

This won't solve the crawl budget problem because bots will still visit your page. It also doesn't help distribute the value of the links.

The rel=canonical attribute

With this attribute, you tell Google that you have one main preferred page to index and rank, and all other versions of content from that page are just duplicates that don't need to be indexed.

Sofia Ibragimova

Content Marketer

If the same page on your site can be reached from multiple URLs, search robots will treat each URL as separate page. Bots will decide that the content on your site is not unique, and this will negatively affect rankings and reduce your position in search results. To avoid this, specify the main canonical page by inserting the following sequence of characters into the HEAD block:

you can use canonical pages, to solve the problem of duplicate content, and the share link will be merged with your main page. But there is a chance that bots will still crawl duplicate pages, which is a waste of crawling budget.

Robots.txt

Blocking some pages from indexing allows you to achieve good results. It's simple, fast and reliable way. The easiest way to do this is to set a custom option to specify all the possible combinations of facets and filters that you want to block. Include it at the end of each URL you want to hide (http://full page address/robots.txt) or use the Robots meta tag in the HEAD area of ​​the page code.

When making changes to the URL, keep in mind that it takes 3-4 weeks for robots to notice and respond to these changes.

There are also certain problems. The value of links will be limited, and a blocked URL may be indexed due to the presence of external links.

Google Search Console

This is a great way to temporarily fix your problems while you work on creating a better and more user-friendly navigation system. You can use the console Google Search to tell the search engine how to crawl your site.

  • Sign in account console and select the “Crawl” section:

  • Indicate the impact each of your settings will have on the page and how you want Google to treat those pages.

Remember that this method only hides duplicate content from search robots Google. Pages will still appear in Bing and Yahoo.

How to Improve Faceted Navigation

Let's briefly consider all the methods that allow you to create the correct faceted navigation:

  • Using AJAX
  • Remove or hide links to categories or filter pages that are missing content.
  • Allow indexing of certain combinations of facets that have high volume of search traffic
  • Setting up a site hierarchy via bread crumbs in categories and subcategories.
  • Creating canonical (main) pages for duplicate content.
  • Consolidate indexing properties from component pages across the entire series using page markup with rel="next" and rel="prev" .

Conclusion

Each of the solutions mentioned has its own advantages and disadvantages. Universal solution does not exist, it all depends on the specifics of your business and the specific case. Optimized faceted navigation will allow your site to target a wider range of keywords. To avoid risk, make sure that the navigation not only meets the requirements of search robots, but also provides a good user experience.

Contents of articles:
Previous article:

Facets are defined constraints on the XDTO value type. One facet defines the constraint type and the constraint value. For example [Maximum inclusive - 5]. The XDTO value type can store several facets, but their types must be unique, that is, you cannot specify two facets [Maximum inclusive - 5] and [Maximum inclusive - 3].

In this article I want to show how you can use facets to check inputs and how to make a ws-operation parameter of a composite type.

Let's put simple task- upload items via a web service. We will upload the code, name, price and balance in the store. But the directory can be very large, so when calling the ws operation it should be possible to set the selection. We will select by item code.

Let's create new base and add new package XDTO, let's call it PackageXDTO, indicate the namespace.

Next, add a Value Type to the package as shown in the figure.


Let's create a "Code" type to organize selection by unique identifier nomenclature. Respectively, new type The "code" can be derived from the string type by adding a character limit. There should be 9 characters, that is, the number of characters in the nomenclature code. For the "Space characters" facet, set the value to "collapse", this will display an error if there are spaces in the code.


Now let’s create the “Price” Value Type in the same way. For it we will set the minimum to 0, maximum to 1,000,000.


To solve the problem, we will create a reference book of nomenclature; we will store the remainder in one of its details - this is not important.


Well, now let's start creating a web service. Let's call it "Remains" and add 3 parameters:
  • Code is the Code type that we previously created. By this parameter We will establish selection based on item code;
  • PriceFrom - type Price. This is the lower limit of the price of a product;
  • PriceBefore - type Price. This is the upper limit of the price of a product.

In order for the web service to display an array of products, each of which has a code, name, price and balance, let’s create an XDTO “Nomenclature” Object in which we specify the fields:

  • Code - type Code;
  • Name - string type;
  • Price - type Price;
  • Remainder - type int

And let's create an XDTO Object "Products", which will have one property "Nomenclature", but with the ability to display it as a list. For the type of the "Nomenclature" field of the "Products" object, specify the "Nomenclature" object. You can read more about how to pass an array through a web service.

Let's move on to the properties of the "Remains" operation. Let's specify the return value type - "Products" and open its module. Let's write the following code:

Function Remains (Code, PriceFrom, PriceTo)

Request = New Request;
Request . Text =
"CHOOSE
|Nomenclature.Code,
|Nomenclature.Name,
|Nomenclature.Price,
|Nomenclature.Remainder
|FROM
|Directory.Nomenclature AS Nomenclature
|WHERE
|1 = 1
|And 2 = 2" ;

IfValueFilled(Code )
AND Code<>"000000000" Then
Request . Text = StrReplace(Query . Text , " 1 = 1 " , " Nomenclature.Code = &Code" );
Request . SetParameter("Code", Code);
endIf;

If not PriceFrom = PriceToThen
Request . Text = StrReplace (Query . Text , "2 = 2" , "Nomenclature. Price BETWEEN &PriceFrom AND &PriceTo");
Request . SetParameter("PriceTo", PriceTo);
Request . SetParameter("PriceFrom", PriceFrom);
endIf;

Query Result= Request. Run();

SelectionDetailedRecords= Query Result. Choose ();
TypeXDTOProducts = FactoryXDTO . Type(, "Products");

Products = FactoryXDTO . Create(TypeXDTOProducts);
TypeXDTONomenclature= FactoryXDTO . Type ( "http://codenotes-1c.blogspot.ru/", "Nomenclature" );
ByeSelectionDetailedRecords. Next() Loop
Item = FactoryXDTO . Create ( TypeXDTONomenclature);
Nomenclature. Code =SelectionDetailedRecords. Code ;
Nomenclature. Name =SelectionDetailedRecords. Name ;
Nomenclature. Price =SelectionDetailedRecords. Price ;
Nomenclature. Remainder =SelectionDetailedRecords. Remainder ;
Goods . Nomenclature. Add(Nomenclature);
EndCycle ;

Return Goods ;

EndFunction

To demonstrate the operation of facets, it is not necessary to look at the code itself; now it is important to check that without coding the restrictions on input parameters they are present due to the facets.
To do this, we call the ws operation with various parameters:

  • without parameters. To be precise, we will indicate the code "000000000", the price from =0, the price to = 0. Because empty parameters are unacceptable.

Well, we will indicate this type for the “Code” parameter of the ws-operation “Remains”. After which we will be able to indicate the item code both as a number and as a string. Without modifying the code, we can only specify 0, which corresponds to an empty parameter value, and the selection will not be installed.

And that's cool. After all, if we can make restrictions on input data in code, without using facets, then composite type data cannot be done this way. This gives ample opportunities when developing web services.

Next, for reference, I will give the value of each property field. This description taken from the link: What does the concept of “Facet” refer to within the XDTO model? What is a facet? Many thanks to the authors!

  • Base type - a type that is used as a basis. Default is anyType. The allowed set of facets depends on this value.
  • Length - length facet. Contains the number of length units, with the length unit having different meanings for different types. For the "string" and "anyURI" types, the length contains the number of characters. For the "hexBinary" and "base64Binary" types, the length contains the number of bytes of binary data. For list-defined types, length contains the number of elements of the list;
  • MaxInclusive—the maximum facet that includes the boundary. Limits the value space of this type maximum value. Any value of this type is less than or equal to the specified value;
  • MaxLength—facet of maximum length. Contains maximum amount units of length, with the unit of length having different meanings for different types. For type "string" maximum length contains the maximum number of characters. For the "hexBinary" and "base64Binary" types, the maximum length contains the maximum number of bytes of binary data. For list-defined types, the maximum length contains the maximum number of list elements;
  • MaxExclusive is a maximum facet that does not include a boundary. Limits the value space of a given type to its maximum value. Any value of this type is less than the specified value;
  • MinInclusive - facet of the minimum that includes the boundary. Limits the value space of a given type minimum value. Any value of this type is greater than or equal to the specified value;
  • MinLength—minimal length facet. Contains a minimum number of length units, with a length unit having different meanings for different types. For the "string" type, the minimum length contains the minimum number of characters. For the "hexBinary" and "base64Binary" types, the minimum length contains the minimum number of bytes of binary data. For list-defined types, the minimum length contains the minimum number of list elements;
  • MinExclusive—a facet of a minimum that does not include a boundary. Limits the value space of a given type to a minimum value. Any value of this type is greater than the specified value;
  • Enumeration is an enumeration facet. Defines a set acceptable values of this type;
  • WhitespaceCharacters - facet whitespace characters. Can take one of three values:
    • Save - the string can contain any whitespace characters;
    • Replace - the line must not contain #x9 (tab), #xA (line feed) and #xD (carriage return). If they exist, they must be replaced with the #x20 (space) character;
    • Collapse—In addition to the requirements specified for the replace value, the string must not contain paired #x20 (space) characters or leading or trailing #x20 (space) characters;
  • DigitsTotal - facet of the total number of digits. Contains the total number of digits of a number ( whole part plus fractional part);
  • FractionalPartDigits - facet of the number of digits of the fractional part. Contains the number of digits of the fractional part of the number.

Tatyana Starkova

Lesson difficulty:

Level 3 - medium difficulty. It takes attention and a little thought.

Not available in editions:

No restrictions

IN modern world every second is precious. People try to spend less and less time on shopping. Slow product catalogs drive people away, the store loses customers and part of its profits. You, in turn, do not make such mistakes, make your online store attractive along with technology faceted Faceted - i.e. predefined search. Create faceted indexes Facet index- this is ready search set goods according to a certain property value. and significantly speed up not only the search for products, but also the work of the entire catalog as a whole.

Note: facet search available from module version 15.0.1 Information blocks.

A bit of "boring theory" about faceted search

The system generates facets in advance (all possible combinations of intersection of product properties) and when executing search query The result is immediately displayed - these ready-made facets. The faceted search mechanism is built into information blocks and integrated with component Component- This program code, designed in a visual shell, performing a certain function any module for displaying data in the Public part. We can insert this block of code into website pages without writing any code directly. Smart filter The component prepares a filter for selecting from the information block and displays a filter form for filtering elements. The component must be connected before the component for displaying catalog elements, otherwise the list of elements will not be filtered. The component is standard, included in the module distribution and contains three templates: .default, visual_horizontal And visual_vertical. (The last two templates are not supported, they remain to maintain compatibility.)

IN visual editor the component is located along the path Content > Catalog > Smart filter.

The component belongs to the Information blocks module.

Setting up faceted search involves creating faceted indexes and is done in just a few simple steps:


The created facet indexes are stored in the database, and in the table for product catalogs in the column State displayed Works:

Note: For the offering catalog, faceted indexes are created when indexes are created for the main catalog.

Faceted search improves the performance of the product catalog. To use it you need:

  1. create faceted indexes for a product catalog;
  2. Watch for notifications about the need to recreate indexes.

Organizes the so-called faceted search (faceted navigation) on the site. Its meaning is that search results can be refined using various characteristics material - author, type, term, date of creation, etc.

For example, if you have an online store selling electronic equipment, and the user enters the phrase audio player. On the results page, in addition to the results themselves, there will be facets:

- Chapter: audio equipment (54), computer technology (85)
- Brand: Apple (25), Samsung (68), iRiver (78)
- Availability in stock: yes (456), no (12)
- Price: 100-1000$ (45), 1000-10000$ (12)

etc. The number of products (nodes) that meet these characteristics will be indicated in brackets. By clicking on the links, the user will narrow the search results.


On the one hand, this is an alternative to expanded filters in Views, on the other, an alternative to the standard advanced search.

Installation

Facets section

In this section, you can specify which facets to use when searching. For example, allow you to select materials by taxonomy, date added, or author. The number of facets depends on the included modules.

Results page section

Display style- search results display style: Extracts means to display as in a normal search (highlighted text, author, date); Teasers means displaying teasers of materials using the appropriate node.tpl.php.

Use the Extracts display style selectively- If the option is checked, then the style Extracts will always be applied if a keyword is entered. If you do not check this option, you can use the module as a replacement for navigating taxonomy terms.

Current search section

Allows you to turn on the block Current search, which displays the search terms: