Archive for March, 2011
Smarter Check Availability Page on Bestbuy.com with RDFa
Posted in: Uncategorized
Recently we’ve set off on a project journey something we’ve thoughtfully nicknamed “browse widening”. The basic aim of the browse widening project is, well, to make the site wider (groundbreaking, I know). Because this activity (in theory) should take a dev about 5 minutes to accomplish, we have decided to stick a couple of extra nuggets into the “requirements” of the project. As part of my ongoing passion to turn bestbuy.com into the most data-rich website on the planet, I am augmenting the site’s HTML with RDFa and vocabularies like GoodRelations, vCard, and Google’s review vocab, integrating rich product and store data directly into the front-end user experience to maximize the machine extractability and readability while preserving the visual user experience as it stands today.
In late February, we deployed the first iteration of RDFa-enhanced browse widening to the Check Availability page. In it’s current state, users who navigate through the bestbuy.com browse experience and check availability on products are taken to the check product availability page. If unrecognized or unauthenticated, the user is required to enter a zip code which shows the availability of said product at the closet store locations. If authenticated, the user is taken to the product availability page with their preferred stores availability status.
On the surface, this seems like your run-of-the-mill product availability page. But if you peel back a layer and view source on the HTML code, you’ll find a literal treasure trove of rich product and store data. Let’s take a peek at an example of a product that has varying store availability to show the data we’re making available to machines, and, most importantly, the RELATIONSHIPS we’re establishing between the product and where it “lives”.
First we’ll start with a code snippet highlighting the offering and product details:
<div class="csc-large-column csc-last-column" style="margin-top: 0; padding-top: 0;" xmlns:gr="http://purl.org/goodrelations/v1#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:vcard="http://www.w3.org/2006/vcard/ns#"> <div id="checkavail-prodlisting" typeof="gr:Offering" about="#Offering_0885909435340"> <span rel="gr:availableAtOrFrom" resource="#BestBuy_281"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_1000"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_1935"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_611"></span> <div rel="gr:includesObject"> <div typeof="gr:TypeAndQuantityNode" about="#TypeAndQuantityNode_0885909435340"> <span property="gr:amountOfThisGood" datatype="xsd:float" content="1.0"></span> <span property="gr:hasUnitOfMeasurement" datatype="xsd:string" content="C62"></span> <div rel="gr:typeOfGood"> <div typeof="gr:ProductOrServicesSomeInstancesPlaceholder" about="#ProductOrServicesSomeInstancesPlaceholder_0885909435340"> <div class="checkavail-product-image" rel="rdfs:seeAlso foaf:depiction"><a href="/site/olspage.jsp?skuId=9845572&type=product&id=1218183952317" rel="product" class="uri"><img src="http://images.bestbuy.com:80/BestBuy_US/images/products/9845/9845572_sc.jpg" alt="9845572 Front Detail" height="56.0" width="105.0"></a></div> <span class="checkavail-productlisting-link"><a href="/site/olspage.jsp?skuId=9845572&type=product&id=1218183952317" rel="product" class="uri">Apple® - iPod shuffle® 2GB* MP3 Player (4th Generation - Latest Model) - Orange</a><span property="rdfs:label" content="Apple® - iPod shuffle® 2GB* MP3 Player (4th Generation - Latest Model) - Orange"></span></span> <br /> <span class="checkavail-productlisting-model-sku"><strong>Model: </strong><span property="gr:hasMPN">MC752LL/A</span> | <strong>SKU:</strong><span property="gr:hasStockKeepingUnit">9845572</span><span property="gr:hasEAN_UCC-13" content="0885909435340"></span></span> </div> </div> <div class="clear"></div> </div> </div> </div>
To the average HTML layperson, this markup might not equate to much. But let me assure you, there is some very interesting and powerful data modeling at work here, delivered directly through the HTML markup. Let’s take a granular look at what is going on in the code by highlighting individual parts of this snippet. To start, we declare the namespaces of the vocabularies we’re using in our solution. For the Check Product Availability page we combine GoodRelations, FOAF and vCard vocabularies to model the complete solution.
<div class="csc-large-column csc-last-column" xmlns:gr="http://purl.org/goodrelations/v1#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:vcard="http://www.w3.org/2006/vcard/ns#">
Next, we identify a unique identifier for each product offer, using the gr:Offering class. My naming convention includes the UPC of the product with a zero prepended to it, which essentially is the standard format for EAN — a world-wide identifier used everywhere except the US (take that world — you, your EANs and crazy metric system can take a hike!). I’m a big fan of the UPC as product identifier — of course the “u” stands for “universal” which I dream could serve as a sort of “primary key” for product searching on the web.
<div id="checkavail-prodlisting" typeof="gr:Offering" about="#Offering_0885909435340">
The next section of code is where we start to see relationships forming. For this example, we see four span tags with gr:availableAtOrFrom and unique hash identifiers. The importance of this code as a relationship builder (the “cupid” of the GoodRelations solution?) will become more apparent later in the example. For now, let’s just recognize that it’s key to informing machines where the product offer can be procured, ie, where the product is in stock.
<span rel="gr:availableAtOrFrom" resource="#BestBuy_281"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_1000"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_1935"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_6"></span>
Our next sub-snippet encapsulates the product offered in the offering (via gr:includesObject), product type and quantity (via gr:TypeAndQuantityNode) and the description and attributes of the product (via gr:typeOfGood and it’s child methods). As you can see, we utilize GoodRelations as the “base” vocabulary, but also include FOAF and RDFS vocabs to provide a full solution. This is also where we dive deeper into product details, far beyond surface level attributes like price. foaf:depiction for a product image, gr:hasMPN for manufacturer product number, gr:hasStockKeepingUnit for SKU, and gr:hasEAN_UCC-13 for EAN (UPC) give machines access to important product attributes that are beneficial to allow machines to make sense of the massive amount of unstructured product data out on the web today.
<div rel="gr:includesObject"> <div typeof="gr:TypeAndQuantityNode" about="#TypeAndQuantityNode_0885909435340"> <span property="gr:amountOfThisGood" datatype="xsd:float" content="1.0"></span> <span property="gr:hasUnitOfMeasurement" datatype="xsd:string" content="C62"></span> <div rel="gr:typeOfGood"> <div typeof="gr:ProductOrServicesSomeInstancesPlaceholder" about="#ProductOrServicesSomeInstancesPlaceholder_0885909435340"> <div class="checkavail-product-image" rel="rdfs:seeAlso foaf:depiction"> <a href="/site/olspage.jsp?skuId=9845572&type=product&id=1218183952317" rel="product" class="uri"><img src="http://images.bestbuy.com:80/BestBuy_US/images/products/9845/9845572_sc.jpg" alt="9845572 Front Detail" height="56.0" width="105.0"></a></div> <span class="checkavail-productlisting-link"> <a href="/site/olspage.jsp?skuId=9845572&type=product&id=1218183952317" rel="product" class="uri">Apple® - iPod shuffle® 2GB* MP3 Player (4th Generation - Latest Model) - Orange</a><span property="rdfs:label" content="Apple® - iPod shuffle® 2GB* MP3 Player (4th Generation - Latest Model) - Orange"></span> </span> <br /> <span class="checkavail-productlisting-model-sku"> <strong>Model: </strong><span property="gr:hasMPN">MC752LL/A</span> | <strong>SKU:</strong><span property="gr:hasStockKeepingUnit">9845572</span> <span property="gr:hasEAN_UCC-13" content="0885909435340"></span> </span> </div> </div> </div>
Finally, we produce the list of stores with their location data and product availability status. Luckily for machines, we are coding these results using vCard RDFa — providing a rich consumable result (truncated to two store results for readability):
<tr>
<td class="checkavail-locationtable-locncolumn" typeof="gr:LocationOfSalesOrServiceProvisioning" about="#BestBuy_281">
<span class="location-name" property="rdfs:label">RICHFIELD MN</span>
<div class="street-address" rel="vcard:adr">
<span property="vcard:street-address">1000 WEST 78TH ST</span>,
<span property="vcard:locality">RICHFIELD</span>,
<span property="vcard:region">MN</span>
<span property="vcard:postal-code">55423</span>
</div>
<div class="addl">
<a href="Javascript:mapanddirection('281', 'cat12091')">Map & Directions</a>
</div>
</td>
<td class="checkavail-locationtable-availstatuscolumn">
<span class="checkavail-availablenow">
<span class="available-now">Available now</span>
</span>
<br />
Pick up 03/15/2011
</td>
<td class="checkavail-locationtable-btncolumn">
<input type="image" src="http://images.bestbuy.com:80/BestBuy_US/en_US/images/global/buttons/btn_addtocart_pdp.gif" border="0" alt="Add To Cart" name="281addtocart">
</td>
</tr>
<tr>
<td class="checkavail-locationtable-locncolumn" typeof="gr:LocationOfSalesOrServiceProvisioning" about="#BestBuy_5">
<span class="location-name">EDINA MN</span>
<div class="street-address" rel="vcard:adr">
<span property="vcard:street-address">3200 SOUTHDALE CIR</span>,
<span property="vcard:locality">EDINA</span>,
<span property="vcard:region">MN</span>
<span property="vcard:postal-code">55435</span>
</div>
<div class="addl">
<a href="Javascript:mapanddirection('5', 'cat12091')">Map & Directions</a>
</div>
</td>
<td class="checkavail-locationtable-availstatuscolumn">
<div>
<span class="shiptostore">
<span class="ship-to-store">Ship to store</span>
</span>
</div>
Usually <strong>ships to store</strong> in 3 to 5 days
</td>
<td class="checkavail-locationtable-btncolumn">
<input type="image" onclick="Javascript:checkCallType('5')" src="http://images.bestbuy.com:80/BestBuy_US/en_US/images/global/buttons/btn_addtocart_pdp.gif" border="0" alt="Add To Cart" name="5addtocart">
</td>
</tr>
Now that we have rich markup for our offering, the product and the store locations, it’s time to examine the magic that is inherent in this solution. Remember gr:availableAtOrFrom? It’s part of the key to establish RELATIONSHIPS, which is part of the sweet, sweet goodness of Semantic Web and Linked Data. In our HTML result, stores that have the product on hand are represented by a gr:availableAtOrFrom node which contains a resource attribute. This provides an inter-document pointer to the rich store location details (marked by the “about” attribute), essentially linking a product offer object to a store object and it’s location attributes. Stores where the product is not physically available do not return a gr:availableAtOrFrom. From this RDFa model, a machine can assert product availability – and extract rich location details about the stores where the product is in stock. A truncated code example may help here:
<!-- gr:availableAtOrFrom resources --> <span rel="gr:availableAtOrFrom" resource="#BestBuy_281"></span> <span rel="gr:availableAtOrFrom" resource="#BestBuy_1000"></span> [code]... <!-- html/data representation of the store, note "about" attribute --> <td class="checkavail-locationtable-locncolumn" typeof="gr:LocationOfSalesOrServiceProvisioning" about="#BestBuy_281"> <span class="location-name" property="rdfs:label">RICHFIELD MN</span> [etc]...
Planning Phase 2 — Matrix URIs
The examples you see here represent the first phase in a two-phased approach to deliver the smartest Check Product Availability page on the web today (at least for the next couple of months until everyone else catches on). As I mentioned in the intro, the code featured here is available through the human browse experience, so physical clicks of the mouse are necessary to resolve to the RDFa-enriched data endpoints. For phase 2, I would like to introduce a direct RDFa endpoint via Matrix URIs to complete the machine-readable portion of the solution.
If you’re unfamiliar with the concepts and structure of a Matrix URI, there is an interesting write-up from Tim Berners-Lee back in 1996 that outlines the design. Imagine this Matrix URI being the canonical URI to access very rich data about a product and it’s availability:

Using these methods enables the unleashing of important data on the web that provides machines with a greater visibility and understanding of the products, goods and services we offer — all without an API key. We’re not stopping at Product Availability — keep you’re eyes open for our next iteration where we will enable the smartest search results pages using RDFa and friendly “shop” URLs. Don’t miss it!
Search
You are currently browsing the Random Musings from Jay Myers weblog archives for March, 2011.
Pages
Archives
Categories
- AJAX (1)
- Business (29)
- CSS (2)
- Data Portability (6)
- example of the day (3)
- Foodstuffs (1)
- GoodRelations (3)
- hProduct (5)
- HTML (11)
- JavaScript (1)
- Linked Data (6)
- Linux (1)
- Microformats (14)
- OpenID (1)
- Rant (14)
- RDF (5)
- RDFa (7)
- Semantic Web (8)
- SEO (1)
- Theory (17)
- Ubuntu (1)
- Uncategorized (8)
- Web 2.0 (1)
- Web Standards (18)
- Working (27)
- Zompire Dracularius (1)
RSS
Contact