Monday, May 29, 2017

Google product feed - using templates to output XML. I'm 99% there - just need a bit of help...

Read article : Google product feed - using templates to output XML. I'm 99% there - just need a bit of help...

Hi Everyone,

http://showerandbathrooms.co.uk - as the url suggests, we sell stuff for showers and bathrooms... Baths, Showers, Shower trays etc etc...

We sell things which typically have variants. For example: A shower tray will come in at least 3 sizes. A Shower will come in at least 3 different KW. A Bath will come in at least 3 different finishes. The variants all share the same description.

So for our purposes, it made perfect sense to list one product, plus variants, like so: http://showerandbathrooms.co.uk/products/the-radiator-company-ancona-bench-seat

However, we ran into problems when using Shopify's google product feed facility. The feed listed the variants, but advised us that we needed to add a Google Product Type as well as a unique product identifier (one of MPN, UPC, EAN or JAN) for each item. However... Shopify's interface does not give you the ability to add a GPT or MPN to variants...

So shopify lists the products which error, but doesn't give you a way to fix the error....

So I had a think. We've already set the Google-product-type on the main product... (I used the following commands in irb to bulk update the products in each collection...).

products = ShopifyAPI::Product.find( :all, :params => { :collection_id => xxxxxx, :limit => 250 } )
products.size /// Counts the number of products to see if they match what shopify says is in the collection
products.each { |p| p.add_metafield(ShopifyAPI::Metafield.new({ :description => ' ', :namespace => 'google', :key => 'google_product_type', :value => 'Home & Garden > Pool & Spa > Spas', :value_type => 'string' })); p.save }

By sheer luck, we also used the products MPN as SKU... So each variant has the MPN (inputted as SKU), and the variants parent contains the Google-product-type.

(sorry if this gets confusing)

So I thought I could write a template, which iterates through all the products - and generates an XML google product feed. The theory was I could output the parent's google product type as the product type for the variants... and output each items SKU as MPN.... thus generating a clean feed.

The end result was this http://showerandbathrooms.co.uk/pages/google-product-feed [be careful when clicking - its massive....]

So this is a sample of the output for variants...

        

<item>
<title><![CDATA[Aquatora Stormseals]]> <![CDATA[2.3m Roll]]></title>
<link>http://showerandbathrooms.co.uk/products/aquatora-stormseal&lt;/link&gt;
<description><![CDATA[STORMSEAL is a flexible up-stand seal installed onto the perimeter side walls of the shower tray before final location against adjacent walls. When installed, the up-stand is sandwiched between the wall and tile with a silicone joint between the bottom edge of the tile and ledge giving you a water tight seal.The unique twin liner design eases the installation dramatically while an isolating membrane prevents plasticizer migration the results in the sealant yellowing.]]></description>
<g:id>128290552</g:id>
<g:condition>New</g:condition>
<g:price>36.00 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Storm_Seal_large.jpg?7570&lt;/g:image_link&gt;
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Aquatora</g:brand>
<g:mpn>SS23</g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Shower &gt; Shower Bases</g:google_product_category>
<g:product_type>Stormseal</g:product_type>
</item>




<!-- variant Item #2 -->


<item>
<title><![CDATA[Aquatora Stormseals]]> <![CDATA[2.8m Roll]]></title>
<link>http://showerandbathrooms.co.uk/products/aquatora-stormseal&lt;/link&gt;
<description><![CDATA[STORMSEAL is a flexible up-stand seal installed onto the perimeter side walls of the shower tray before final location against adjacent walls. When installed, the up-stand is sandwiched between the wall and tile with a silicone joint between the bottom edge of the tile and ledge giving you a water tight seal.The unique twin liner design eases the installation dramatically while an isolating membrane prevents plasticizer migration the results in the sealant yellowing.]]></description>
<g:id>128290662</g:id>
<g:condition>New</g:condition>
<g:price>42.00 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Storm_Seal_large.jpg?7570&lt;/g:image_link&gt;
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Aquatora</g:brand>
<g:mpn>SS28</g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Shower &gt; Shower Bases</g:google_product_category>
<g:product_type>Stormseal</g:product_type>
</item>

               
               
               
               
               
The template I wrote is as follows:
{% layout none %}<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
<channel>
<title>{{shop.name | escape }} Products</title>
<description>test</description>
<link>{{shop.url}}</link>
{% paginate collections.product-feed.products by 1000000 %}
{% for product in collections.product-feed.products %}
<!-- #{{ forloop.index }} -->
{% if product.variants.size > 1 %}
{% for variant in product.variants %}
<!-- variant Item #{{ forloop.index }} -->
{% if variant.sku == "" %}

<!-- do nothing - no SKU -->

{% else %}

<item>
<title><![CDATA[{{ product.title | strip_html | strip_newlines | escape | replace: 'amp;', 'and' | replace: '&#38;', 'and' | replace: "é", "e" | replace: "à ", "a" }}]]> <![CDATA[{{ variant.title | strip_html | strip_newlines | escape | replace: 'amp;', 'and' | replace: '&#38;', 'and' | replace: "é", "e" | replace: "à ", "a" }}]]></title>
<link>{{shop.url}}{{product.url}}</link>
<description><![CDATA[{{ product.description | strip_html | strip_newlines | replace: 'amp;', 'and' | replace: '&#38;', 'and' | replace: "..", ". " | replace: " ", " " | replace: "‘", "&#39;" | replace: "’", "&#39;" | replace: "&#8216;", "&#39;" | replace: "&#8217;", "&#39;" | replace: "&#8217;", "&#39;" | replace: "’", "&#39;" | replace: "“", "&#39;" | replace: "‘", "&#39;" | replace: "´", "&#39;" | replace: "“", "&#34;" | replace: "”", "&#34;" | replace: "&#8211;", "-" | replace: "–", "-" | replace: "—", "-" | replace: "–", "&mdash;" | replace: "—", "&mdash;" | replace: "%", "&#37;" | replace: "©", "&copy;" | replace: "®", "&reg;" | replace: "â„¢", "&trade;" | replace: "£", "&pound;" | replace: "ï¿­", "&#42;" | replace: "•", "&#42;" | replace: "”", "&#39;" | replace: "&#233;", "e" | replace: "é", "e" | replace: "à ", "a" | replace: "ó", "o" | replace: "ê", "e" | replace: "Ø", "O" | replace: "&#8482;", "" | replace: "&#174;", "" }}]]></description>
<g:id>{{variant.id}}</g:id>
<g:condition>New</g:condition>
<g:price>{{variant.price | money_without_currency}} GBP</g:price>
<g:availability>{% if product.available == true %}in stock{% else %} available for order {% endif %}</g:availability>
<g:image_link>{{product.featured_image | product_img_url: 'large'}}</g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>{{product.vendor | escape }}</g:brand>
<g:mpn>{{variant.sku}}</g:mpn>
<g:google_product_category>{{ product.metafields.google.google_product_type | escape }}</g:google_product_category>
<g:product_type>{{product.type | escape }}</g:product_type>
</item>

{% endif %}

{% endfor %}

{% else %}

<!-- parent Item #{{ forloop.index }} -->

{% if product.variants.first.sku == "" %}

<!-- do not output.. no SKU -->

{% else %}

<item>
<title><![CDATA[{{ product.title | strip_html | strip_newlines | escape | replace: 'amp;', 'and' | replace: '&#38;', 'and' | replace: "é", "e" | replace: "à ", "a" }}]]></title>
<link>{{shop.url}}{{product.url}}</link>
<description><![CDATA[{{ product.description | strip_html | strip_newlines | replace: 'amp;', 'and' | replace: '&#38;', 'and' | replace: "..", ". " | replace: " ", " " | replace: "‘", "&#39;" | replace: "’", "&#39;" | replace: "&#8216;", "&#39;" | replace: "&#8217;", "&#39;" | replace: "&#8217;", "&#39;" | replace: "’", "&#39;" | replace: "“", "&#39;" | replace: "‘", "&#39;" | replace: "´", "&#39;" | replace: "“", "&#34;" | replace: "”", "&#34;" | replace: "&#8211;", "-" | replace: "–", "-" | replace: "—", "-" | replace: "–", "&mdash;" | replace: "—", "&mdash;" | replace: "%", "&#37;" | replace: "©", "&copy;" | replace: "®", "&reg;" | replace: "â„¢", "&trade;" | replace: "£", "&pound;" | replace: "ï¿­", "&#42;" | replace: "•", "&#42;" | replace: "”", "&#39;" | replace: "&#233;", "e" | replace: "é", "e" | replace: "à ", "a" | replace: "ó", "o" | replace: "ê", "e" | replace: "Ø", "O" | replace: "&#8482;", "" | replace: "&#174;", "" }}]]></description>
<g:id>{{product.variants.first.id}}</g:id>
<g:condition>New</g:condition>
<g:price>{{ product.variants.first.price | money_without_currency}} GBP</g:price>
<g:availability>{% if product.variants.first.available == true %}in stock{% else %} available for order {% endif %}</g:availability>
<g:image_link>{{product.featured_image | product_img_url: 'large'}}</g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>{{product.vendor | escape }}</g:brand>
<g:mpn>{{ product.variants.first.sku}}</g:mpn>
<g:google_product_category>{{ product.metafields.google.google_product_type | escape }}</g:google_product_category>
<g:product_type>{{product.type | escape }}</g:product_type>
</item>

{% endif %}

{% endif %}


{% endfor %}
{% endpaginate %}
</channel>
</rss>

So, to get this working - I first created a smart collection, which contains ALL our products [set it to include anything with a price over 0].

I then iterated over this collection - pulling  out the required data. Using variant.sku as the mpn - and the parents google product type {{ product.metafields.google.google_product_type | escape }} as the variants google product type.

If a product has variants, then only the variants are outputted. If the product does not have variants, the then product is outputted. This prevents this weird double up, in which the product is also the first variant.

Lastly, I put in something to check to see if the variants has an sku {% if variant.sku == "" %} - because if it doesn't have an sku, then we don't want the variant to appear in our feed (I wrote a separate template to tell me which products don't have an sku - I then update the variant accordingly).

I also get the XML feed to punch out some comments - just so I know it's iterated over the item.

<!-- #693 -->


<!-- parent Item #693 -->



<!-- do not output.. no SKU -->


<!-- #694 -->


<!-- parent Item #694 -->



<!-- do not output.. no SKU -->

This all appears to be working as intended - but I've just noticed a problem... Despite the error checking for SKU, some variants are being outputted.... Note the <g:mpn> ... while other variants are not. Take #78 for example:
















<item>
<title><![CDATA[Carron Echelon 1700 x 750 Double Ended Bath]]> <![CDATA[5 mm bath with standard front panel]]></title>
<link>http://showerandbathrooms.co.uk/products/carron-echelon-1700-x-750-double-ended-bath&lt;/link>
<description><![CDATA[The Echelon is a smooth, minimalist design with no sharp angles and is ideally suited to being fully built in but can also be ordered with an L-shaped panel if required. The main feature of the bath is its combined filler/overflow, the oval design of which mirrors the lines of the bath itself.(price includes the overflow filler and click waste as pictured) Size:Length - 1700 mmWidth - 750 mmHeight - 540 mmDepth - 420 mmCapacity - 209 litresDelivery: 2-3 working days. (subject to delivery location)]]></description>
<g:id>200223912</g:id>
<g:condition>New</g:condition>
<g:price>650.39 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Top_1442318_large.jpg?7570&lt;/g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Carron</g:brand>
<g:mpn></g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Bathtubs</g:google_product_category>
<g:product_type>Rectangular Bath</g:product_type>
</item>







<item>
<title><![CDATA[Carron Echelon 1700 x 750 Double Ended Bath]]> <![CDATA[5 mm bath with standard front &and end panel]]></title>
<link>http://showerandbathrooms.co.uk/products/carron-echelon-1700-x-750-double-ended-bath&lt;/link>
<description><![CDATA[The Echelon is a smooth, minimalist design with no sharp angles and is ideally suited to being fully built in but can also be ordered with an L-shaped panel if required. The main feature of the bath is its combined filler/overflow, the oval design of which mirrors the lines of the bath itself.(price includes the overflow filler and click waste as pictured) Size:Length - 1700 mmWidth - 750 mmHeight - 540 mmDepth - 420 mmCapacity - 209 litresDelivery: 2-3 working days. (subject to delivery location)]]></description>
<g:id>200223922</g:id>
<g:condition>New</g:condition>
<g:price>711.11 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Top_1442318_large.jpg?7570&lt;/g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Carron</g:brand>
<g:mpn></g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Bathtubs</g:google_product_category>
<g:product_type>Rectangular Bath</g:product_type>
</item>







<item>
<title><![CDATA[Carron Echelon 1700 x 750 Double Ended Bath]]> <![CDATA[5 mm bath with Carronite front panel]]></title>
<link>http://showerandbathrooms.co.uk/products/carron-echelon-1700-x-750-double-ended-bath&lt;/link>
<description><![CDATA[The Echelon is a smooth, minimalist design with no sharp angles and is ideally suited to being fully built in but can also be ordered with an L-shaped panel if required. The main feature of the bath is its combined filler/overflow, the oval design of which mirrors the lines of the bath itself.(price includes the overflow filler and click waste as pictured) Size:Length - 1700 mmWidth - 750 mmHeight - 540 mmDepth - 420 mmCapacity - 209 litresDelivery: 2-3 working days. (subject to delivery location)]]></description>
<g:id>200223932</g:id>
<g:condition>New</g:condition>
<g:price>686.79 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Top_1442318_large.jpg?7570&lt;/g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Carron</g:brand>
<g:mpn></g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Bathtubs</g:google_product_category>
<g:product_type>Rectangular Bath</g:product_type>
</item>







<item>
<title><![CDATA[Carron Echelon 1700 x 750 Double Ended Bath]]> <![CDATA[5 mm bath with Carronite front &and end panel]]></title>
<link>http://showerandbathrooms.co.uk/products/carron-echelon-1700-x-750-double-ended-bath&lt;/link>
<description><![CDATA[The Echelon is a smooth, minimalist design with no sharp angles and is ideally suited to being fully built in but can also be ordered with an L-shaped panel if required. The main feature of the bath is its combined filler/overflow, the oval design of which mirrors the lines of the bath itself.(price includes the overflow filler and click waste as pictured) Size:Length - 1700 mmWidth - 750 mmHeight - 540 mmDepth - 420 mmCapacity - 209 litresDelivery: 2-3 working days. (subject to delivery location)]]></description>
<g:id>200223942</g:id>
<g:condition>New</g:condition>
<g:price>764.81 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Top_1442318_large.jpg?7570&lt;/g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Carron</g:brand>
<g:mpn></g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Bathtubs</g:google_product_category>
<g:product_type>Rectangular Bath</g:product_type>
</item>















<item>
<title><![CDATA[Carron Echelon 1700 x 750 Double Ended Bath]]> <![CDATA[Carronite bath with standard front panel]]></title>
<link>http://showerandbathrooms.co.uk/products/carron-echelon-1700-x-750-double-ended-bath&lt;/link>
<description><![CDATA[The Echelon is a smooth, minimalist design with no sharp angles and is ideally suited to being fully built in but can also be ordered with an L-shaped panel if required. The main feature of the bath is its combined filler/overflow, the oval design of which mirrors the lines of the bath itself.(price includes the overflow filler and click waste as pictured) Size:Length - 1700 mmWidth - 750 mmHeight - 540 mmDepth - 420 mmCapacity - 209 litresDelivery: 2-3 working days. (subject to delivery location)]]></description>
<g:id>200223962</g:id>
<g:condition>New</g:condition>
<g:price>895.46 GBP</g:price>
<g:availability>in stock</g:availability>
<g:image_link>http://cdn.shopify.com/s/files/1/0100/4752/products/Top_1442318_large.jpg?7570&lt;/g:image_link>
<g:shipping>
<g:country>UK</g:country>
<g:service>Standard Free Shipping</g:service>
<g:price>0 GBP</g:price>
</g:shipping>
<g:brand>Carron</g:brand>
<g:mpn></g:mpn>
<g:google_product_category>Hardware &gt; Plumbing &gt; Plumbing Fixtures &gt; Bathtubs</g:google_product_category>
<g:product_type>Rectangular Bath</g:product_type>
</item>




...so I'm really, really confused by this. I've gone and checked the sku field of the variants which are being outputted - and they are empty.... so why would some obey the rule, while others don't...

Very confused - and just need a little help to get over this hurdle.

Many thanks,
Rob

No comments:

Post a Comment