<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Russell Yapp</title>
    <description>The latest articles on DEV Community by Russell Yapp (@yappman).</description>
    <link>https://dev.to/yappman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4141609%2Ff630391d-9009-468a-9e5a-0b32a3d7d03e.jpg</url>
      <title>DEV Community: Russell Yapp</title>
      <link>https://dev.to/yappman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yappman"/>
    <language>en</language>
    <item>
      <title>How to get Tesco prices with an API in 2026</title>
      <dc:creator>Russell Yapp</dc:creator>
      <pubDate>Thu, 24 Sep 2026 16:37:53 +0000</pubDate>
      <link>https://dev.to/yappman/how-to-get-tesco-prices-with-an-api-in-2026-4n20</link>
      <guid>https://dev.to/yappman/how-to-get-tesco-prices-with-an-api-in-2026-4n20</guid>
      <description>&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;There is no public Tesco price API today. Tesco ran a developer programme in the past, but it was retired years ago and nothing has replaced it. If you need Tesco's shelf prices as structured data now, for a price-comparison tool, competitor monitoring, an inflation tracker or an AI shopping agent, the practical choices are the same as for any other major UK retailer: scrape tesco.com yourself, or use a maintained scraper that already exposes the data through an API.&lt;/p&gt;

&lt;p&gt;We built the second option. The UK Supermarket Price Scraper is an Apify Actor that reads Tesco, Sainsbury's, Asda and Aldi and returns one schema of structured product rows, callable through Apify's API, its client libraries, or the Apify console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doing it yourself is expensive to keep working
&lt;/h2&gt;

&lt;p&gt;Keeping a Tesco scraper working is not a one-off job. Tesco's site is protected by a bot-management service, so a plain HTTP client needs serious engineering to get a consistent response. Tesco's front end also changed its URL scheme and rendering during 2026, which breaks any scraper written against the old pages. Clubcard pricing adds a further wrinkle: single-item Clubcard prices and multibuy or meal-deal Clubcard offers look similar on the page, but only the single-item price is a number anywhere in the data, and the rest only ever appears as promotion text, so a naive parser silently drops it. Keeping up with all of that, in practice, means fixtures for every page type, a canary run that catches breakage before customers do, and someone on call for when the site next changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Actor returns for Tesco
&lt;/h2&gt;

&lt;p&gt;The Actor runs in three modes for Tesco: &lt;code&gt;search&lt;/code&gt; (keywords, run against Tesco's own search), &lt;code&gt;category&lt;/code&gt; (a tesco.com browse URL, copied from the site, for example &lt;code&gt;.../shop/en-GB/browse/&amp;lt;department&amp;gt;/&amp;lt;aisle&amp;gt;/...&lt;/code&gt; including the &lt;code&gt;/all&lt;/code&gt; form), and &lt;code&gt;product&lt;/code&gt; (a Tesco product page URL, for example &lt;code&gt;https://www.tesco.com/shop/en-GB/products/257265436&lt;/code&gt;). All three modes return the same row shape.&lt;/p&gt;

&lt;p&gt;For Tesco, each row carries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;retailer&lt;/code&gt;: always &lt;code&gt;tesco&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retailerProductId&lt;/code&gt;: Tesco's own product id, the &lt;code&gt;tpnc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ean&lt;/code&gt;: the barcode, present on every Tesco row we have seen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;brand&lt;/code&gt;, &lt;code&gt;packSize&lt;/code&gt;: as shown on the product page (own-label rows carry &lt;code&gt;Tesco&lt;/code&gt; as the brand)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;price&lt;/code&gt;: the current GBP shelf price&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loyaltyPrice&lt;/code&gt;: the single-item Clubcard price, when one is shown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;promotionText&lt;/code&gt;: multibuy and meal-deal Clubcard offers, as free text (Tesco's &lt;code&gt;wasPrice&lt;/code&gt; is always null)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inStock&lt;/code&gt;: Tesco's own online availability flag, national rather than per store&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;categoryPath&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;imageUrl&lt;/code&gt;, &lt;code&gt;rating&lt;/code&gt;, &lt;code&gt;scrapedAt&lt;/code&gt;: as elsewhere in the schema&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;A search for milk on Tesco, using Apify's Python client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yappman/uk-supermarket-price-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retailers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tesco&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;semi skimmed milk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loyaltyPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same call as a single curl request to Apify's run-and-wait endpoint, returning CSV directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/yappman~uk-supermarket-price-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN&amp;amp;format=csv"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode":"search","retailers":["tesco"],"queries":["semi skimmed milk"],"maxItems":50}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two real rows from that search, captured on 24 September 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;tpnc&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Loyalty price&lt;/th&gt;
&lt;th&gt;Promotion text&lt;/th&gt;
&lt;th&gt;EAN&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tesco British Semi Skimmed Milk 2.272L, 4 Pints&lt;/td&gt;
&lt;td&gt;254656543&lt;/td&gt;
&lt;td&gt;£1.65&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;5000436589457&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arla LactoFREE Semi Skimmed Milk Drink 2L&lt;/td&gt;
&lt;td&gt;309449739&lt;/td&gt;
&lt;td&gt;£3.15&lt;/td&gt;
&lt;td&gt;£2.50&lt;/td&gt;
&lt;td&gt;£2.50 Clubcard Price&lt;/td&gt;
&lt;td&gt;5000181042139&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All prices here are national online shelf prices as published on tesco.com on 24 September 2026. They will have changed by the time you read this; treat them as illustrative, not current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;Pricing is pay-per-event, and platform usage is included in it. Two charges apply: $0.005 once per run as a start fee, and $2.50 per 1,000 products returned ($0.0025 each) on the Free plan, with discounts on higher Apify plans. A run that searches 5 terms across all four retailers and returns 300 products costs 300 × $0.0025 + $0.005 = $0.755. You can cap spend with Apify's maximum-charge setting on the run, and with the &lt;code&gt;maxItems&lt;/code&gt; input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This is public, logged-out data, with the same limits as the rest of the Actor. Tesco prices and stock are both national figures; there is no store-level stock and no store selection for Tesco. Tesco's own search mixes in marketplace-seller and F&amp;amp;F clothing listings, and the Actor skips both, returning only grocery products. There is no basket, account or delivery-slot data, and no Clubcard account is used; everything comes from pages Tesco serves to any visitor, logged out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more things worth knowing
&lt;/h2&gt;

&lt;p&gt;If you want a look at real numbers before writing any code, there is a free daily dataset: a fixed basket of 212 staple products across Tesco, Sainsbury's, Asda and Aldi, fetched every morning and appended to one dataset since 24 September 2026, no account or token needed:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.apify.com/v2/datasets/ynAT9NPps2EdjMOJa/items?signature=MC4xNzkxNDcwNDMyMzg2LjEwV244Nll3OGZYYThIeTN6UkhENA&lt;/code&gt; (add &lt;code&gt;&amp;amp;format=csv&lt;/code&gt; for CSV).&lt;/p&gt;

&lt;p&gt;And if you want an alert rather than a one-off pull, a daily price-drop workflow for a shopping basket, built on this Actor, is available in n8n's template library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;p&gt;The UK Supermarket Price Scraper is on the Apify Store: &lt;a href="https://apify.com/yappman/uk-supermarket-price-scraper" rel="noopener noreferrer"&gt;https://apify.com/yappman/uk-supermarket-price-scraper&lt;/a&gt;. This Actor is not affiliated with, endorsed by, or partnered with Tesco; retailer names are used only to describe the public product data it returns.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>api</category>
      <category>data</category>
    </item>
  </channel>
</rss>
