<?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: Omar Eldeeb</title>
    <description>The latest articles on DEV Community by Omar Eldeeb (@odeeb).</description>
    <link>https://dev.to/odeeb</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%2F3961363%2F988e15d2-f7cd-489d-8bb8-4e3cfae6e4e2.png</url>
      <title>DEV Community: Omar Eldeeb</title>
      <link>https://dev.to/odeeb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/odeeb"/>
    <language>en</language>
    <item>
      <title>White Friday &amp; 11.11 Tracker: Noon and Amazon UAE Prices</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:49:00 +0000</pubDate>
      <link>https://dev.to/odeeb/white-friday-1111-tracker-noon-and-amazon-uae-prices-dbe</link>
      <guid>https://dev.to/odeeb/white-friday-1111-tracker-noon-and-amazon-uae-prices-dbe</guid>
      <description>&lt;p&gt;The Gulf's November sales are 11.11 (Wednesday 11 November 2026; noon's 2025 sale ran 7-12 November) and the White Friday / Yellow Friday period around Friday 27 November (noon's Yellow Friday ran 20-30 November in 2025). To track them, start weekly price records on noon UAE and Amazon UAE / Saudi Arabia in mid-October, by product URL, and check every 6 hours during each sale, in AED or SAR.&lt;/p&gt;

&lt;p&gt;This is the Gulf page of the cluster. It covers the sale names and windows, what can actually be scraped on noon and Amazon in the UAE and Saudi Arabia (with results from live runs on 20 October 2026), the URL details that decide the currency and language, and costs. The global calendar is in the &lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;retail sales calendar&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three names, one season
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sale&lt;/th&gt;
&lt;th&gt;Who runs it&lt;/th&gt;
&lt;th&gt;2025 window&lt;/th&gt;
&lt;th&gt;2026 anchor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;11.11 (Singles' Day)&lt;/td&gt;
&lt;td&gt;noon, Amazon UAE and other UAE retailers&lt;/td&gt;
&lt;td&gt;noon: evening of 7 Nov to 12 Nov. Amazon UAE: until 12 Nov&lt;/td&gt;
&lt;td&gt;Wed 11 Nov 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yellow Friday&lt;/td&gt;
&lt;td&gt;noon (named after its yellow logo)&lt;/td&gt;
&lt;td&gt;20-30 Nov&lt;/td&gt;
&lt;td&gt;around Fri 27 Nov 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;White Friday&lt;/td&gt;
&lt;td&gt;Gulf name for Black Friday, used by Amazon UAE and other brands&lt;/td&gt;
&lt;td&gt;Amazon UAE reported as 22-29 Nov (Property Finder; we found no Amazon announcement)&lt;/td&gt;
&lt;td&gt;around Fri 27 Nov 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Black Friday&lt;/td&gt;
&lt;td&gt;many UAE retailers use the US name&lt;/td&gt;
&lt;td&gt;Black Friday was 28 Nov 2025&lt;/td&gt;
&lt;td&gt;Fri 27 Nov 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dubai Shopping Festival&lt;/td&gt;
&lt;td&gt;Dubai Festivals and Retail Establishment&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;4 Dec 2026 - 17 Jan 2027 (announced)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 2026 retailer windows weren't announced on 20 October 2026. The 2025 pattern suggests the 11.11 window opens a few days before the 11th, and the Friday sales run about a week either side of the Friday. Start the baseline before either could begin: mid-October.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can be scraped (tested 20 October 2026)
&lt;/h2&gt;

&lt;p&gt;All examples use the &lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Product Scraper Pro&lt;/a&gt; actor. Its README rates the Gulf sites below as &lt;strong&gt;best-effort&lt;/strong&gt;, except noon's Saudi storefront, which it lists as &lt;strong&gt;unsupported&lt;/strong&gt;. Our own runs on 20 October 2026 gave these results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Result on 2026-10-20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;noon UAE (&lt;code&gt;noon.com/uae-en/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;browser for the first page, then residential with the browser's cookies&lt;/td&gt;
&lt;td&gt;Delivered: 3 of 3 products in two separate runs, priced in AED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;noon Saudi Arabia (&lt;code&gt;noon.com/saudi-en/&lt;/code&gt;, &lt;code&gt;/saudi-ar/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;none: not fetched&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unsupported.&lt;/strong&gt; Akamai answered "Access Denied" on every route in the actor's October 2026 tests, including Saudi residential connections and the browser. Each input gets a status row with &lt;code&gt;outcome: "unsupported"&lt;/code&gt; and costs nothing beyond the run's start fee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon UAE (&lt;code&gt;amazon.ae&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;residential&lt;/td&gt;
&lt;td&gt;Delivered, AED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Saudi Arabia (&lt;code&gt;amazon.sa&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;datacenter&lt;/td&gt;
&lt;td&gt;Delivered, SAR, from Arabic and English product pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IKEA UAE / Saudi Arabia&lt;/td&gt;
&lt;td&gt;residential&lt;/td&gt;
&lt;td&gt;Not tested today; best-effort in the README, first page of listings only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So for Saudi prices, use Amazon Saudi Arabia. The actor doesn't try noon's Saudi pages at all, so they cost no traffic or renders; the status row explains why. If a run contains only noon Saudi inputs, it ends as FAILED with that reason. Other Gulf retailers (Namshi, Sharaf DG, Carrefour, Lulu, Jarir, Extra) aren't in the supported-sites matrix, so we haven't tested them; any that run on Shopify or WooCommerce would fall under the actor's generic support.&lt;/p&gt;

&lt;p&gt;noon is protected by Akamai. The actor handles it by rendering a page in a real browser to earn the session cookies, then fetching the rest over residential connections with those cookies. Few pages need the browser: a run of 60 noon UAE product URLs on 18 October 2026 delivered all 60 with 2 browser renders and 8 billed MB of residential traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  URLs: market, currency and language
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;noon puts the market in the path.&lt;/strong&gt; &lt;code&gt;/uae-en/&lt;/code&gt; is the UAE in English, &lt;code&gt;/egypt-en/&lt;/code&gt; Egypt, &lt;code&gt;/saudi-en/&lt;/code&gt; Saudi Arabia (unsupported, see above). The actor reads the path to set the currency (AED, EGP). The same product ID under &lt;code&gt;/uae-en/&lt;/code&gt; and &lt;code&gt;/egypt-en/&lt;/code&gt; comes back as two separate rows, one per market, so you can track both. The actor's &lt;code&gt;keyword&lt;/code&gt; + &lt;code&gt;marketplaces: ["www.noon.com"]&lt;/code&gt; search uses the UAE English site; for Egypt, paste that market's search URL into &lt;code&gt;listingUrls&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Saudi Arabia defaults to Arabic, and that's fine.&lt;/strong&gt; The default Arabic product page &lt;code&gt;https://www.amazon.sa/dp/B0DGHYPFGK&lt;/code&gt; returned on 20 October 2026 an Arabic title, &lt;code&gt;price: 449&lt;/code&gt;, &lt;code&gt;originalPrice: 599&lt;/code&gt;, &lt;code&gt;currency: "SAR"&lt;/code&gt;, &lt;code&gt;inStock: true&lt;/code&gt; and &lt;code&gt;brandName: "Apple"&lt;/code&gt;. The actor reads Arabic availability text (Arabic-Indic digits included) and sets &lt;code&gt;stockQty&lt;/code&gt; only from an explicit low-stock phrase on the new-item offer. If you want English titles, use the &lt;code&gt;/-/en/&lt;/code&gt; path, like &lt;code&gt;https://www.amazon.sa/-/en/dp/B0DGHYPFGK&lt;/code&gt;; both forms work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No breadcrumbs on Amazon UAE or Saudi Arabia.&lt;/strong&gt; &lt;code&gt;breadcrumbs&lt;/code&gt; is an empty list on &lt;code&gt;amazon.ae&lt;/code&gt; and &lt;code&gt;amazon.sa&lt;/code&gt; rows, because Amazon doesn't put a category trail on those pages. Group those products by your own category list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One ASIN, two storefronts.&lt;/strong&gt; The same ASIN often exists on &lt;code&gt;amazon.ae&lt;/code&gt; and &lt;code&gt;amazon.sa&lt;/code&gt;, which makes cross-market comparison easy (see below).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Watch product URLs, not search pages
&lt;/h2&gt;

&lt;p&gt;Search results on noon reshuffle quickly. Two runs of the same noon UAE search for "samsung", 2 hours apart on 17 October 2026, returned 30 products each, and only 11 products appeared in both. Of those 11, one price changed (AED 1,168 to AED 1,167.55). If you track a search page, most of the "changes" you see are products entering and leaving the page. Use search pages to find candidates, then track the product URLs you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare noon and Amazon UAE
&lt;/h2&gt;

&lt;p&gt;This pulls one noon UAE search and the same keyword on Amazon UAE, and prints each price with its discount from the store's own was-price (&lt;code&gt;pip install apify-client&lt;/code&gt;).&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;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;constructive_calm/ecommerce-product-scraper-pro&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="c1"&gt;# Noon: paste the search URL of the market you want (uae-en here)
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;listingUrls&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;url&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;https://www.noon.com/uae-en/search/?q=airpods%204&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="c1"&gt;# Amazon.ae: keyword + marketplace
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyword&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;airpods 4&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;marketplaces&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;www.amazon.ae&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;followPagination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxItemsPerStartUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxProductResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;max_total_charge_usd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.25&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&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;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;was&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;originalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% off was-price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no was-price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&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="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&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="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&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;Real output, 2026-10-20:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;noon         554.00 AED  30% off was-price      AirPods 4 ANC Wireless Earbuds, Bluetooth Headph
amazon-ae    549.00 AED  27% off was-price      Apple AirPods 4 Wireless Earbuds, Bluetooth Head
amazon-ae    549.00 AED  no was-price           Apple AirPods 5 Wireless Earbuds
noon         427.00 AED  22% off was-price      AirPods 4 Wireless Earbuds, Bluetooth Headphones
noon         759.00 AED  20% off was-price      AirPods Pro 3 Wireless Earbuds, Bluetooth Headph
amazon-ae    429.00 AED  22% off was-price      Apple AirPods 4 Wireless Earbuds, Bluetooth Head
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five weeks before White Friday, both stores already show 20-30% "off" their was-prices. That is exactly why the November discount has to be measured against your own October baseline, not against the was-price. The &lt;a href="https://datatooly.xyz/guides/spot-fake-black-friday-discounts/" rel="noopener noreferrer"&gt;fake discounts guide&lt;/a&gt; shows the baseline method.&lt;/p&gt;

&lt;p&gt;noon rows also carry &lt;code&gt;seller&lt;/code&gt;. On marketplaces with many sellers, the cheapest offer can come from a seller you wouldn't buy from, so keep the seller column in your comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the UAE and Saudi Arabia
&lt;/h2&gt;

&lt;p&gt;Both currencies are pegged to the US dollar (AED 3.6725 and SAR 3.75 per dollar), so a fixed conversion is accurate enough for a price comparison:&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="c1"&gt;# Same ASIN on amazon.ae and amazon.sa, converted at the official USD pegs.
&lt;/span&gt;&lt;span class="n"&gt;PEG&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;AED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.6725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# units per 1 USD
&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# from a scraper run on 2026-10-20 (ASIN B0DGHYPFGK)
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&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;amazon-ae&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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;originalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;549&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&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;AED&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;site&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;amazon-sa&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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;449&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;originalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;599&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&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;SAR&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PEG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;originalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;originalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&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="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; USD  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% below was-price)&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amazon-ae    429 AED = 116.81 USD  (22% below was-price)
amazon-sa    449 SAR = 119.73 USD  (25% below was-price)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A schedule for the Gulf sales
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Window (2026)&lt;/th&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Cron (UTC)&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~15 Oct - 5 Nov&lt;/td&gt;
&lt;td&gt;weekly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 4 * * 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Baseline. 04:00 UTC is 08:00 in the UAE, 07:00 in Saudi Arabia.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~6 Nov - 13 Nov (11.11)&lt;/td&gt;
&lt;td&gt;every 6 hours&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 2,8,14,20 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;06:00, 12:00, 18:00 and 00:00 UAE time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14 Nov - 18 Nov&lt;/td&gt;
&lt;td&gt;daily&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 4 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gap between the two sales.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~19 Nov - 1 Dec (Yellow / White Friday)&lt;/td&gt;
&lt;td&gt;every 6 hours&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 2,8,14,20 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 Dec - 17 Jan (Dubai Shopping Festival)&lt;/td&gt;
&lt;td&gt;daily&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 4 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use monitor mode (&lt;code&gt;monitorMode: true&lt;/code&gt;) with the same product URL list throughout: the actor's monitor state is keyed by the exact set of input URLs, and each row then carries &lt;code&gt;changeType&lt;/code&gt;, &lt;code&gt;previousPrice&lt;/code&gt; and &lt;code&gt;priceChangePct&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Pay per event, from the actor's README. FREE tier: $0.0048 per product row, $0.0004 per listing page, $0.008 per browser-rendered page, $0.018 per residential MB (billed at 89% of measured traffic, only for items that delivered), $0.005 per run start. GOLD: $0.0008, $0.00021, $0.006, $0.014.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The noon + Amazon UAE run above&lt;/strong&gt; was charged 6 products, 2 listing pages, 1 browser render and 2 residential MB. On FREE: $0.005 + 6 × $0.0048 + 2 × $0.0004 + $0.008 + 2 × $0.018 = &lt;strong&gt;$0.0786&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 60 noon product URLs&lt;/strong&gt; mentioned above (60 products, 2 browser renders, 8 MB), on FREE: $0.005 + 60 × $0.0048 + 2 × $0.008 + 8 × $0.018 = &lt;strong&gt;$0.453&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,000 noon products&lt;/strong&gt;, using the README's measured 30-200 renders and 89-356 billed MB per 1,000: on FREE, $4.80 + $0.24-$1.60 + $1.60-$6.41 + $0.005 = &lt;strong&gt;about $6.65-$12.81&lt;/strong&gt;. The README puts the same run at $2.23-$6.99 on GOLD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Saudi Arabia&lt;/strong&gt; went through datacenter connections in our run, so it had no traffic fee: 3 products = $0.005 + 3 × $0.0048 = $0.0194 on FREE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For runs with more than about 50 noon URLs, the README recommends 2 GB of memory (one extra $0.005 start event). Apify's free plan gives $5 of usage every month, no credit card required; the actor has no free trial of its own. The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker query builder&lt;/a&gt; builds the input without code and shows a fixed example of the output shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/ramadan-eid-sales-price-tracking/" rel="noopener noreferrer"&gt;Ramadan and Eid sale price tracking&lt;/a&gt;, the Gulf's other big season&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;Retail sales calendar 2026-2027&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/spot-fake-black-friday-discounts/" rel="noopener noreferrer"&gt;How to spot fake Black Friday discounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/amazon-price-scraper-asin/" rel="noopener noreferrer"&gt;Amazon price scraper by ASIN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the Apify actor and this site. The comparison scripts work on prices from any source.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://www.khaleejtimes.com/uae/1111-sale-dh1-deals-90-discounts-gifts" rel="noopener noreferrer"&gt;Khaleej Times: 11.11 sales in the UAE (7 November 2025)&lt;/a&gt;, &lt;a href="https://www.khaleejtimes.com/uae/black-white-yellow-green-friday-sales-explainer" rel="noopener noreferrer"&gt;Khaleej Times: Black, White, Yellow and Green Friday explained (21 November 2025)&lt;/a&gt;, &lt;a href="https://www.propertyfinder.ae/blog/?p=188198" rel="noopener noreferrer"&gt;Property Finder: UAE White Friday 2025 deals&lt;/a&gt;, &lt;a href="https://tradearabia.com/News/487977/Dubai-Shopping-Festival-returns-for-32nd-edition-from-December-4-to-January-17" rel="noopener noreferrer"&gt;TradeArabia: Dubai Shopping Festival 2026-27&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/United_Arab_Emirates_dirham" rel="noopener noreferrer"&gt;Wikipedia: UAE dirham&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Saudi_riyal" rel="noopener noreferrer"&gt;Saudi riyal&lt;/a&gt; (dollar pegs), the actor's README (supported sites, pricing, memory), and live actor runs on 2026-10-17 and 2026-10-20 (&lt;code&gt;amazon.sa&lt;/code&gt; Arabic page and noon Saudi status row: run &lt;code&gt;rm7jhN0AiKNMb6V6K&lt;/code&gt;, build 0.1.16).&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>python</category>
      <category>webscraping</category>
      <category>uae</category>
    </item>
    <item>
      <title>What Slippage Should I Set to Avoid Sandwich Attacks?</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:48:38 +0000</pubDate>
      <link>https://dev.to/odeeb/what-slippage-should-i-set-to-avoid-sandwich-attacks-16dk</link>
      <guid>https://dev.to/odeeb/what-slippage-should-i-set-to-avoid-sandwich-attacks-16dk</guid>
      <description>&lt;p&gt;Set the lowest slippage tolerance that still lets your swap fill under normal price movement. As a starting point, that's about 0.1–0.5% on deep major pairs and about 0.5–1% on mid-size tokens. Go higher only on thin, fast-moving pools, and there an MEV-protected route matters more than the exact number. Your tolerance is the most a sandwich bot can push your price: 5% on a $1,000 swap lets your fill land up to $50 below the quote. Check whether the pool is actually being sandwiched before you choose.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is an explainer about trade execution, not financial advice. It doesn't tell you what to buy or sell.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What slippage tolerance actually does
&lt;/h2&gt;

&lt;p&gt;When you request a swap, the app quotes an output amount. That quote already includes your &lt;strong&gt;price impact&lt;/strong&gt;, which is how far your own trade moves the pool's price. Slippage tolerance is the extra room you allow on top of the quote, to cover price moves between the moment you sign and the moment your transaction lands in a block. The app turns it into a minimum output:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minimum output = quoted output × (1 − slippage tolerance)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If the price moves further than that before your swap executes, the swap fails instead of filling. On a normal public route you still pay the network fee for the failed transaction. Uniswap says its web app suggests a tolerance "usually between 0.1% and 5%", based on live gas fees and your swap size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why high slippage invites sandwich attacks
&lt;/h2&gt;

&lt;p&gt;A sandwich bot sees your pending swap and buys just before it, which pushes the price up. Your swap then fills at that worse price, and the bot sells just after you. The only thing that limits how far the bot can push you is your minimum output. Set it loosely, and you've told the bot exactly how much room it has.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Swap size&lt;/th&gt;
&lt;th&gt;Tolerance&lt;/th&gt;
&lt;th&gt;Most your fill can land below the quote&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$1,000&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$1,000&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$1,000&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;$50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$1,000&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is plain arithmetic. It shows a ceiling, not the bot's profit: the bot also pays its own price impact, pool fees and gas or tips, so it only attacks when what it can take beats those costs. That's why academic work on sandwiches describes the trade-off as two-sided. A tolerance that's too small makes trades fail, and one that's too large lets bots profit. Larger trades and looser settings make you a more attractive target. Meme-coin traders get hit hard because they tend to set high slippage on thin, volatile pools.&lt;/p&gt;

&lt;h2&gt;
  
  
  A rule-of-thumb table
&lt;/h2&gt;

&lt;p&gt;These are my starting points, not guarantees. They assume you've already checked your price impact on the swap screen, and the "MEV risk" columns come from the pool checks in the next section.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pool and trade&lt;/th&gt;
&lt;th&gt;Low MEV risk, no sandwiches seen&lt;/th&gt;
&lt;th&gt;Medium MEV risk, or a few sandwiches&lt;/th&gt;
&lt;th&gt;High MEV risk, or regular sandwiches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stablecoin to stablecoin, deep pool&lt;/td&gt;
&lt;td&gt;0.05–0.1%&lt;/td&gt;
&lt;td&gt;0.05–0.1%, protected route&lt;/td&gt;
&lt;td&gt;0.05–0.1%, protected route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major pair (ETH/USDC, SOL/USDC), price impact under 0.1%&lt;/td&gt;
&lt;td&gt;0.1–0.5%&lt;/td&gt;
&lt;td&gt;0.1–0.3%, protected route&lt;/td&gt;
&lt;td&gt;0.1–0.3%, protected route, split large orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid-size token, price impact under 1%&lt;/td&gt;
&lt;td&gt;0.5–1%&lt;/td&gt;
&lt;td&gt;0.5%, protected route&lt;/td&gt;
&lt;td&gt;Lowest that fills, protected route, trade smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thin or new pool, price impact over 1%&lt;/td&gt;
&lt;td&gt;1–3%&lt;/td&gt;
&lt;td&gt;Protected route, trade smaller&lt;/td&gt;
&lt;td&gt;Rethink the size; don't fix failed fills by raising slippage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read across a row, not down a column. The same token can sit in a calm pool on one DEX and a heavily sandwiched pool on another, and the tolerance you pick should follow the pool you're actually routing through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the pool's MEV numbers to pick a setting
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/constructive_calm/mev-pair-scanner?fpr=v77kxu" rel="noopener noreferrer"&gt;MEV Risk Scanner&lt;/a&gt; reads Codex's per-pool data for the last 24 hours. Four of its fields decide which column of the table you're in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sandwich_rate_24h&lt;/code&gt; and &lt;code&gt;estimated_sandwich_attacks_24h&lt;/code&gt;: the share of the pool's transactions that were sandwiched, and that rate multiplied by the day's transaction count. Anything above zero means bots are active in that pool right now.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mev_risk_level&lt;/code&gt;: the most common hourly label. Codex labels each hour &lt;code&gt;low&lt;/code&gt; when builder tips are under 3% of fees, &lt;code&gt;medium&lt;/code&gt; at 3–30%, and &lt;code&gt;high&lt;/code&gt; above 30%. It measures how hard bots of every kind compete in the pool, not sandwiches alone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data_availability&lt;/code&gt;: if the sandwich or fee fields weren't populated, treat the pool as unknown, which means the middle column.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;safer_trading_windows_utc&lt;/code&gt;: the hours in the last day that were labelled &lt;code&gt;low&lt;/code&gt;. Use it as a hint about busy hours, not a schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two example rows, abbreviated and rounded, from a test run on 1 October 2026 (not live data):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token0_symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WETH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token1_symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USDt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"exchange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UniswapV3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"liquidity_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;56961580&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"transactions_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;708&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mev_risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mev_to_total_fees_ratio_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.034&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sandwich_rate_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_sandwich_attacks_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token0_symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USDC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token1_symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WETH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"exchange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UniswapV3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"liquidity_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3417730&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"transactions_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22633&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mev_risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mev_to_total_fees_ratio_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.305&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sandwich_rate_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.00063&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_sandwich_attacks_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both pools trade ETH against a dollar stablecoin on Ethereum. The first had no detected sandwiches and a low builder-tip share, so it falls in the left-hand column. The second had about 14 estimated sandwiches in a day and 30% of its fees going to builders, so it's the right-hand column: a tight setting and a protected route. A router may split your trade across both pools, so if you're choosing a pool by hand, choose with this in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the chart before you trade
&lt;/h2&gt;

&lt;p&gt;The numbers above cover the last 24 hours. Before you sign, look at the live chart too. Open the token on &lt;a href="https://www.defined.fi/tokens" rel="noopener noreferrer"&gt;Defined&lt;/a&gt;, switch the chart to 1-minute candles, and check two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;How much the price moves in a minute.&lt;/strong&gt; If recent 1-minute candles swing by 2%, a 0.3% tolerance will fail often. Wait, or trade smaller, rather than opening the gate wide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether the trade list shows sandwiches.&lt;/strong&gt; Look for the same wallet buying just before someone else and selling just after, at the same timestamp. &lt;a href="https://datatooly.xyz/guides/tell-if-token-pool-is-getting-sandwiched/" rel="noopener noreferrer"&gt;How to tell if a token's pool is getting sandwiched&lt;/a&gt; walks through it step by step.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When your swap keeps failing at low slippage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raise it in small steps,&lt;/strong&gt; for example from 0.5% to 0.8%, not straight to 10%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a protected route.&lt;/strong&gt; &lt;a href="https://docs.flashbots.net/flashbots-protect/overview" rel="noopener noreferrer"&gt;Flashbots Protect&lt;/a&gt; on Ethereum keeps your transaction out of the public mempool, and it doesn't include transactions that would revert, so a too-tight setting doesn't cost you a fee. On Solana, Jupiter offers dynamic slippage and an MEV-protect mode that routes through Jito block engines, for slightly higher fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade smaller,&lt;/strong&gt; so your price impact and the room you leave both shrink.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taxed tokens are a special case.&lt;/strong&gt; If a token charges a transfer tax, your tolerance has to cover the tax or the swap fails, and a bot gets that same room. Be extra careful with size on those.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the MEV Risk Scanner.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is 0.5% slippage safe from sandwich attacks?
&lt;/h3&gt;

&lt;p&gt;On a deep major pair with small price impact, 0.5% leaves a bot little room, and an MEV-protected route closes most of the rest. No setting is completely safe, though. On a thin pool with regular sandwiches, even 0.5% of a large trade can be worth attacking.&lt;/p&gt;

&lt;h3&gt;
  
  
  What slippage should I use for meme coins?
&lt;/h3&gt;

&lt;p&gt;Use the lowest setting that fills, usually higher than for major pairs because these prices move fast. Check the pool first. If it shows regular sandwiches, use a protected route and trade smaller instead of raising slippage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my swap fail when I set low slippage?
&lt;/h3&gt;

&lt;p&gt;The price moved past your minimum output before your transaction landed, either from normal volatility or from other trades in the same block. Raise the tolerance a little at a time, or use a route that doesn't charge you for reverted transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does auto slippage protect me from MEV?
&lt;/h3&gt;

&lt;p&gt;Auto or dynamic slippage sets the tolerance from the current market for each trade, which usually keeps it tighter than a fixed high value. That reduces the room a bot gets, but it doesn't hide your transaction. A protected route does that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use high slippage if I send through a private RPC?
&lt;/h3&gt;

&lt;p&gt;A private RPC hides your swap from public-mempool bots, which is the biggest risk. A 2026 study still found sandwiches against "protected" transactions on several chains, so keep slippage sensible even on a private route.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between price impact and slippage?
&lt;/h3&gt;

&lt;p&gt;Price impact is how far your own trade moves the pool's price, and it's already built into your quote. Slippage tolerance is the extra movement you accept on top of the quote before the swap fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a low &lt;code&gt;mev_risk_level&lt;/code&gt; mean I can loosen slippage?
&lt;/h3&gt;

&lt;p&gt;Not by itself. &lt;code&gt;mev_risk_level&lt;/code&gt; reflects the builder-tip share of fees, not sandwiches. Check &lt;code&gt;sandwich_rate_24h&lt;/code&gt; too, and remember that both describe the last 24 hours, not your next block.&lt;/p&gt;

&lt;h3&gt;
  
  
  What slippage should I set for stablecoin swaps?
&lt;/h3&gt;

&lt;p&gt;Stablecoin pairs barely move, so 0.05–0.1% usually fills in a deep pool. Anything much looser only gives a bot room to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Uniswap, "What is slippage in crypto?": &lt;a href="https://blog.uniswap.org/what-is-slippage-crypto" rel="noopener noreferrer"&gt;https://blog.uniswap.org/what-is-slippage-crypto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MEV Blocker, "Sandwich attacks": &lt;a href="https://docs.mevblocker.io/concepts/mev-attack-types/sandwich-attacks" rel="noopener noreferrer"&gt;https://docs.mevblocker.io/concepts/mev-attack-types/sandwich-attacks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Heimbach and Wattenhofer, "Eliminating Sandwich Attacks with the Help of Game Theory" (2022): &lt;a href="https://arxiv.org/abs/2202.03762" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2202.03762&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Helius, "Solana MEV report" (memecoin slippage, Jupiter dynamic slippage and MEV Protect): &lt;a href="https://www.helius.dev/blog/solana-mev-report" rel="noopener noreferrer"&gt;https://www.helius.dev/blog/solana-mev-report&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Heimbach, Solmaz, Öz and Torres, "No Place to Hide: An Analysis on Protected Order Flow Sandwich Attacks" (2026): &lt;a href="https://arxiv.org/abs/2609.28115" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2609.28115&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flashbots Protect overview: &lt;a href="https://docs.flashbots.net/flashbots-protect/overview" rel="noopener noreferrer"&gt;https://docs.flashbots.net/flashbots-protect/overview&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>defi</category>
      <category>crypto</category>
      <category>ethereum</category>
      <category>solana</category>
    </item>
    <item>
      <title>Walmart and Target Price Scraper: Search and Product Pages</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:48:17 +0000</pubDate>
      <link>https://dev.to/odeeb/walmart-and-target-price-scraper-search-and-product-pages-4gab</link>
      <guid>https://dev.to/odeeb/walmart-and-target-price-scraper-search-and-product-pages-4gab</guid>
      <description>&lt;p&gt;A Walmart or Target price scraper reads the price, was-price, stock and GTIN from product pages, or from every product on a search or category page. Both retailers work without a browser. Match the same item across the two by GTIN, not by name: Walmart printed UPCs with 12 digits and Target with 13 (a leading zero) in our runs, so strip leading zeros first.&lt;/p&gt;

&lt;p&gt;This guide uses an Apify actor that supports both retailers and shows real output from 2026-10-20, a Python script that matches the same products on both stores, and the caveats that matter for price data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What works on each retailer
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Product Scraper Pro&lt;/a&gt; supported-sites table (both listed as &lt;strong&gt;supported&lt;/strong&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Walmart (&lt;code&gt;walmart.com&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Target (&lt;code&gt;target.com&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product pages&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Category and search pages&lt;/td&gt;
&lt;td&gt;yes, with pagination&lt;/td&gt;
&lt;td&gt;yes, with pagination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword search&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviews&lt;/td&gt;
&lt;td&gt;all pages&lt;/td&gt;
&lt;td&gt;all pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sellers&lt;/td&gt;
&lt;td&gt;marketplace seller profiles&lt;/td&gt;
&lt;td&gt;store contact only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How pages are reached&lt;/td&gt;
&lt;td&gt;Datacenter&lt;/td&gt;
&lt;td&gt;Datacenter, with a crawler user agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Walmart Mexico (&lt;code&gt;walmart.com.mx&lt;/code&gt;) is supported too, with prices in MXN: product pages, category and search pages and keyword search over datacenter connections, and reviews best-effort (from the page's data). &lt;code&gt;walmart.ca&lt;/code&gt; and Bodega Aurrera (&lt;code&gt;bodegaaurrera.com.mx&lt;/code&gt;) are best-effort. In the actor's AUTO mode, each page starts on the cheapest route that works for the site and moves to a residential proxy or a browser only when it's blocked. For Target that first route is a crawler-style request. In our October test runs, all 70 Target product rows came that way. Walmart rows came over datacenter connections 288 times out of 340 and over residential 52 times. A residential page adds a traffic fee, billed only for items that delivered.&lt;/p&gt;

&lt;p&gt;Don't switch the scrape mode to &lt;code&gt;BROWSER&lt;/code&gt; for these two. In the actor's cloud tests, Walmart answered the browser with a PerimeterX challenge, and Target rendered its pages without a price.&lt;/p&gt;

&lt;h2&gt;
  
  
  A search page from each store
&lt;/h2&gt;

&lt;p&gt;This input reads the first ten results of a "lego" search on each store, with monitor mode on so the next run flags price changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listingUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.target.com/s?searchTerm=lego"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.walmart.com/search?q=lego"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItemsPerStartUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxProductResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"monitorMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All five rows from a smaller run of the same input on 2026-10-20 (&lt;code&gt;maxItemsPerStartUrl: 3&lt;/code&gt;, &lt;code&gt;maxProductResults: 5&lt;/code&gt;; run &lt;code&gt;F0TqL6QA5Rd6wTf9w&lt;/code&gt;), names shortened:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Store&lt;/th&gt;
&lt;th&gt;&lt;code&gt;position&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;sponsored&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;&lt;code&gt;price&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;originalPrice&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;gtin&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;LEGO Minifigures Shrek Series Mystery Box 71053&lt;/td&gt;
&lt;td&gt;4.99&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;0673419424301&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;LEGO Marvel Spider-Man vs. Anti-Venom Heist 30725&lt;/td&gt;
&lt;td&gt;4.99&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;0673419421164&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;LEGO Speed Champions The Fast and The Furious Toyota Supra MK4 77260&lt;/td&gt;
&lt;td&gt;27.99&lt;/td&gt;
&lt;td&gt;29.99&lt;/td&gt;
&lt;td&gt;0673419424769&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walmart&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;LEGO Harry Potter Hogwarts Castle and Grounds 76419&lt;/td&gt;
&lt;td&gt;136.99&lt;/td&gt;
&lt;td&gt;169.99&lt;/td&gt;
&lt;td&gt;673419375863&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walmart&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;LEGO Creator 3 in 1 Cute Hamster with a Flower - 31376&lt;/td&gt;
&lt;td&gt;9.97&lt;/td&gt;
&lt;td&gt;20.39&lt;/td&gt;
&lt;td&gt;673419419291&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;position&lt;/code&gt; is the product's rank on that search input. On Walmart it's the organic rank, 1 to n in the order Walmart lists the results. Walmart's ads are kept but flagged: &lt;code&gt;sponsored: true&lt;/code&gt;, a separate &lt;code&gt;adPosition&lt;/code&gt;, and &lt;code&gt;position: null&lt;/code&gt;. They never take one of the organic slots your &lt;code&gt;maxItemsPerStartUrl&lt;/code&gt; cap allows, and color or size swatches of the same item don't come back as separate products. On Target, &lt;code&gt;sponsored&lt;/code&gt; is null because the actor can't tell Target's ads apart; treat it as unknown. To keep only organic results, drop the rows where &lt;code&gt;sponsored&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;originalPrice&lt;/code&gt; is the was-price the page shows, and it appears only when it's higher than &lt;code&gt;price&lt;/code&gt;. Walmart's hamster set shows $20.39 against $9.97. A was-price is a retailer's claim, not proof of a discount, and stores don't agree on them: the GTIN script below finds the LEGO Daisies set at $9.89 on both stores, with a was-price only on Walmart. See &lt;a href="https://datatooly.xyz/guides/spot-fake-black-friday-discounts/" rel="noopener noreferrer"&gt;spotting fake Black Friday discounts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also skip the URLs and search by keyword: &lt;code&gt;"keyword": "usb c cable"&lt;/code&gt; with &lt;code&gt;"marketplaces": ["www.walmart.com", "www.target.com"]&lt;/code&gt; runs the same search on both stores, and each row carries the keyword. For Walmart Mexico, add &lt;code&gt;"www.walmart.com.mx"&lt;/code&gt;. A Walmart search with no results returns a status row with &lt;code&gt;outcome: "not-found"&lt;/code&gt; ("Walmart's search lists no results for this keyword") and isn't billed beyond the run's start fee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python: match the same products on Walmart and Target
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;constructive_calm/ecommerce-product-scraper-pro&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;detailsUrls&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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;https://www.walmart.com/ip/LEGO-Botanicals-11508/17091312501&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;url&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;https://www.target.com/p/lego-botanicals-daisies-building-toy-for-kids-11508/-/A-94756752&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;url&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;https://www.walmart.com/ip/LEGO-Minifigures-71053/18839271940&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;url&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;https://www.target.com/p/lego-minifigures-shrek-series-mystery-box-71053/-/A-95046345&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;maxProductResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gtin_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Walmart prints UPCs as 12 digits, Target as 13 with a leading 0: compare without leading zeros.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="n"&gt;by_gtin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&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;p&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; gtin=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gtin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; was=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;originalPrice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inStock=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inStock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seller=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;seller&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; via=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tierUsed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;gtin_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gtin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;by_gtin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;gtin_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gtin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])][&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;gtin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;by_gtin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;winners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;low&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GTIN &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gtin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; lowest: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winners&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;Output on 2026-10-20 (&lt;code&gt;pip install apify-client&lt;/code&gt;, &lt;code&gt;APIFY_TOKEN&lt;/code&gt; set to your Apify API token):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target      gtin=0673419424301    4.99 USD was=None inStock=True seller=None via=crawler-ua  LEGO Minifigures Shrek Series Mystery Bo
target      gtin=0673419422000    9.89 USD was=None inStock=True seller=None via=crawler-ua  LEGO Botanicals Daisies Building Toy for
walmart-us  gtin=673419422000     9.89 USD was=14.99 inStock=True seller=Walmart.com via=datacenter  LEGO Botanicals Daisies Building Toy - B
walmart-us  gtin=673419424301     4.97 USD was=None inStock=True seller=Walmart.com via=datacenter  LEGO Minifigures Shrek Series 1-Pack - M
GTIN 673419424301: {'target': 4.99, 'walmart-us': 4.97} -&amp;gt; lowest: walmart-us
GTIN 673419422000: {'target': 9.89, 'walmart-us': 9.89} -&amp;gt; lowest: target = walmart-us
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To match whole search pages instead of hand-picked URLs, run the listing input above and feed its rows to the same &lt;code&gt;gtin_key&lt;/code&gt; grouping. Products that only one store carries simply won't pair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields that differ between the two
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Walmart&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sku&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Walmart item id (&lt;code&gt;17091312501&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Target's TCIN (&lt;code&gt;94756752&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gtin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12-digit UPC&lt;/td&gt;
&lt;td&gt;13 digits, leading zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seller&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"Walmart.com"&lt;/code&gt; or the marketplace seller's name&lt;/td&gt;
&lt;td&gt;null in our runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;additionalProperties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;specifications&lt;/code&gt;, &lt;code&gt;highlights&lt;/code&gt;, &lt;code&gt;sellerId&lt;/code&gt;, &lt;code&gt;fulfillmentType&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;site extras when present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unitPrice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;when the page shows one, e.g. 0.027 per &lt;code&gt;fl oz&lt;/code&gt; on a gallon of milk&lt;/td&gt;
&lt;td&gt;when the page shows one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;variants[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pack sizes, colors and lengths with their own prices&lt;/td&gt;
&lt;td&gt;colors and sizes, on products that have them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Walmart, check &lt;code&gt;seller&lt;/code&gt; before you compare prices. A third-party marketplace offer can be the one shown, and its price, shipping and returns aren't Walmart's. Seller profiles go in the &lt;code&gt;sellerUrls&lt;/code&gt; input and return the seller's name, address, rating and published contact details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats for price data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Store and ZIP prices.&lt;/strong&gt; The actor has no store or ZIP code input. Rows contain the price each page serves to a visitor who hasn't chosen a store. Don't treat it as the shelf price in a particular store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-stock items.&lt;/strong&gt; In our test runs, some out-of-stock Walmart products came back with &lt;code&gt;inStock: false&lt;/code&gt; and no price. Store &lt;code&gt;null&lt;/code&gt; as "unknown", not zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Was-prices differ by store.&lt;/strong&gt; As the Daisies example shows, one store may show a was-price and the other not, for the same product at the same price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listings move.&lt;/strong&gt; &lt;code&gt;position&lt;/code&gt; reflects the search at that moment, and the order can change from run to run. On Walmart, ads are flagged (&lt;code&gt;sponsored: true&lt;/code&gt;) and kept out of &lt;code&gt;position&lt;/code&gt;; on Target they aren't identified.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Pay per event, from the actor's pricing table. Both stores are usually reached without residential traffic, so the main events are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;FREE&lt;/th&gt;
&lt;th&gt;GOLD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Actor start (per run, 1 GB)&lt;/td&gt;
&lt;td&gt;$0.005&lt;/td&gt;
&lt;td&gt;$0.005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product scraped&lt;/td&gt;
&lt;td&gt;$0.0048&lt;/td&gt;
&lt;td&gt;$0.0008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listing page scraped&lt;/td&gt;
&lt;td&gt;$0.0004&lt;/td&gt;
&lt;td&gt;$0.00021&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The README's example, a 20-page category with 1,000 products: 20 × $0.00021 + 1,000 × $0.0008 + $0.005 = &lt;strong&gt;$0.809&lt;/strong&gt; on GOLD. On FREE: 20 × $0.0004 + 1,000 × $0.0048 + $0.005 = &lt;strong&gt;$4.813&lt;/strong&gt;. Pages that escalate to residential add $0.018 (FREE) to $0.014 (GOLD) per MB for items that delivered. Apify's free plan includes $5 of usage every month, with no credit card required. There's no free trial inside the actor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Holiday season: one GTIN list, both stores, every day
&lt;/h2&gt;

&lt;p&gt;Black Friday is Friday, November 27, 2026, and Cyber Monday is November 30. If you buy or sell toys, electronics or anything both retailers stock, build one input with the Walmart and Target URLs of the same GTINs and schedule it daily from early November with &lt;code&gt;monitorMode: true&lt;/code&gt;. Each run then shows which store moved first and by how much (&lt;code&gt;previousPrice&lt;/code&gt;, &lt;code&gt;priceChange&lt;/code&gt;), and the GTIN script above shows which store is lower that day. To compare more stores than these two, see &lt;a href="https://datatooly.xyz/guides/black-friday-google-shopping-price-comparison/" rel="noopener noreferrer"&gt;comparing Black Friday prices across stores&lt;/a&gt;. For dates and lead times, see the &lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;holiday sales calendar&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/scrape-product-reviews-walmart-target-amazon/" rel="noopener noreferrer"&gt;Scrape product reviews from Walmart, Target and Amazon&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/amazon-price-scraper-asin/" rel="noopener noreferrer"&gt;Amazon price scraper by ASIN&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker builder&lt;/a&gt; has a "Walmart only", "Target only" and "US: Amazon + Walmart + Target" keyword option. It produces a ready-to-run input for the actor and shows a fixed example of the output, not live prices.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the actor and this site.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: actor runs on 2026-10-20 (search table: run &lt;code&gt;F0TqL6QA5Rd6wTf9w&lt;/code&gt;, build 0.1.16; GTIN script; milk unit price), our October 2026 cloud test runs (tier counts, out-of-stock rows, and a zero-result Walmart search on build 0.1.16), and the actor's README (supported-sites table, browser-mode note, pricing).&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>python</category>
      <category>webscraping</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Track AI Brand Mentions Over Time: Schedules and Drift</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:47:56 +0000</pubDate>
      <link>https://dev.to/odeeb/track-ai-brand-mentions-over-time-schedules-and-drift-12bi</link>
      <guid>https://dev.to/odeeb/track-ai-brand-mentions-over-time-schedules-and-drift-12bi</guid>
      <description>&lt;p&gt;To track AI brand mentions over time, run the same buyer questions in the same assistants with the same settings on a schedule (monthly by hand, weekly with a script). Log every brand and cited domain per answer and compare runs prompt by prompt. Record the model and whether web search ran, to separate real change from updates and noise.&lt;/p&gt;

&lt;p&gt;The first check tells you where you stand. This guide is about the second and later checks: what to hold constant, what causes drift, and how to tell a real change from noise. The comparison script at the end runs on the same CSV log as the &lt;a href="https://datatooly.xyz/guides/check-if-chatgpt-mentions-your-brand/" rel="noopener noreferrer"&gt;free ChatGPT brand check&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hold these fixed
&lt;/h2&gt;

&lt;p&gt;If any of these change between runs, a change in your numbers may come from your setup, not the assistants.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hold fixed&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt wording&lt;/td&gt;
&lt;td&gt;A reworded question is a different question. Add new prompts as new IDs; never edit old ones.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Competitor list&lt;/td&gt;
&lt;td&gt;Share of voice is relative; a new competitor lowers everyone else's share.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assistants and modes&lt;/td&gt;
&lt;td&gt;Record web search on or off per answer. Searched and unsearched answers behave differently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personalisation&lt;/td&gt;
&lt;td&gt;Same clean session every time: signed out, or memory and custom instructions off.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location and language&lt;/td&gt;
&lt;td&gt;Answers can be localised, so run checks from the same country and language each time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs per prompt&lt;/td&gt;
&lt;td&gt;More runs give a tighter estimate; changing the count changes the noise level.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Counting rules&lt;/td&gt;
&lt;td&gt;What counts as a mention (not "I'm not familiar with X"), and how you record order and citations.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Save the raw answers, not just your tallies, so you can re-score old runs if you refine the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What causes drift
&lt;/h2&gt;

&lt;p&gt;Some changes are real, some come from the assistant, and some are noise.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;How to spot it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model updates&lt;/td&gt;
&lt;td&gt;The vendor replaces or updates the model behind the app or API.&lt;/td&gt;
&lt;td&gt;Record the model name the app or API reports for each run. A jump across many prompts on the same date suggests an update.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval changes&lt;/td&gt;
&lt;td&gt;The pages web search returns for a question change: new reviews, updated listicles, your own new pages.&lt;/td&gt;
&lt;td&gt;Compare the cited domains between runs for the prompts that changed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crawler access&lt;/td&gt;
&lt;td&gt;Blocking a search crawler removes your pages from that assistant's search answers. OpenAI documents that sites opted out of OAI-SearchBot "will not be shown in ChatGPT search answers."&lt;/td&gt;
&lt;td&gt;Re-check robots.txt and CDN bot rules; see &lt;a href="https://datatooly.xyz/guides/how-ai-assistants-choose-brands-sources/" rel="noopener noreferrer"&gt;How AI assistants choose which brands and sources to mention&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your own changes&lt;/td&gt;
&lt;td&gt;Pricing pages, comparison pages, reviews, PR.&lt;/td&gt;
&lt;td&gt;Note the date of each change, and compare the prompts it should affect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Randomness&lt;/td&gt;
&lt;td&gt;The same question gives different answers run to run.&lt;/td&gt;
&lt;td&gt;Small samples. A 1-in-3 to 2-in-3 move on one prompt is usually noise.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How often to run
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Suits&lt;/th&gt;
&lt;th&gt;Sample per run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;Most brands, done by hand&lt;/td&gt;
&lt;td&gt;10–20 prompts × 3 runs per assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;Active content or PR work, scripted&lt;/td&gt;
&lt;td&gt;The same set, so weeks are comparable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After a known change&lt;/td&gt;
&lt;td&gt;A relaunch, a pricing change, a big review&lt;/td&gt;
&lt;td&gt;One extra run, compared with the last scheduled one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running more often doesn't make a single run more accurate. More answers per run does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare two runs
&lt;/h2&gt;

&lt;p&gt;This script compares two logs prompt by prompt (won, lost, up, down, same) and tests whether the overall mention rate changed by more than chance would explain:&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;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;

&lt;span class="n"&gt;BRAND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Northwind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;{(assistant, prompt_id): [True/False per run]} - did the answer name BRAND?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;brands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brands_in_order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BRAND&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;brands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;two_proportion_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Two-sided p-value for &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;the mention rate changed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; (normal approximation).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;se&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;erfc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  change&lt;/span&gt;&lt;span class="sh"&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;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  only in one run: not comparable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;won&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;up&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;down&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;same&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;mention rate &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;two_proportion_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&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;&lt;code&gt;python compare_runs.py log_2026-09.csv log_2026-10.csv&lt;/code&gt; on two small example logs (the brands are placeholders, not real measurements):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt            before   after  change
chatgpt/P1      1/3     3/3       up
chatgpt/P2      0/3     1/3       won
chatgpt/P3      3/3     2/3       down

mention rate 4/9 = 44% -&amp;gt; 6/9 = 67%, p = 0.34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The headline looks like a 23-point gain. With 9 answers per month, p = 0.34 says a change this size would turn up often by chance alone. Same rates with more answers, using the same test:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;p&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12/30 (40%)&lt;/td&gt;
&lt;td&gt;18/30 (60%)&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40/100 (40%)&lt;/td&gt;
&lt;td&gt;50/100 (50%)&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80/200 (40%)&lt;/td&gt;
&lt;td&gt;100/200 (50%)&lt;/td&gt;
&lt;td&gt;0.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40/100 (40%)&lt;/td&gt;
&lt;td&gt;60/100 (60%)&lt;/td&gt;
&lt;td&gt;0.005&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A rule of thumb from this: on 100 answers a run, a 20-point move is solid evidence; a 10-point move needs about 200 answers a run before it stands out. The prompt-by-prompt view is still useful at small sizes. It tells you where to look, such as which cited sources changed on a prompt you lost, even when the total isn't conclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Comparing different prompt sets.&lt;/strong&gt; If you added prompts, compare only the prompts present in both runs. The script marks the others as not comparable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixing searched and unsearched answers.&lt;/strong&gt; If one month used search more often than the other, the rates move for that reason alone. Compare within each mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reacting to one prompt.&lt;/strong&gt; One prompt going from 3/3 to 2/3 is one answer's difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting branded prompts.&lt;/strong&gt; Mention rate on branded prompts is always high. Keep them out of the totals and use them to track what the assistant says about you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/check-if-chatgpt-mentions-your-brand/" rel="noopener noreferrer"&gt;How to check if ChatGPT mentions your brand (free)&lt;/a&gt;, which defines the log format.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/ai-share-of-voice-chatgpt-perplexity/" rel="noopener noreferrer"&gt;AI share of voice: measure it in ChatGPT and Perplexity&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/how-ai-assistants-choose-brands-sources/" rel="noopener noreferrer"&gt;How AI assistants choose which brands and sources to mention&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/measure-ai-share-of-voice-python/" rel="noopener noreferrer"&gt;Measure AI share of voice in Python&lt;/a&gt;, for collecting the answers through the APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/llm-visibility-monitor-tool/" rel="noopener noreferrer"&gt;LLM visibility query builder&lt;/a&gt; gives you a ready-to-run audit input from your brand, category and competitors, with a fixed example of the output. It doesn't call any AI model from your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  At scale
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/constructive_calm/llm-visibility-monitor?fpr=v77kxu" rel="noopener noreferrer"&gt;LLM Visibility Monitor on Apify&lt;/a&gt; is built for scheduled runs. It saves the generated question set so every run asks the same questions, compares each run with your previous run for the same brand, country and language (questions won, lost and unchanged), and records the model it requested and the model that actually answered on every row. You can schedule it weekly through Apify. It's free to start, then pay-as-you-go.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I build the datatooly query builder and the Apify actor. The comparison script works on any hand-collected log.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://developers.openai.com/api/docs/bots" rel="noopener noreferrer"&gt;OpenAI: overview of OpenAI crawlers&lt;/a&gt;, &lt;a href="https://help.openai.com/en/articles/9237897-chatgpt-search" rel="noopener noreferrer"&gt;OpenAI Help Center: Searching the web with ChatGPT&lt;/a&gt;, and the two-proportion z-test (computed in the script above).&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>marketing</category>
      <category>python</category>
    </item>
    <item>
      <title>When Do Black Friday Prices Drop? The 2026 Timeline</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:47:34 +0000</pubDate>
      <link>https://dev.to/odeeb/when-do-black-friday-prices-drop-the-2026-timeline-i0m</link>
      <guid>https://dev.to/odeeb/when-do-black-friday-prices-drop-the-2026-timeline-i0m</guid>
      <description>&lt;p&gt;Black Friday prices don't drop on one day. In 2025, Walmart's first Black Friday event started November 14, Amazon's Black Friday Week November 20, Target's event November 23, and Walmart's main event went live online on the Tuesday before Thanksgiving, with deals running through Cyber Monday. For 2026 (Thanksgiving November 26, Black Friday November 27), check daily from early November and see when each product actually hits its low.&lt;/p&gt;

&lt;p&gt;This guide lays out the 2025 timeline from primary sources, maps it onto the 2026 calendar, and gives a script that reads your daily price snapshots and tells you the day each product reached its season low.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2025 timeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date (2025)&lt;/th&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nov 13, 7 p.m. ET&lt;/td&gt;
&lt;td&gt;Thu&lt;/td&gt;
&lt;td&gt;Walmart+ early access to Walmart's Deals Event 1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://corporate.walmart.com/news/2025/10/27/walmart-announces-2025-black-friday-deals-events-wrapped-in-the-magic-of-whoknewville" rel="noopener noreferrer"&gt;Walmart&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 14 – 16&lt;/td&gt;
&lt;td&gt;Fri – Sun&lt;/td&gt;
&lt;td&gt;Walmart Black Friday Deals Event 1, online and in stores&lt;/td&gt;
&lt;td&gt;Walmart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 20, 12:01 a.m. PST&lt;/td&gt;
&lt;td&gt;Thu&lt;/td&gt;
&lt;td&gt;Amazon's US Black Friday Week starts, running through Dec 1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.aboutamazon.com/news/retail/when-is-black-friday-2025" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 23 – 29&lt;/td&gt;
&lt;td&gt;Sun – Sat&lt;/td&gt;
&lt;td&gt;Target's Black Friday event&lt;/td&gt;
&lt;td&gt;&lt;a href="https://corporate.target.com/press/release/2025/11/target-transforms-black-friday-into-an-experience-with-deep-deals,-exclusive-launches-and-surprise-g" rel="noopener noreferrer"&gt;Target&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 25 – 27&lt;/td&gt;
&lt;td&gt;Tue – Thu&lt;/td&gt;
&lt;td&gt;Walmart Deals Event 2, online only&lt;/td&gt;
&lt;td&gt;Walmart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 27&lt;/td&gt;
&lt;td&gt;Thu&lt;/td&gt;
&lt;td&gt;Thanksgiving: $6.4 billion US online spending&lt;/td&gt;
&lt;td&gt;&lt;a href="https://news.adobe.com/news/2025/12/adobe-cyber-monday-hits-record" rel="noopener noreferrer"&gt;Adobe&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 28&lt;/td&gt;
&lt;td&gt;Fri&lt;/td&gt;
&lt;td&gt;Black Friday: $11.8 billion online, up 9.1% year on year; Walmart Event 2 opens in stores (to Nov 30)&lt;/td&gt;
&lt;td&gt;Adobe, Walmart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 29 – 30&lt;/td&gt;
&lt;td&gt;Sat – Sun&lt;/td&gt;
&lt;td&gt;Weekend: Adobe says "earlier deals drove record spending over the weekend"; Target's Cyber Monday event opens Nov 30&lt;/td&gt;
&lt;td&gt;Adobe, Target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dec 1&lt;/td&gt;
&lt;td&gt;Mon&lt;/td&gt;
&lt;td&gt;Cyber Monday: $14.25 billion online, Walmart's Cyber Monday event (online only)&lt;/td&gt;
&lt;td&gt;Adobe, Walmart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After Dec 1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Adobe: "After Cyber Week, discounts will come down from their peaks"&lt;/td&gt;
&lt;td&gt;Adobe&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Adobe's spending figures are for all US online retail, not for any one store. And spending isn't price: a busy day can be a busy day because people waited for payday, not because prices were lowest. The table tells you when deals were &lt;em&gt;available&lt;/em&gt;, not when a given product was cheapest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that suggests for 2026
&lt;/h2&gt;

&lt;p&gt;None of these retailers has announced 2026 dates as of October 20, 2026. In 2025, Walmart published its event dates on October 27, and Target's detailed Black Friday release came out on November 20. Expect 2026 announcements between late October and mid-November.&lt;/p&gt;

&lt;p&gt;If retailers keep the same weekday offsets from Thanksgiving (an assumption, not an announcement), 2026 would look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;2025 event&lt;/th&gt;
&lt;th&gt;Days before Thanksgiving&lt;/th&gt;
&lt;th&gt;Same offset in 2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Walmart Event 1 starts (Fri Nov 14)&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Fri Nov 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Black Friday Week starts (Thu Nov 20)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Thu Nov 19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target Black Friday event starts (Sun Nov 23)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Sun Nov 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walmart Event 2 online (Tue Nov 25)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Tue Nov 24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thanksgiving&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Thu Nov 26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Black Friday&lt;/td&gt;
&lt;td&gt;−1&lt;/td&gt;
&lt;td&gt;Fri Nov 27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cyber Monday&lt;/td&gt;
&lt;td&gt;−4&lt;/td&gt;
&lt;td&gt;Mon Nov 30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So a baseline that starts by late October is clean: it's before every 2025 event. A tracker that only starts in the week of Thanksgiving has no "before" to compare with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you have to measure it yourself
&lt;/h2&gt;

&lt;p&gt;"Prices drop on Black Friday" is an average. Product by product, the low can come earlier, later, or not at all. Which?, the UK consumer group, looked at 227 tech and appliance deals from Black Friday 2023 and found that "every one of the 'deals' we checked was available for the same price or less" at other times in the six months before or after, and around four in 10 were cheaper at some other point (&lt;a href="https://www.which.co.uk/news/article/black-friday-deals-exposed-as-duds-aoK6u3j2suyu" rel="noopener noreferrer"&gt;Which?, 26 Nov 2024&lt;/a&gt;). That's UK retailers and one year, but it shows that the "best day" is product-specific.&lt;/p&gt;

&lt;p&gt;The way to find it is a daily price snapshot per product from late October to early December. The &lt;a href="https://datatooly.xyz/guides/black-friday-price-tracker/" rel="noopener noreferrer"&gt;Black Friday price tracker guide&lt;/a&gt; sets that up with a scheduled run of &lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Product Scraper Pro&lt;/a&gt; on Amazon, Walmart and Target, and exports the history to a &lt;code&gt;price_history.csv&lt;/code&gt; file with these columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scrapedAt,site,url,name,price,originalPrice,currency,inStock,seller,changeType,previousPrice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Find the day each product hit its low
&lt;/h2&gt;

&lt;p&gt;The script below works on that file. It takes the lowest price per product per US Eastern day (so it copes with several runs a day), fills in unchanged days (with &lt;code&gt;onlyChanges&lt;/code&gt;, a product only appears on days its price or stock changed), and reports the first day each product reached its season low, how long the low lasted, and which phase of the season that day falls in.&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;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;   &lt;span class="c1"&gt;# Windows: pip install tzdata
&lt;/span&gt;
&lt;span class="n"&gt;ET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/New_York&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PHASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# (first day, label) for the 2026 season; edit as retailers announce dates
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;baseline (before Nov 1)&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;early November&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pre-Thanksgiving events&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thanksgiving Day&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Black Friday&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekend&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cyber Monday&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="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after Cyber Monday&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PHASES&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Lowest price seen per product per US-Eastern day (handles several runs a day).
&lt;/span&gt;&lt;span class="n"&gt;lows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r&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="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scrapedAt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&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;+00:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;lows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]][&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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;tally&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;earlier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;by_day&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by_day&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;earlier&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;earlier&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;START&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                      &lt;span class="c1"&gt;# carry the price forward over unchanged days
&lt;/span&gt;        &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;by_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; flat at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; all season&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;low_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;low_days&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;low_days&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;tally&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; low &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; first &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;), &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;held &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low_days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; day(s) until &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last_low&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Products whose season low started in each phase:&lt;/span&gt;&lt;span class="sh"&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PHASES&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;tally&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&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;Here's the output on a made-up four-product season (the products and prices are invented to show each pattern; the file format is the real export format):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TV 1          low   399.99 first Thu Nov 26 (Thanksgiving Day), held 5 day(s) until Nov 30
Vacuum 2      low   179.00 first Fri Nov 13 (pre-Thanksgiving events), held 4 day(s) until Nov 16
Toy 3         low    39.99 first Mon Nov 30 (Cyber Monday), held 2 day(s) until Dec 01
Cable 4       flat at 7.97 all season

Products whose season low started in each phase:
  baseline (before Nov 1)   0
  early November            0
  pre-Thanksgiving events   1
  Thanksgiving Day          1
  Black Friday              0
  weekend                   0
  Cyber Monday              1
  after Cyber Monday        0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TV 1&lt;/strong&gt; dropped in steps and reached its low on Thanksgiving Day, then held it through Cyber Monday. Waiting for Black Friday cost nothing, but waiting past Monday would have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vacuum 2&lt;/strong&gt; was cheapest in an early event and never matched that price again. Its later "Black Friday" price was a smaller discount. This is the pattern the 2025 Walmart Event 1 dates make possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toy 3&lt;/strong&gt; kept falling until Cyber Monday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cable 4&lt;/strong&gt; never moved. Plenty of products don't take part in the sales at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottom table is the one to keep. After the season, run it on your whole watchlist and you get your own answer to "when do prices drop" for the categories you care about. Keep the file: next year you can set your alerts around the phase where your products actually bottomed out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Details that change the answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time zones.&lt;/strong&gt; &lt;code&gt;scrapedAt&lt;/code&gt; is UTC. A drop at 11 p.m. Eastern on Thanksgiving is already Friday in UTC, which is why the script converts to Eastern before picking the day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run frequency.&lt;/strong&gt; With one run a day, a low that lasted six hours can be missed. From Thanksgiving to Cyber Monday, run every few hours; the &lt;a href="https://datatooly.xyz/guides/cyber-monday-deal-tracker/" rel="noopener noreferrer"&gt;Cyber Monday deal tracker guide&lt;/a&gt; works out what hourly checks cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out of stock.&lt;/strong&gt; A low price on a day the item was sold out isn't a price you could pay. If that matters, skip rows where &lt;code&gt;inStock&lt;/code&gt; is false before computing lows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the low a real deal?&lt;/strong&gt; A season low can still be the normal price if the item was raised beforehand. The &lt;a href="https://datatooly.xyz/guides/spot-fake-black-friday-discounts/" rel="noopener noreferrer"&gt;fake Black Friday discounts guide&lt;/a&gt; compares it with the 30-day median and the 90-day low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit the phases.&lt;/strong&gt; When retailers announce 2026 dates, change the &lt;code&gt;PHASES&lt;/code&gt; list so the labels match the real events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the year's other sales and when to start each baseline, see the &lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;holiday sales calendar and price tracking guide&lt;/a&gt;, and for gift-buying after Cyber Monday, the &lt;a href="https://datatooly.xyz/guides/christmas-price-tracker/" rel="noopener noreferrer"&gt;Christmas price tracker&lt;/a&gt;. The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker&lt;/a&gt; page builds the tracking input.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the actor and this site. The script reads any CSV with these columns, whatever collected the prices.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://corporate.walmart.com/news/2025/10/27/walmart-announces-2025-black-friday-deals-events-wrapped-in-the-magic-of-whoknewville" rel="noopener noreferrer"&gt;Walmart 2025 Black Friday Deals events (Oct 27, 2025)&lt;/a&gt;, &lt;a href="https://www.aboutamazon.com/news/retail/when-is-black-friday-2025" rel="noopener noreferrer"&gt;Amazon: When is Black Friday 2025&lt;/a&gt;, &lt;a href="https://corporate.target.com/press/release/2025/11/target-transforms-black-friday-into-an-experience-with-deep-deals,-exclusive-launches-and-surprise-g" rel="noopener noreferrer"&gt;Target press release (Nov 20, 2025)&lt;/a&gt;, &lt;a href="https://news.adobe.com/news/2025/12/adobe-cyber-monday-hits-record" rel="noopener noreferrer"&gt;Adobe: Cyber Monday hits record (Dec 2, 2025)&lt;/a&gt;, &lt;a href="https://www.which.co.uk/news/article/black-friday-deals-exposed-as-duds-aoK6u3j2suyu" rel="noopener noreferrer"&gt;Which?: Black Friday deals exposed as duds (Nov 26, 2024)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>python</category>
      <category>datascience</category>
      <category>shopping</category>
    </item>
    <item>
      <title>How to Tell if a Token's Pool Is Getting Sandwiched Before You Swap</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:47:13 +0000</pubDate>
      <link>https://dev.to/odeeb/how-to-tell-if-a-tokens-pool-is-getting-sandwiched-before-you-swap-kng</link>
      <guid>https://dev.to/odeeb/how-to-tell-if-a-tokens-pool-is-getting-sandwiched-before-you-swap-kng</guid>
      <description>&lt;p&gt;To tell whether a token's pool is getting sandwiched, look through its recent trades for one wallet that buys just before someone else's buy and sells just after it, in the same block. Then check two numbers for the last 24 hours: the share of the pool's transactions that were sandwiched, and the share of its fees that went to MEV. If either is high, trade with tight slippage through an MEV-protected route, or trade smaller.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is an explainer about how trades get executed. It is not financial advice and says nothing about whether any token is worth buying.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a sandwich attack is
&lt;/h2&gt;

&lt;p&gt;A sandwich attack is three swaps in one block. A bot buys the token just before your pending buy (the front-run), your buy fills at the higher price the bot just created, and the bot sells right after you (the back-run) and keeps the difference. Your slippage tolerance caps how far the bot can push your price: if the front-run moves the price past your minimum output, your swap reverts instead of filling.&lt;/p&gt;

&lt;p&gt;The bot has to see your swap before it lands in a block. On Ethereum that usually means the public mempool. Solana has no public mempool, but sandwiches still happen there through private mempools and validators that see transactions before they're included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 1: read the pool's trade feed
&lt;/h2&gt;

&lt;p&gt;The most direct evidence is in the trades themselves. Open the pool's chart and trade list, for example this &lt;a href="https://www.defined.fi/eth/0xe0554a476a092703abdb3ef35c80e0d76d32939f" rel="noopener noreferrer"&gt;USDC/WETH Uniswap v3 pool on Defined&lt;/a&gt;, and work through these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Switch the chart to a 1-minute timeframe and click a busy candle. The trade table then shows only the trades inside that candle.&lt;/li&gt;
&lt;li&gt;Look for a triplet: wallet A buys, a different wallet B buys, then wallet A sells about the same amount it bought, with the same timestamp.&lt;/li&gt;
&lt;li&gt;Filter the trade table by wallet A's address. A sandwich bot repeats that buy-then-sell rhythm around other people's trades, often many times a day on the same pool.&lt;/li&gt;
&lt;li&gt;To confirm a single case, open the three transactions in a block explorer (Etherscan, Solscan, Basescan) and check that they share a block number and appear in that order.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some patterns look similar but aren't sandwiches. A wallet that buys and sells minutes apart is just a trader, and a lone sell right after a big swap is usually arbitrage. A sandwich needs both legs, one on each side of a victim. Checking by eye also only covers one pool for one hour. It can't tell you how often the pool gets hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 2: measure the sandwich rate and the MEV share
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.codex.io/" rel="noopener noreferrer"&gt;Codex&lt;/a&gt; indexes DEX trades and flags sandwiched transactions for each pool. The &lt;a href="https://apify.com/constructive_calm/mev-pair-scanner?fpr=v77kxu" rel="noopener noreferrer"&gt;MEV Risk Scanner&lt;/a&gt; on Apify reads that data for a token's top pools, for specific pool addresses, or for a chain's busiest pools. For each pool it returns these fields covering the last 24 hours:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sandwich_rate_24h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Share of the pool's transactions that were sandwiched (the average of the hourly rates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;estimated_sandwich_attacks_24h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;That rate multiplied by the 24h transaction count, rounded. It's an estimate, not a list of attacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mev_to_total_fees_ratio_24h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Share of the pool's fees paid to block builders as tips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mev_risk_level&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The most common hourly label. Codex labels each hour &lt;code&gt;low&lt;/code&gt; (under 3% builder tips), &lt;code&gt;medium&lt;/code&gt; (3–30%) or &lt;code&gt;high&lt;/code&gt; (over 30%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fee_regime_24h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mev-dominated&lt;/code&gt;, &lt;code&gt;gas-dominated&lt;/code&gt; or &lt;code&gt;pool-fee-dominated&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;safer_trading_windows_utc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start times of the hours labelled &lt;code&gt;low&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data_availability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How much of each field was populated, so you can tell "zero" from "not indexed"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's an example row, abbreviated and rounded, from a test run on 1 October 2026. It isn't live data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pair_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0xe0554a476a092703abdb3ef35c80e0d76d32939f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ethereum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UniswapV3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token0_symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USDC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token1_symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WETH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"liquidity_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3417730&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactions_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22633&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mev_risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mev_to_total_fees_ratio_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.305&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fee_regime_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mev-dominated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sandwich_rate_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.00063&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimated_sandwich_attacks_24h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"safer_trading_windows_utc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In plain terms, about 0.06% of roughly 22,600 transactions were sandwiched, around 14 in a day. Meanwhile about 30% of the pool's fees went to block builders, and no hour was quiet enough to count as &lt;code&gt;low&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Those are two different signals. &lt;code&gt;mev_risk_level&lt;/code&gt; measures builder tips, and builder tips pay for every kind of MEV, including arbitrage, back-runs and liquidations, not just sandwiches. A pool can be &lt;code&gt;high&lt;/code&gt; and see few sandwiches, or &lt;code&gt;low&lt;/code&gt; and still get sandwiched. Use &lt;code&gt;sandwich_rate_24h&lt;/code&gt; as your sandwich signal, and &lt;code&gt;mev_risk_level&lt;/code&gt; as a measure of how hard bots compete in that pool. Compare pools that trade the same pair: one whose rate is several times higher than its siblings is the one to route around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 3: compare your trade size with the pool
&lt;/h2&gt;

&lt;p&gt;A sandwich only pays if the bot's profit beats its own gas, fees and tips. That profit grows with two things: how far your trade moves the price, and how much slippage you allow. Your swap screen's &lt;strong&gt;price impact&lt;/strong&gt; figure shows how much your trade alone moves the price. A trade with almost no price impact in a deep pool is a thin target. A trade that moves a thin meme-coin pool by several percent, sent with a loose slippage setting, is exactly what bots look for.&lt;/p&gt;

&lt;p&gt;Liquidity figures aren't always comparable across sites, because they count pool depth differently. Compare figures from the same source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 4: decide how to send the trade
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use an MEV-protected route.&lt;/strong&gt; On Ethereum, &lt;a href="https://docs.flashbots.net/flashbots-protect/overview" rel="noopener noreferrer"&gt;Flashbots Protect&lt;/a&gt; sends your transaction to a private mempool that sandwich bots can't see. It doesn't include transactions that would revert, so you don't pay for failed swaps. &lt;a href="https://docs.mevblocker.io/" rel="noopener noreferrer"&gt;MEV Blocker&lt;/a&gt; works in a similar way. On Solana, some aggregators offer an MEV-protect mode that routes through Jito block engines, at slightly higher fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep slippage tight.&lt;/strong&gt; The tighter it is, the less room a front-run has. The companion guide &lt;a href="https://datatooly.xyz/guides/what-slippage-to-set-to-avoid-sandwich-attacks/" rel="noopener noreferrer"&gt;What slippage should I set to avoid sandwich attacks?&lt;/a&gt; covers how to choose a number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade smaller, or split the order,&lt;/strong&gt; when your price impact is high.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat timing as a hint.&lt;/strong&gt; &lt;code&gt;safer_trading_windows_utc&lt;/code&gt; shows which hours yesterday had a low builder-tip share. It doesn't promise anything about tomorrow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What these checks can't tell you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They describe the past 24 hours, not your next transaction.&lt;/strong&gt; The bot that targets you only needs to see your swap once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection comes from Codex,&lt;/strong&gt; and coverage varies. Some Solana pools return a sandwich rate but no fee breakdown. On L2s like Base, builder tips are empty or zero, so &lt;code&gt;mev_to_total_fees_ratio_24h&lt;/code&gt; comes back null there. Read &lt;code&gt;data_availability&lt;/code&gt; before trusting a zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protected routes reduce the risk but don't remove it.&lt;/strong&gt; A 2026 measurement study found sandwich attacks against "protected" transactions on every chain it examined, and noted that these often don't sit tightly around the victim. That means they won't always show up as the neat triplet from Check 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it for your own tokens
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/mev-pair-scanner-tool/" rel="noopener noreferrer"&gt;MEV pair scanner query builder&lt;/a&gt; turns a token or pool address and a chain into a ready-to-run input, and shows the output shape. It doesn't call Codex from your browser. The &lt;a href="https://apify.com/constructive_calm/mev-pair-scanner?fpr=v77kxu" rel="noopener noreferrer"&gt;actor&lt;/a&gt; runs the scan: up to 20 pools per run, at 1-hour bars by default (5-minute to 1-day bars are also available), with an optional one-line AI verdict. It's free to start, then pay-as-you-go per pool scanned. If you'd rather write the code yourself, &lt;a href="https://datatooly.xyz/guides/check-dex-pool-sandwich-attack-rate/" rel="noopener noreferrer"&gt;the Python version of this check&lt;/a&gt; calls Codex directly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the query builder and the actor.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I see a sandwich attack on a DEX chart?
&lt;/h3&gt;

&lt;p&gt;Not on the candles themselves. You can see one in the trade list: the same wallet buys just before another trader's buy and sells just after it, in the same block. Click a 1-minute candle to filter the trades down to that minute, then filter by the suspected bot's wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a high MEV risk level mean the pool is getting sandwiched?
&lt;/h3&gt;

&lt;p&gt;Not necessarily. &lt;code&gt;mev_risk_level&lt;/code&gt; comes from the share of fees paid to block builders, and that covers arbitrage and liquidations as well as sandwiches. Check &lt;code&gt;sandwich_rate_24h&lt;/code&gt; for sandwiches specifically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a pool with a 0% sandwich rate safe to swap in?
&lt;/h3&gt;

&lt;p&gt;It means few or no sandwiches were detected in the last 24 hours, or, if &lt;code&gt;data_availability&lt;/code&gt; shows the field as missing, that nothing was measured. A loose slippage setting on a public route can still get you sandwiched, so keep slippage tight either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are meme coins sandwiched more than major pairs?
&lt;/h3&gt;

&lt;p&gt;Meme-coin traders are an easy target because they often set high slippage when trading thin, volatile pools. The pool's own numbers matter more than the category, though, so check each pool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do private RPCs stop sandwich attacks?
&lt;/h3&gt;

&lt;p&gt;They hide your transaction from the public mempool, and that removes the most common way bots find victims. Research published in 2026 still found sandwiches against protected transactions on several chains, so treat a private RPC as a big risk reduction, not a guarantee.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why are some fields null for Solana or Base pools?
&lt;/h3&gt;

&lt;p&gt;Codex's fee indexing varies by pool on Solana, and L2s like Base don't have a builder-tip market like Ethereum mainnet's. A null value means "not available", not zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which chains can I check?
&lt;/h3&gt;

&lt;p&gt;Any network Codex indexes, which is more than a hundred, including Ethereum, Solana, Base, BNB Chain and Arbitrum. Paste a bare &lt;code&gt;0x&lt;/code&gt; address with a default chain selected, a Solana address as-is, or &lt;code&gt;chain:address&lt;/code&gt; to mix chains in one run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MEV Blocker, "Sandwich attacks": &lt;a href="https://docs.mevblocker.io/concepts/mev-attack-types/sandwich-attacks" rel="noopener noreferrer"&gt;https://docs.mevblocker.io/concepts/mev-attack-types/sandwich-attacks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Helius, "Solana MEV report" (private mempools, memecoin slippage, Jupiter MEV Protect): &lt;a href="https://www.helius.dev/blog/solana-mev-report" rel="noopener noreferrer"&gt;https://www.helius.dev/blog/solana-mev-report&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Heimbach and Wattenhofer, "Eliminating Sandwich Attacks with the Help of Game Theory" (2022): &lt;a href="https://arxiv.org/abs/2202.03762" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2202.03762&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Heimbach, Solmaz, Öz and Torres, "No Place to Hide: An Analysis on Protected Order Flow Sandwich Attacks" (2026): &lt;a href="https://arxiv.org/abs/2609.28115" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2609.28115&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flashbots Protect overview: &lt;a href="https://docs.flashbots.net/flashbots-protect/overview" rel="noopener noreferrer"&gt;https://docs.flashbots.net/flashbots-protect/overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Defined docs, transaction filtering: &lt;a href="https://docs.defined.fi/trading/defined-features/transaction-filtering" rel="noopener noreferrer"&gt;https://docs.defined.fi/trading/defined-features/transaction-filtering&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>defi</category>
      <category>crypto</category>
      <category>ethereum</category>
      <category>solana</category>
    </item>
    <item>
      <title>Apify E-commerce Scraping Tool Alternative: Same Input</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:46:52 +0000</pubDate>
      <link>https://dev.to/odeeb/apify-e-commerce-scraping-tool-alternative-same-input-2o92</link>
      <guid>https://dev.to/odeeb/apify-e-commerce-scraping-tool-alternative-same-input-2o92</guid>
      <description>&lt;p&gt;E-commerce Product Scraper Pro is an alternative to Apify's E-commerce Scraping Tool that takes the same input: all 38 field names, types and options, so your input JSON runs after you change the actor ID. It adds monitor mode, per-input caps, variants and a proxy-country option. It supports fewer sites, and its pricing differs: cheaper per product, more expensive on residential and browser pages.&lt;/p&gt;

&lt;p&gt;I built E-commerce Product Scraper Pro. This page tries to give a fair account of when switching makes sense and when it doesn't. Facts about the E-commerce Scraping Tool come from its Store page, its input schema and its public pricing record, all checked on 2026-10-20. Apify maintains it, and its public actor record on the Apify API showed 848 users in the last 30 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays the same
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The input.&lt;/strong&gt; Every field of the E-commerce Scraping Tool's input schema exists in the alternative with the same name, type and options: &lt;code&gt;detailsUrls&lt;/code&gt;, &lt;code&gt;listingUrls&lt;/code&gt;, &lt;code&gt;keyword&lt;/code&gt;, &lt;code&gt;marketplaces&lt;/code&gt;, &lt;code&gt;scrapeMode&lt;/code&gt;, the AI summary fields, the review, seller and influencer sections, the Google Shopping ("search engine") section and the food-delivery section. The internal &lt;code&gt;disableFallbacks&lt;/code&gt; flag is accepted and ignored.&lt;/p&gt;

&lt;p&gt;We checked this against the E-commerce Scraping Tool's current build (0.0.120, published 2026-10-20): all 38 of its input keys and every option value of its select fields (251 &lt;code&gt;marketplaces&lt;/code&gt;, 226 &lt;code&gt;marketplacesReviews&lt;/code&gt;, 239 &lt;code&gt;countryCode&lt;/code&gt; values and the rest) are in the alternative's input schema. That includes &lt;code&gt;www.walmart.com.mx&lt;/code&gt; and &lt;code&gt;www.bodegaaurrera.com.mx&lt;/code&gt;, the two Mexican values in &lt;code&gt;marketplaces&lt;/code&gt; and &lt;code&gt;marketplacesReviews&lt;/code&gt;. Walmart Mexico is a supported site on the alternative (datacenter, prices in MXN); Bodega Aurrera is best-effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The main output fields.&lt;/strong&gt; Product rows keep the field names the E-commerce Scraping Tool documents: &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;inputUrl&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;offers&lt;/code&gt; (&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;priceCurrency&lt;/code&gt;), &lt;code&gt;brand&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;additionalProperties&lt;/code&gt;, &lt;code&gt;aiSummary&lt;/code&gt; and &lt;code&gt;keyword&lt;/code&gt;, plus the review, seller and influencer keys. The compatibility was checked against that tool's documented dataset views and samples, not field by field on its live output, so check your own integration against a sample run (script below).&lt;/p&gt;

&lt;h2&gt;
  
  
  What's added on top
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Addition&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxItemsPerStartUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A cap per input (per product URL, listing, keyword and marketplace, review input or storefront), so one large category can't use the whole run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;followPagination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn off next-page following on listings (on by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;includeVariants&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;variants[]&lt;/code&gt; array with each variant's SKU, options, price and stock (on by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proxyCountry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pin the residential exit to a country, to get prices in that market's currency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;monitorMode&lt;/code&gt;, &lt;code&gt;onlyChanges&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Compare with the previous run: &lt;code&gt;price-down&lt;/code&gt;, &lt;code&gt;price-up&lt;/code&gt;, &lt;code&gt;back-in-stock&lt;/code&gt;, &lt;code&gt;out-of-stock&lt;/code&gt;, &lt;code&gt;removed&lt;/code&gt;; optionally write only changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row also has flat, spreadsheet-friendly fields next to the nested ones (&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;, &lt;code&gt;originalPrice&lt;/code&gt;, &lt;code&gt;unitPrice&lt;/code&gt;, &lt;code&gt;inStock&lt;/code&gt;, &lt;code&gt;stockQty&lt;/code&gt;, &lt;code&gt;sku&lt;/code&gt;, &lt;code&gt;gtin&lt;/code&gt;, &lt;code&gt;images&lt;/code&gt;) and a &lt;code&gt;recordType&lt;/code&gt; (&lt;code&gt;product&lt;/code&gt;, &lt;code&gt;review&lt;/code&gt;, &lt;code&gt;seller&lt;/code&gt;, &lt;code&gt;influencerPost&lt;/code&gt;, &lt;code&gt;search-result&lt;/code&gt;, &lt;code&gt;delivery&lt;/code&gt;). The README publishes a supported-sites table with each site's status and route.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sites
&lt;/h3&gt;

&lt;p&gt;The E-commerce Scraping Tool's Store page names eBay, Etsy, Best Buy, Home Depot, Costco, Alibaba, Argos and Mercado Livre among the marketplaces it works with. On 2026-10-19 its pricing record added an "Unblocker" proxy for "stores that block even residential proxies".&lt;/p&gt;

&lt;p&gt;E-commerce Product Scraper Pro marks those sites &lt;strong&gt;unsupported&lt;/strong&gt;: eBay, Etsy, Best Buy, Home Depot, Lowe's, Costco, Alibaba, Allegro, Newegg, Argos, Mercado Livre (Brazil) and about 30 more. Inputs for them get a status row explaining why and aren't fetched or billed beyond the run's start fee. If your work depends on those marketplaces, keep using the E-commerce Scraping Tool for them. You can split your inputs and run each actor on the sites it handles.&lt;/p&gt;

&lt;p&gt;Sites the alternative lists as supported after its own cloud tests: Shopify and WooCommerce stores, Amazon (&lt;code&gt;amazon.com&lt;/code&gt; and &lt;code&gt;amazon.de&lt;/code&gt; supported, 22 other storefronts best-effort), Walmart (&lt;code&gt;walmart.com&lt;/code&gt; and &lt;code&gt;walmart.com.mx&lt;/code&gt;), Target, Zalando, John Lewis, M&amp;amp;S, Aldi UK and Waitrose. Several more are best-effort; the table on the actor page lists each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where errors go
&lt;/h3&gt;

&lt;p&gt;The E-commerce Scraping Tool writes failures to a named &lt;code&gt;errors&lt;/code&gt; dataset. The alternative writes &lt;strong&gt;one status row per input&lt;/strong&gt; (successes too) to a separate dataset, linked on the run's Output tab as "Status per input". Through the API it's &lt;code&gt;run["storageIds"]["datasets"]["status"]&lt;/code&gt;. The &lt;code&gt;error&lt;/code&gt; field uses the same error codes as the E-commerce Scraping Tool's documented errors, and &lt;code&gt;outcome&lt;/code&gt;, &lt;code&gt;blockedBy&lt;/code&gt;, &lt;code&gt;itemsFound&lt;/code&gt;, &lt;code&gt;itemsDelivered&lt;/code&gt; and &lt;code&gt;stopReason&lt;/code&gt; say what happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;Both are pay per event. Prices below are from the E-commerce Scraping Tool's public pricing record (in effect since 2026-10-19; its Store page headline says "from $1.00 / 1,000 product details") and from E-commerce Product Scraper Pro's pricing table, for Apify's FREE and GOLD plan tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;E-commerce Scraping Tool (FREE / GOLD)&lt;/th&gt;
&lt;th&gt;E-commerce Product Scraper Pro (FREE / GOLD)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run start&lt;/td&gt;
&lt;td&gt;$0.0001 / $0.00007&lt;/td&gt;
&lt;td&gt;$0.005 / $0.005 (per GB of memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;$0.006 / $0.001&lt;/td&gt;
&lt;td&gt;$0.0048 / $0.0008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listing page&lt;/td&gt;
&lt;td&gt;$0.0005 / $0.00026&lt;/td&gt;
&lt;td&gt;$0.0004 / $0.00021&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;$0.002 / $0.0007&lt;/td&gt;
&lt;td&gt;$0.0016 / $0.00056&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seller&lt;/td&gt;
&lt;td&gt;$0.003 / $0.001&lt;/td&gt;
&lt;td&gt;$0.0024 / $0.0008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI summary&lt;/td&gt;
&lt;td&gt;$0.0075 / $0.00275&lt;/td&gt;
&lt;td&gt;$0.006 / $0.0022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Residential proxy&lt;/td&gt;
&lt;td&gt;$0.003 / $0.0008 flat per product&lt;/td&gt;
&lt;td&gt;$0.018 / $0.014 per MB used to deliver the item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser rendering&lt;/td&gt;
&lt;td&gt;$0.0006 / $0.00051 flat per product&lt;/td&gt;
&lt;td&gt;$0.008 / $0.006 per render, plus residential MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unblocker proxy&lt;/td&gt;
&lt;td&gt;$0.0015 / $0.001 per product&lt;/td&gt;
&lt;td&gt;not offered (those sites are unsupported)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Shopping request&lt;/td&gt;
&lt;td&gt;no separate event in its pricing record&lt;/td&gt;
&lt;td&gt;$0.0045 / $0.0035 per successful request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The E-commerce Scraping Tool also has PLATINUM and DIAMOND tiers with lower prices (for example $0.0005 per product on DIAMOND). On the alternative, those plans pay the GOLD price.&lt;/p&gt;

&lt;p&gt;What that means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter sites (Shopify, WooCommerce, &lt;code&gt;amazon.com&lt;/code&gt;, Walmart, Target and others):&lt;/strong&gt; the alternative is cheaper once the per-product saving covers its higher start fee. On FREE that's from 5 products per run: the start fee costs $0.005 − $0.0001 = $0.0049 more, and each product saves $0.0012. On GOLD it's from 25 products per run ($0.00493 ÷ $0.0002 = 24.7).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Residential sites:&lt;/strong&gt; usually more expensive on the alternative. Its README example is 100 &lt;code&gt;amazon.co.uk&lt;/code&gt; products at $0.46–$0.71 on GOLD. If all 100 needed the E-commerce Scraping Tool's residential fee, they'd cost 100 × ($0.001 + $0.0008) + $0.00007 ≈ $0.18 there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser sites (Noon, Wayfair, Sephora and others):&lt;/strong&gt; more expensive on the alternative, by its design: it bills each render and its traffic instead of a flat fee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring:&lt;/strong&gt; with &lt;code&gt;onlyChanges&lt;/code&gt;, unchanged products are billed as "product checked" ($0.0012 FREE, $0.0002 GOLD) instead of a full product. The E-commerce Scraping Tool's input has no monitor-mode option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternative doesn't charge for items that never delivered: blocked or empty pages cost nothing, and their residential traffic and renders aren't billed. Apify's free plan includes $5 of usage every month, with no credit card required. Neither actor needs your own proxies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python: run your existing input and check the fields you use
&lt;/h2&gt;

&lt;p&gt;Paste your current input into &lt;code&gt;RUN_INPUT&lt;/code&gt; and list the fields your code reads:&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;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&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="c1"&gt;# Your existing E-commerce Scraping Tool input, unchanged
&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;detailsUrls&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;url&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;https://www.skullcandy.com/products/crusher-540-active&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;listingUrls&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;url&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;https://www.deathwishcoffee.com/collections/coffee&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;maxProductResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# The fields your integration reads today (edit to match your code)
&lt;/span&gt;&lt;span class="n"&gt;FIELDS_I_USE&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;url&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;inputUrl&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;name&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;image&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;offers&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;brand&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;description&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;additionalProperties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;constructive_calm/ecommerce-product-scraper-pro&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="n"&gt;RUN_INPUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows |&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordType&lt;/span&gt;&lt;span class="sh"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&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;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FIELDS_I_USE&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing fields:&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="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offers shape:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offers&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;| brand shape:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Per-input outcomes: a separate "status" dataset (one row per input), not an "errors" dataset
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;storageIds&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;datasets&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;status&lt;/span&gt;&lt;span class="sh"&gt;"&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;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputField&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;|&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="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errorDescription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on 2026-10-20 (&lt;code&gt;pip install apify-client&lt;/code&gt;, &lt;code&gt;APIFY_TOKEN&lt;/code&gt; set):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUCCEEDED | 5 rows | Counter({'product': 5})
missing fields: none
offers shape: {'price': 229.99, 'priceCurrency': 'USD'} | brand shape: {'slogan': 'Skullcandy US'}
detailsUrls ok None | 
listingUrls ok None | 22 product(s) found on 1 page(s); 4 delivered; 18 not scraped; stopped: maxProductResults
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The input is the E-commerce Scraping Tool's own field set (&lt;code&gt;detailsUrls&lt;/code&gt;, &lt;code&gt;listingUrls&lt;/code&gt;, &lt;code&gt;maxProductResults&lt;/code&gt;) and ran unchanged. To compare values, not just field names, run the same input on both actors and join the rows on &lt;code&gt;url&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check your sites&lt;/strong&gt; against the supported-sites table on the &lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;actor page&lt;/a&gt;. Move unsupported marketplaces to a separate input that stays on the E-commerce Scraping Tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check your marketplace values.&lt;/strong&gt; Every value the E-commerce Scraping Tool offers validates on the alternative, but a value that validates isn't always a site that works. Look up each marketplace in your &lt;code&gt;marketplaces&lt;/code&gt; and &lt;code&gt;marketplacesReviews&lt;/code&gt; lists in the table: &lt;code&gt;www.walmart.com.mx&lt;/code&gt; is supported, &lt;code&gt;www.bodegaaurrera.com.mx&lt;/code&gt; best-effort, and values such as &lt;code&gt;www.ebay.com&lt;/code&gt; or &lt;code&gt;www.homedepot.com&lt;/code&gt; only return a status row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change the actor ID&lt;/strong&gt; everywhere it appears: API calls, saved tasks, schedules, webhooks and integrations (Make, Zapier, n8n, Google Sheets). &lt;code&gt;apify/e-commerce-scraping-tool&lt;/code&gt; becomes &lt;code&gt;constructive_calm/ecommerce-product-scraper-pro&lt;/code&gt;. Saved tasks belong to an actor, so create new tasks from the same input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a sample on both&lt;/strong&gt; with a small &lt;code&gt;maxProductResults&lt;/code&gt;, and compare the row counts, prices, currencies and stock flags for the same URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check output field mapping&lt;/strong&gt; with the script above: the fields you read, the &lt;code&gt;offers&lt;/code&gt; and &lt;code&gt;brand&lt;/code&gt; shapes, and any &lt;code&gt;additionalProperties&lt;/code&gt; keys you depend on. Per-site extras in &lt;code&gt;additionalProperties&lt;/code&gt; differ between the two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move error handling&lt;/strong&gt; from the &lt;code&gt;errors&lt;/code&gt; dataset to the status dataset, and decide what counts as a failure (&lt;code&gt;blocked&lt;/code&gt;, &lt;code&gt;not-found&lt;/code&gt;, &lt;code&gt;restricted&lt;/code&gt;, or a zero-row run).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set a cost cap&lt;/strong&gt; with the run's maximum cost (&lt;code&gt;maxTotalChargeUsd&lt;/code&gt; in the API), and compare the charged events of the sample run with your current costs, especially on residential and browser sites. Read them from the run's &lt;code&gt;OUTPUT&lt;/code&gt; record (&lt;code&gt;billing.chargedEvents&lt;/code&gt;): the &lt;code&gt;chargedEventCounts&lt;/code&gt; on the run object that &lt;code&gt;call()&lt;/code&gt; returns can be incomplete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run both in parallel&lt;/strong&gt; for a few days before turning the old schedule off.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Holiday season: switch before the sale, not during it
&lt;/h2&gt;

&lt;p&gt;If you track prices for Black Friday (Friday, November 27, 2026) and Cyber Monday (November 30), do the migration and the side-by-side runs in early November. Changing the scraper mid-season breaks the continuity of your price history, and monitor mode needs at least one run as its baseline. Once switched, &lt;code&gt;monitorMode&lt;/code&gt; gives you &lt;code&gt;previousPrice&lt;/code&gt; and &lt;code&gt;priceChangePct&lt;/code&gt; on every row. See the &lt;a href="https://datatooly.xyz/guides/black-friday-price-tracker/" rel="noopener noreferrer"&gt;Black Friday price tracker&lt;/a&gt; for a schedule, and the &lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;holiday sales calendar&lt;/a&gt; for the dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/competitor-price-monitoring-scraper/" rel="noopener noreferrer"&gt;Competitor price monitoring&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/price-drop-alerts-email-slack/" rel="noopener noreferrer"&gt;Price drop alerts to email or Slack&lt;/a&gt;, built on monitor mode.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/scrape-product-variants-sizes-colors/" rel="noopener noreferrer"&gt;Scrape product variants&lt;/a&gt;, one of the added options.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker builder&lt;/a&gt; produces a ready-to-run input with the shared field names, and shows a fixed example of the output.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built E-commerce Product Scraper Pro and this site. I'm not affiliated with Apify beyond publishing actors on its Store.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://apify.com/apify/e-commerce-scraping-tool?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Scraping Tool on the Apify Store&lt;/a&gt; (marketplaces named, pricing headline), its public actor record and its &lt;code&gt;latest&lt;/code&gt; build 0.0.120 on the Apify API (pricing per event, input schema, 30-day users), checked 2026-10-20; the E-commerce Product Scraper Pro README; and an actor run on 2026-10-20.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>webscraping</category>
      <category>python</category>
      <category>api</category>
    </item>
    <item>
      <title>SEC EDGAR API: Submissions JSON and Ticker to CIK</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:46:30 +0000</pubDate>
      <link>https://dev.to/odeeb/sec-edgar-api-submissions-json-and-ticker-to-cik-9f5</link>
      <guid>https://dev.to/odeeb/sec-edgar-api-submissions-json-and-ticker-to-cik-9f5</guid>
      <description>&lt;p&gt;Yes, EDGAR has an API, and it's free with no API key. Map a ticker to a CIK with &lt;code&gt;https://www.sec.gov/files/company_tickers.json&lt;/code&gt;, then fetch &lt;code&gt;https://data.sec.gov/submissions/CIK##########.json&lt;/code&gt;, with the CIK zero-padded to 10 digits, to get the company's details and full filing history. The one requirement is a User-Agent header with your name and email.&lt;/p&gt;

&lt;p&gt;This page covers the two building blocks every EDGAR project starts with: ticker-to-CIK and the submissions API. The code was run on 2026-10-14.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SEC's APIs at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;Key?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ticker → CIK map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://www.sec.gov/files/company_tickers.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ticker → CIK + exchange&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://www.sec.gov/files/company_tickers_exchange.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filing history + metadata&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://data.sec.gov/submissions/CIK##########.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XBRL financials&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;https://data.sec.gov/api/xbrl/companyfacts/CIK##########.json&lt;/code&gt; (and &lt;code&gt;companyconcept&lt;/code&gt;, &lt;code&gt;frames&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text search&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://efts.sec.gov/LATEST/search-index?q=...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filing documents&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://www.sec.gov/Archives/edgar/data/{cik}/{accession}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The SEC's &lt;a href="https://www.sec.gov/search-filings/edgar-application-programming-interfaces" rel="noopener noreferrer"&gt;EDGAR APIs page&lt;/a&gt; says data.sec.gov's APIs "do not require any authentication or API keys to access." What is required is a declared User-Agent and a rate of no more than 10 requests per second; see the &lt;a href="https://datatooly.xyz/guides/sec-edgar-rate-limit-user-agent/" rel="noopener noreferrer"&gt;SEC rate limit and User-Agent guide&lt;/a&gt;. A search for "SEC API key" often leads to filer API tokens. Those exist, but they are for submitting filings to EDGAR, not for reading data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ticker to CIK
&lt;/h2&gt;

&lt;p&gt;EDGAR identifies every filer by a Central Index Key (CIK). The SEC says CIKs "remain unique to the filer; they are not recycled." &lt;code&gt;company_tickers.json&lt;/code&gt; maps tickers to CIKs:&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;-s&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"Data Tooly research contact@example.com"&lt;/span&gt; &lt;span class="s2"&gt;"https://www.sec.gov/files/company_tickers.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"cik_str"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1045810&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"NVDA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"NVIDIA CORP"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"cik_str"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;320193&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Apple Inc."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"cik_str"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1652044&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GOOGL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Alphabet Inc."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What to know about the file (checked on 2026-10-14):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The top-level keys (&lt;code&gt;"0"&lt;/code&gt;, &lt;code&gt;"1"&lt;/code&gt;, ...) are just row numbers. Build your own index by ticker.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cik_str&lt;/code&gt; is an &lt;strong&gt;integer&lt;/strong&gt; despite its name. Pad it yourself: &lt;code&gt;f"CIK{cik:010d}"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It held 10,447 tickers for 8,032 CIKs. 1,440 companies have more than one ticker; GOOGL and GOOG both map to 1652044, for example.&lt;/li&gt;
&lt;li&gt;Share classes use a dash: Berkshire Class B is &lt;code&gt;BRK-B&lt;/code&gt;, not &lt;code&gt;BRK.B&lt;/code&gt;. Normalize user input.&lt;/li&gt;
&lt;li&gt;The SEC says it updates these files periodically "but do[es] not guarantee accuracy or scope". Delisted tickers drop out. For historical work, keep the CIK, not the ticker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;company_tickers_exchange.json&lt;/code&gt; has the same mapping in a columnar layout with the exchange added: &lt;code&gt;{"fields": ["cik", "name", "ticker", "exchange"], "data": [[1045810, "NVIDIA CORP", "NVDA", "Nasdaq"], ...]}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The submissions API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"Data Tooly research contact@example.com"&lt;/span&gt; &lt;span class="s2"&gt;"https://data.sec.gov/submissions/CIK0000320193.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top-level fields for Apple: &lt;code&gt;cik&lt;/code&gt;, &lt;code&gt;entityType&lt;/code&gt;, &lt;code&gt;sic&lt;/code&gt;, &lt;code&gt;sicDescription&lt;/code&gt;, &lt;code&gt;ownerOrg&lt;/code&gt;, &lt;code&gt;insiderTransactionForOwnerExists&lt;/code&gt;, &lt;code&gt;insiderTransactionForIssuerExists&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;tickers&lt;/code&gt;, &lt;code&gt;exchanges&lt;/code&gt;, &lt;code&gt;ein&lt;/code&gt;, &lt;code&gt;lei&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;website&lt;/code&gt;, &lt;code&gt;investorWebsite&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;fiscalYearEnd&lt;/code&gt;, &lt;code&gt;stateOfIncorporation&lt;/code&gt;, &lt;code&gt;stateOfIncorporationDescription&lt;/code&gt;, &lt;code&gt;addresses&lt;/code&gt;, &lt;code&gt;phone&lt;/code&gt;, &lt;code&gt;flags&lt;/code&gt;, &lt;code&gt;formerNames&lt;/code&gt;, &lt;code&gt;filings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;filings&lt;/code&gt; has two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;filings.recent&lt;/code&gt;&lt;/strong&gt;: per the SEC, "at least one year's of filing or to 1,000 (whichever is more) of the most recent filings in a compact columnar data array". Apple's had 1,001 rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;filings.files&lt;/code&gt;&lt;/strong&gt;: extra JSON files for older filings, each with &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;filingCount&lt;/code&gt;, &lt;code&gt;filingFrom&lt;/code&gt; and &lt;code&gt;filingTo&lt;/code&gt;. Apple had one, &lt;code&gt;CIK0000320193-submissions-001.json&lt;/code&gt;, with 1,264 filings from 1994-01-26 to 2015-09-15.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;recent&lt;/code&gt; is &lt;strong&gt;column-oriented&lt;/strong&gt;: one array per field (&lt;code&gt;accessionNumber&lt;/code&gt;, &lt;code&gt;filingDate&lt;/code&gt;, &lt;code&gt;reportDate&lt;/code&gt;, &lt;code&gt;acceptanceDateTime&lt;/code&gt;, &lt;code&gt;act&lt;/code&gt;, &lt;code&gt;form&lt;/code&gt;, &lt;code&gt;fileNumber&lt;/code&gt;, &lt;code&gt;filmNumber&lt;/code&gt;, &lt;code&gt;items&lt;/code&gt;, &lt;code&gt;core_type&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;isXBRL&lt;/code&gt;, &lt;code&gt;isInlineXBRL&lt;/code&gt;, &lt;code&gt;isXBRLNumeric&lt;/code&gt;, &lt;code&gt;primaryDocument&lt;/code&gt;, &lt;code&gt;primaryDocDescription&lt;/code&gt;). Row &lt;code&gt;i&lt;/code&gt; is the &lt;code&gt;i&lt;/code&gt;th element of every array.&lt;/p&gt;

&lt;p&gt;The SEC says the submissions API updates in real time "with a typical processing delay of less than a second".&lt;/p&gt;

&lt;h2&gt;
  
  
  Python: ticker → CIK → filings → document URLs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;HEADERS&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;User-Agent&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;Data Tooly research contact@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# use your own name + email
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# stay under 10 requests/second
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Ticker -&amp;gt; CIK, from the SEC's own mapping file.
&lt;/span&gt;&lt;span class="n"&gt;tickers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.sec.gov/files/company_tickers.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;by_ticker&lt;/span&gt; &lt;span class="o"&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;ticker&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="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;tickers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by_ticker&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tickers;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_ticker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;by_ticker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BRK-B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cik_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;by_ticker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cik_str&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Submissions: company metadata plus filing history.
&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cik_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aapl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://data.sec.gov/submissions/CIK&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;010&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;sub&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;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tickers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exchanges&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;| SIC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sicDescription&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;| FY ends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fiscalYearEnd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# `recent` is column-oriented: one list per field. Zip it into rows.
&lt;/span&gt;&lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filings&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;recent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&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;values&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recent filings; older pages:&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="n"&gt;f&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filings&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;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filing_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&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="n"&gt;folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.sec.gov/Archives/edgar/data/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&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;accessionNumber&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&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="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&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;primaryDocument&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&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;accessionNumber&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-index.htm&lt;/span&gt;&lt;span class="sh"&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;form&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-K&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;10-Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)][:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filing_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&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="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;form&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;filingDate&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;period&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;reportDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Older filings live in extra files listed under filings.files.
&lt;/span&gt;&lt;span class="n"&gt;older&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://data.sec.gov/submissions/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;filings&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;files&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&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="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oldest filing on record:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;older&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filingDate&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;| rows in older file:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;older&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accessionNumber&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;Output on 2026-10-14:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10447 tickers; {'cik_str': 320193, 'ticker': 'AAPL', 'title': 'Apple Inc.'} {'cik_str': 1067983, 'ticker': 'BRK-B', 'title': 'BERKSHIRE HATHAWAY INC'}
Apple Inc. ['AAPL'] ['Nasdaq'] | SIC 3571 Electronic Computers | FY ends 0926
1001 recent filings; older pages: ['CIK0000320193-submissions-001.json']
10-Q 2026-07-31 period 2026-06-27 https://www.sec.gov/Archives/edgar/data/320193/000032019326000020/aapl-20260627.htm
10-Q 2026-05-01 period 2026-03-28 https://www.sec.gov/Archives/edgar/data/320193/000032019326000013/aapl-20260328.htm
10-Q 2026-01-30 period 2025-12-27 https://www.sec.gov/Archives/edgar/data/320193/000032019326000006/aapl-20251227.htm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The older file is a bare column dictionary, with no &lt;code&gt;filings.recent&lt;/code&gt; wrapper, which is why the last step reads &lt;code&gt;older["filingDate"]&lt;/code&gt; directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pad the CIK in data.sec.gov URLs.&lt;/strong&gt; &lt;code&gt;submissions/CIK320193.json&lt;/code&gt; returned HTTP 404 with an XML body. Archive paths (&lt;code&gt;/Archives/edgar/data/320193/...&lt;/code&gt;) take the unpadded number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cik&lt;/code&gt; comes back as a string here.&lt;/strong&gt; The submissions JSON has &lt;code&gt;"cik": "0000320193"&lt;/code&gt; (padded string), while &lt;code&gt;company_tickers.json&lt;/code&gt; has an integer. Normalize with &lt;code&gt;int()&lt;/code&gt; before you compare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accession numbers appear in two forms.&lt;/strong&gt; The JSON uses &lt;code&gt;0000320193-25-000079&lt;/code&gt;. Folder paths drop the dashes: &lt;code&gt;000032019325000079&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fiscalYearEnd&lt;/code&gt; is &lt;code&gt;MMDD&lt;/code&gt;.&lt;/strong&gt; Apple's &lt;code&gt;0926&lt;/code&gt; means its fiscal year ends around September 26.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent isn't everything.&lt;/strong&gt; For a full history, also fetch every file in &lt;code&gt;filings.files&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk option.&lt;/strong&gt; The SEC publishes all submissions as one nightly ZIP: &lt;code&gt;https://www.sec.gov/Archives/edgar/daily-index/bulkdata/submissions.zip&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related guides for this API
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/sec-edgar-companyfacts-xbrl-frames-api/" rel="noopener noreferrer"&gt;Company facts, company concept and frames&lt;/a&gt;: the financial numbers, using the same CIK.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/sec-edgar-full-text-search-api/" rel="noopener noreferrer"&gt;EDGAR full-text search API&lt;/a&gt;: find filings by their text, filtered by CIK.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/sec-edgar-rate-limit-user-agent/" rel="noopener noreferrer"&gt;SEC rate limits and User-Agent rules&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/sec-edgar-api-python-guide/" rel="noopener noreferrer"&gt;SEC EDGAR API in Python&lt;/a&gt;, the overview guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/sec-edgar-search/" rel="noopener noreferrer"&gt;SEC EDGAR query builder&lt;/a&gt; takes plain tickers, so you don't have to look up or pad CIKs, plus a mode (filings, financials, full-text search or insider trades) and form types. It shows the output shape. It builds the query rather than fetching live, because browsers can't send the User-Agent the SEC requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  At scale
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/constructive_calm/sec-edgar-scraper?fpr=v77kxu" rel="noopener noreferrer"&gt;SEC EDGAR Scraper actor&lt;/a&gt; on Apify resolves tickers to CIKs and pulls filings, financials, Form 4 insider trades, 13F holdings, 13D/G stakes and parsed 8-K items, with the User-Agent and pacing handled. It exports JSON, CSV or Excel. It's free to start, then pay-as-you-go.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I build the datatooly query builder and the Apify actor. The endpoints above work without either.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://www.sec.gov/search-filings/edgar-application-programming-interfaces" rel="noopener noreferrer"&gt;SEC: EDGAR Application Programming Interfaces&lt;/a&gt;, &lt;a href="https://www.sec.gov/search-filings/edgar-search-assistance/accessing-edgar-data" rel="noopener noreferrer"&gt;SEC: Accessing EDGAR Data&lt;/a&gt;, &lt;a href="https://www.sec.gov/about/webmaster-frequently-asked-questions" rel="noopener noreferrer"&gt;SEC webmaster FAQ&lt;/a&gt;, and live responses on 2026-10-14.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Search Hacker News by Date Range or Points (API)</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:46:09 +0000</pubDate>
      <link>https://dev.to/odeeb/search-hacker-news-by-date-range-or-points-api-n9i</link>
      <guid>https://dev.to/odeeb/search-hacker-news-by-date-range-or-points-api-n9i</guid>
      <description>&lt;p&gt;To search Hacker News by date, call &lt;code&gt;https://hn.algolia.com/api/v1/search_by_date&lt;/code&gt; with &lt;code&gt;numericFilters=created_at_i&amp;gt;=START,created_at_i&amp;lt;END&lt;/code&gt; in Unix seconds. To filter by score, add &lt;code&gt;points&amp;gt;100&lt;/code&gt; to the same parameter, comma-separated. Use &lt;code&gt;/search&lt;/code&gt; instead when you want the highest-scoring stories first. No API key is needed. One query returns at most 1,000 hits, so split long ranges.&lt;/p&gt;

&lt;p&gt;Every example below ran against the live API on 2026-10-14. For the full parameter list, see the &lt;a href="https://datatooly.xyz/guides/hacker-news-algolia-api-reference/" rel="noopener noreferrer"&gt;HN Algolia API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The filters you need
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Posted in September 2026 (UTC)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numericFilters=created_at_i&amp;gt;=1788220800,created_at_i&amp;lt;1790812800&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;At least 100 points&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numericFilters=points&amp;gt;=100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Busy discussions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numericFilters=num_comments&amp;gt;=200&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;numericFilters=points&amp;gt;=100,num_comments&amp;gt;=200&lt;/code&gt; (comma = AND)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;numericFilters=(points&amp;gt;5000,num_comments&amp;gt;4000)&lt;/code&gt; (parentheses = OR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stories only&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tags=story&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show HN or Ask HN&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tags=show_hn&lt;/code&gt; or &lt;code&gt;tags=ask_hn&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Newest first&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;/search_by_date&lt;/code&gt; endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Highest score first&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;/search&lt;/code&gt; endpoint with an empty &lt;code&gt;query&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;created_at_i&lt;/code&gt; is in seconds, not milliseconds, and in UTC. The official page's own example is &lt;code&gt;search_by_date?tags=story&amp;amp;numericFilters=created_at_i&amp;gt;X,created_at_i&amp;lt;Y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which endpoint sorts how.&lt;/strong&gt; The same September window gave a different first page on each endpoint. &lt;code&gt;/search_by_date&lt;/code&gt; returned stories from the last minutes of the window, each with a handful of points. &lt;code&gt;/search&lt;/code&gt; returned the month's biggest stories. The official page says &lt;code&gt;/search&lt;/code&gt; is "sorted by relevance, then points, then number of comments", and with no query, relevance is a tie, so points decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The top stories of a month, with curl
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Top 5 stories of September 2026 (UTC) by points&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://hn.algolia.com/api/v1/search?tags=story&amp;amp;numericFilters=created_at_i%3E%3D1788220800,created_at_i%3C1790812800&amp;amp;hitsPerPage=5&amp;amp;attributesToRetrieve=title,points,created_at"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys, json; [print(h['points'], h['created_at'][:10], h['title']) for h in json.load(sys.stdin)['hits']]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2391 2026-09-15 Show HN: An e-ink frame that hears birds and draws them as 1800s illustrations
2301 2026-09-04 Discovery of a new OpenAI agent message board
2279 2026-09-03 GPT-6 Astra
2230 2026-09-03 .name Termination
2054 2026-09-08 Navier-Stokes – Tristan Buckmaster [pdf]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;%3E%3D&lt;/code&gt; is &lt;code&gt;&amp;gt;=&lt;/code&gt; and &lt;code&gt;%3C&lt;/code&gt; is &lt;code&gt;&amp;lt;&lt;/code&gt;. To get the timestamps in Python, use &lt;code&gt;int(datetime(2026, 9, 1, tzinfo=timezone.utc).timestamp())&lt;/code&gt;; that gives &lt;code&gt;1788220800&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The last 7 days, in JavaScript
&lt;/h2&gt;

&lt;p&gt;The API sends &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;, so this works in a browser as well as in Node 18+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Stories from the last 7 days with 300+ points, highest first.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;story&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;numericFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created_at_i&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;since&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,points&amp;gt;=300`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hitsPerPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://hn.algolia.com/api/v1/search?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nbHits&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nbHits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; stories`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on 2026-10-14:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;98 stories
2123 2026-10-07 Margaret Hamilton has died
1440 2026-10-09 Triple-A Minesweeper
1407 2026-10-13 Gemini 4 Argon
1384 2026-10-09 Cloudflare acquires Deno
1134 2026-10-08 Why isn't the industry freaking out about DeepSeek 4.1 Flash?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More than 1,000 results: split the window
&lt;/h2&gt;

&lt;p&gt;A single query stops at 1,000 hits. Past that, the API returns no hits and the message "you can only fetch the 1000 hits for this query". The fix is to cut the date range into slices that each hold 1,000 or fewer and join them. This function does it automatically by halving any window that is too big:&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;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://hn.algolia.com/api/v1/search_by_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""'&lt;/span&gt;&lt;span class="s"&gt;2025-01-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; Unix seconds (UTC).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 1, 2, 4 s on 429/5xx
&lt;/span&gt;    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&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;Every hit with start &amp;lt;= created_at_i &amp;lt; end. Splits the window while a
    slice holds more than 1,000 hits, because deeper pages are refused.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;nf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at_i&amp;gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,created_at_i&amp;lt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;numericFilters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hitsPerPage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nbHits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;search_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;search_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;show_hn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;points&amp;gt;=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;objectID&lt;/span&gt;&lt;span class="sh"&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;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Show HN posts with 100+ points in 2025 &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unique) in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&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;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])[:&lt;/span&gt;&lt;span class="mi"&gt;5&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;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# A window that needs splitting: every story posted in one week.
&lt;/span&gt;    &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;week&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;story&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-09-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-09-08&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; stories posted 2026-09-01..07 in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&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;Output on 2026-10-14:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;742 Show HN posts with 100+ points in 2025 (742 unique) in 3.5s
3346 2025-12-09 Show HN: Gemini Pro 3 imagines the HN front page 10 years from now
3237 2025-01-22 Show HN: I made an open-source laptop from scratch
1581 2025-02-26 Show HN: I got laid off from Meta and created a minor hit on Steam
1557 2025-12-20 Show HN: Jmail – Google Suite for Epstein files
1539 2025-06-27 Show HN: I'm an airline pilot – I built interactive graphs/globes of my flights
7020 stories posted 2026-09-01..07 in 18.5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first query fit in one request. The second didn't: 7,020 stories in one week, so the function split the week into slices. As a check, I asked for each of the seven days separately; their &lt;code&gt;nbHits&lt;/code&gt; added up to exactly 7,020, and every returned ID was unique and inside the window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Half-open windows.&lt;/strong&gt; Use &lt;code&gt;&amp;gt;=&lt;/code&gt; at the start and &lt;code&gt;&amp;lt;&lt;/code&gt; at the end, as above. With &lt;code&gt;&amp;gt;=&lt;/code&gt; and &lt;code&gt;&amp;lt;=&lt;/code&gt; on both ends, an item posted exactly on a boundary second lands in two slices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust &lt;code&gt;nbHits&lt;/code&gt; only when it's exact.&lt;/strong&gt; For broad queries the response sets &lt;code&gt;exhaustiveNbHits: false&lt;/code&gt;, and the count is an estimate. The splitting function only compares &lt;code&gt;nbHits&lt;/code&gt; with 1,000 on narrow windows, where the counts I saw were exact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;points&lt;/code&gt; is current, not historical.&lt;/strong&gt; A story's points keep changing while it's active, and the filter uses the latest value. "Stories that had 100 points on day one" isn't something this API can answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A wrong field name fails silently.&lt;/strong&gt; &lt;code&gt;numericFilters=score&amp;gt;10&lt;/code&gt; returned 0 hits and no error. The fields are &lt;code&gt;created_at_i&lt;/code&gt;, &lt;code&gt;points&lt;/code&gt; and &lt;code&gt;num_comments&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time zones.&lt;/strong&gt; Timestamps are UTC. "September" in New York starts four hours later than in UTC; build your boundaries in the time zone you mean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limit.&lt;/strong&gt; Splitting multiplies requests. The official limit is 10,000 per hour per IP, and the one-week split above used 17 requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related guides for this API
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/hacker-news-algolia-api-reference/" rel="noopener noreferrer"&gt;HN Algolia API reference&lt;/a&gt;: all endpoints, tags and limits.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/hacker-news-api-get-all-comments/" rel="noopener noreferrer"&gt;Get all comments for a Hacker News story&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/hacker-news-firebase-api-vs-algolia/" rel="noopener noreferrer"&gt;Hacker News Firebase API vs Algolia&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/hacker-news-search-api/" rel="noopener noreferrer"&gt;The Hacker News Search API: free, no key&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/hacker-news-search/" rel="noopener noreferrer"&gt;Hacker News search tool&lt;/a&gt; runs these searches live in your browser. Type a keyword, choose stories, comments, Show HN or Ask HN, sort by relevance or date, and set a minimum score. The results are real, with points, comment counts, author and date.&lt;/p&gt;

&lt;h2&gt;
  
  
  At scale
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/constructive_calm/hacker-news-scraper?fpr=v77kxu" rel="noopener noreferrer"&gt;Hacker News Scraper actor&lt;/a&gt; on Apify takes the same filters as inputs: a keyword, a date range in Unix seconds (&lt;code&gt;dateFromUnix&lt;/code&gt;, &lt;code&gt;dateToUnix&lt;/code&gt;), minimum score and comments, and a domain filter. It returns flat rows you can export as JSON, CSV or Excel, optionally with comment trees and author karma, and it runs on a schedule. It's free to start, then pay-as-you-go.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I build the datatooly tool and the Apify actor. The scripts above use only the public API.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://hn.algolia.com/api" rel="noopener noreferrer"&gt;HN Search API (official page)&lt;/a&gt;, &lt;a href="https://www.algolia.com/doc/api-reference/api-parameters/numericFilters" rel="noopener noreferrer"&gt;Algolia: numericFilters&lt;/a&gt;, and live responses from &lt;code&gt;hn.algolia.com/api/v1&lt;/code&gt; on 2026-10-14.&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>api</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Scrape Product Variants: Sizes, Colors and Price per SKU</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:45:47 +0000</pubDate>
      <link>https://dev.to/odeeb/scrape-product-variants-sizes-colors-and-price-per-sku-3a3o</link>
      <guid>https://dev.to/odeeb/scrape-product-variants-sizes-colors-and-price-per-sku-3a3o</guid>
      <description>&lt;p&gt;To scrape product variants, read every variant of a product, not just the price on the page. Each variant (a size, color or pack size) has its own SKU, option values, price and stock. On Shopify, add &lt;code&gt;.js&lt;/code&gt; to a product URL to get them as JSON. For Shopify, Walmart, Zalando, Target and other stores, an Apify actor returns them as a &lt;code&gt;variants[]&lt;/code&gt; array on each product row.&lt;/p&gt;

&lt;p&gt;This guide shows both methods with real output from 2026-10-20, what a variant row contains, which sites give per-variant prices, and the traps that make variant data wrong without an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the product price isn't enough
&lt;/h2&gt;

&lt;p&gt;A product page shows one selected variant. The price at the top is that variant's price, and the stock flag is that variant's stock. Here's the same Walmart listing in one of our runs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;In stock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5 Pack (the URL's own variant)&lt;/td&gt;
&lt;td&gt;$19.40&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 Pack&lt;/td&gt;
&lt;td&gt;$7.76&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single&lt;/td&gt;
&lt;td&gt;$3.88&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you had stored only the product-level fields, you'd have recorded "$19.40, out of stock" for a cable you could buy that day. Variant-level data fixes three common problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price ranges.&lt;/strong&gt; On one Zalando sneaker page in an October 17 run, most sizes cost €39.95 but size 36 cost €64.95.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial stock.&lt;/strong&gt; A Columbia rain jacket had 271 color and size combinations, and 207 of them were out of stock, while the product as a whole said "in stock".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matching.&lt;/strong&gt; Retailers sell a pack size or color under its own SKU or GTIN, so matching across stores works only at the variant level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method 1: Shopify's &lt;code&gt;.js&lt;/code&gt; endpoint (no tools)
&lt;/h2&gt;

&lt;p&gt;Every Shopify store serves a product's variants at the product URL plus &lt;code&gt;.js&lt;/code&gt;. No key or login is needed:&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.skullcandy.com/products/crusher-540-active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&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;User-Agent&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;Mozilla/5.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&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;| options:&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="n"&gt;o&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&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;| price_min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&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_min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&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_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variants&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&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="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;was=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;compare_at_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;compare_at_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  available=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&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;Output on 2026-10-20:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Crusher® 540 Active | options: ['Color'] | price_min 209.99 price_max 229.99
  50564609998905  S6EVW-U054  Skullcandy X Baker Skateboards    229.99  was=None  available=True
  45146487652409  S6EVW-T013  Smoke                             209.99  was=None  available=True
  45138862538809  S6EVW-T016  Forest                            209.99  was=None  available=True
  44402118393913  S6EVW-T005  Soft                              209.99  was=None  available=True
  44494413594681  S6EVW-T992  Coal                              209.99  was=None  available=True
  44402118524985  S6EVW-T993  Concrete                          209.99  was=None  available=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt; and &lt;code&gt;compare_at_price&lt;/code&gt; are &lt;strong&gt;integers in cents&lt;/strong&gt;. Divide by 100.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.json&lt;/code&gt; version (&lt;code&gt;/products/handle.json&lt;/code&gt;) returns the price as a string like &lt;code&gt;"229.99"&lt;/code&gt; and has &lt;strong&gt;no &lt;code&gt;available&lt;/code&gt; field&lt;/strong&gt;. Use &lt;code&gt;.js&lt;/code&gt; when you need stock.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To list every product of a store before you read variants, see &lt;a href="https://datatooly.xyz/guides/shopify-products-json-pagination/" rel="noopener noreferrer"&gt;Shopify products.json pagination&lt;/a&gt;. This method only works on Shopify. Other platforms put variants in JSON-LD, in embedded page state, or in a separate API, and each needs its own parser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: variants from any supported store, through the API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Product Scraper Pro&lt;/a&gt; on Apify reads variants on Shopify, WooCommerce, Walmart, Target, Zalando and the other stores in its supported-sites table, and puts them in a &lt;code&gt;variants[]&lt;/code&gt; array on each product row. &lt;code&gt;includeVariants&lt;/code&gt; is on by default. This script runs it on three stores and writes one CSV row per variant:&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;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;constructive_calm/ecommerce-product-scraper-pro&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;detailsUrls&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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;https://www.skullcandy.com/products/crusher-540-active&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;url&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;https://www.zalando.de/puma-club-ii-era-unisex-sneaker-low-whiteblackvapor-gray-pu115o0sb-a11.html&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;url&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;https://www.walmart.com/ip/5-pack-onn-3ft-USB-C-USB-C-Braided-Charging-Cable-Fast-Charging-USB-IF-Certified-White-Compatible-iPhone-17-16-15-Series-Samsung-Galaxy-Note-Pixel-De/20232964243&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;includeVariants&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# the default; shown for clarity
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxProductResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&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;p&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variants&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variants&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sold_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variants&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inStock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | page price &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; variants, price range &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sold_out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; sold out&lt;/span&gt;&lt;span class="sh"&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# one flat row per variant
&lt;/span&gt;        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site&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;product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variant_sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&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;options&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;; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;items&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;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&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;was_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;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;originalPrice&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;in_stock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inStock&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;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variants.csv&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;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variant rows written to variants.csv&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;Output on 2026-10-20 (&lt;code&gt;pip install apify-client&lt;/code&gt;, and &lt;code&gt;APIFY_TOKEN&lt;/code&gt; set to your Apify API token):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skullcandy.com: Crusher® 540 Active | page price 229.99 USD | 6 variants, price range 209.99-229.99, 0 sold out
zalando-de: CLUB UNISEX - Low-Top-Sneaker - white/black/v | page price 39.95 EUR | 19 variants, price range 39.95-39.95, 4 sold out
walmart-us: (5 pack) onn 3ft Braided USB-C Charging Cable | page price 19.4 USD | 3 variants, price range 3.88-19.4, 1 sold out
28 variant rows written to variants.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first lines of &lt;code&gt;variants.csv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;site,product,variant_sku,options,price,currency,was_price,in_stock,url
skullcandy.com,Crusher® 540 Active,S6EVW-U054,Color=Skullcandy X Baker Skateboards,229.99,USD,,True,https://www.skullcandy.com/products/crusher-540-active?variant=50564609998905
skullcandy.com,Crusher® 540 Active,S6EVW-T013,Color=Smoke,209.99,USD,,True,https://www.skullcandy.com/products/crusher-540-active?variant=45146487652409
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three pages were fetched over datacenter connections, so the run had no traffic fee. It billed one start event and three product events. Variants don't add events.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a variant contains
&lt;/h2&gt;

&lt;p&gt;Each entry in &lt;code&gt;variants[]&lt;/code&gt; has the same keys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sku&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The variant's own identifier: a store SKU, a Walmart item id, or an Amazon ASIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The variant's title, e.g. "Smoke" or "5 Pack"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;options&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The option values as an object, e.g. &lt;code&gt;{"Color": "Red Lily", "Size": "L"}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;, &lt;code&gt;originalPrice&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The variant's price, its ISO currency and its was-price, when the page states them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;inStock&lt;/code&gt;, &lt;code&gt;availability&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Stock flag and the raw availability (&lt;code&gt;InStock&lt;/code&gt;, &lt;code&gt;OutOfStock&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;gtin&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Barcode, variant image and a URL that selects the variant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The product row around it has &lt;code&gt;variantCount&lt;/code&gt;, the product-level &lt;code&gt;price&lt;/code&gt; and &lt;code&gt;inStock&lt;/code&gt; (the selected variant's), and &lt;code&gt;changedVariants&lt;/code&gt; in monitor mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which sites give per-variant prices
&lt;/h2&gt;

&lt;p&gt;The pattern below comes from the product rows in about 260 of our cloud test runs between 2026-10-17 and 2026-10-20. It's a measure of what those pages contained, not a guarantee for every product:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Variant entries&lt;/th&gt;
&lt;th&gt;With a price&lt;/th&gt;
&lt;th&gt;With stock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shopify stores (Death Wish Coffee, Skullcandy, Beardbrand)&lt;/td&gt;
&lt;td&gt;2,572&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;2,454&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Columbia (Salesforce Commerce)&lt;/td&gt;
&lt;td&gt;2,803&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zalando&lt;/td&gt;
&lt;td&gt;1,239&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walmart&lt;/td&gt;
&lt;td&gt;1,212&lt;/td&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bloomingdale's&lt;/td&gt;
&lt;td&gt;835&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;John Lewis&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;43 (11 of 70 products had variants)&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon (.com, .co.uk, .de)&lt;/td&gt;
&lt;td&gt;2,651&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A WooCommerce store (Nalgene)&lt;/td&gt;
&lt;td&gt;571&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Amazon is the exception. Its product pages list each variant's ASIN and option values, but not their prices. The row's own &lt;code&gt;price&lt;/code&gt; is the price of the ASIN in your URL. To price a sibling, put its &lt;code&gt;variants[].url&lt;/code&gt; (a &lt;code&gt;/dp/ASIN&lt;/code&gt; link) into &lt;code&gt;detailsUrls&lt;/code&gt; and scrape it as a product. The &lt;a href="https://datatooly.xyz/guides/amazon-price-scraper-asin/" rel="noopener noreferrer"&gt;Amazon price scraper guide&lt;/a&gt; covers that by ASIN and marketplace.&lt;/p&gt;

&lt;p&gt;Grocery sites in our runs (Waitrose, Aldi UK) returned no variants: each pack size is its own product page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traps that give wrong variant data without an error
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reporting one price per product.&lt;/strong&gt; Store the range (min and max over variants) or a price per variant. "From $209.99" and "$229.99" are both true on the Skullcandy page above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading &lt;code&gt;inStock&lt;/code&gt; on the product as "every size is available".&lt;/strong&gt; The product flag is the selected variant's. The Columbia jacket said in stock with 207 of 271 combinations sold out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Losing the currency.&lt;/strong&gt; Variant &lt;code&gt;currency&lt;/code&gt; can be empty when the page omits it (always on Amazon). Fall back to the product's &lt;code&gt;currency&lt;/code&gt;, as the script does. If prices come back in the wrong currency, set &lt;code&gt;proxyCountry&lt;/code&gt; to the store's country.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparing variants by name.&lt;/strong&gt; "Black" on one store is "Jet Black" on another. Match on &lt;code&gt;gtin&lt;/code&gt; when both sides have it, or on your own mapping of option values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Huge rows.&lt;/strong&gt; A product with 271 variants is one row with a long array. Flatten it, as the script does, before you load it into a spreadsheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expecting stable variant prices.&lt;/strong&gt; The Zalando size-36 price of €64.95 on October 17 was €39.95 on October 20, like the other sizes. Variant prices move on their own, which is exactly why you track them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Holiday season: watch sizes, not just prices
&lt;/h2&gt;

&lt;p&gt;During Black Friday (Friday, November 27, 2026) and Cyber Monday (November 30), a "deal" can cover only some sizes or colors, and the popular ones can sell out first. Neither can be seen from the product-level price. Two settings help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedule the same input daily from early November with &lt;code&gt;monitorMode: true&lt;/code&gt;. A variant whose price, was-price or currency changes marks the product &lt;code&gt;changed&lt;/code&gt; and lists it in &lt;code&gt;changedVariants&lt;/code&gt;, so you can see a discount that applies to one color only.&lt;/li&gt;
&lt;li&gt;Track &lt;code&gt;inStock&lt;/code&gt; per variant for the sizes you care about. The &lt;a href="https://datatooly.xyz/guides/back-in-stock-alert-scraper/" rel="noopener noreferrer"&gt;back-in-stock alert guide&lt;/a&gt; turns that into a notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;holiday sales calendar&lt;/a&gt; lists the 2026 and 2027 sale dates and how early to start a baseline for each.&lt;/p&gt;

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

&lt;p&gt;Pay per event, from the actor's pricing table: one product row is one "product scraped" event, $0.0048 on Apify's FREE plan tier down to $0.0008 on GOLD, plus $0.005 per run start. Variants are inside the row, so they don't add events. Sites that need residential proxies or a browser (see the table on the actor page) add a per-MB or per-render fee. Apify's free plan includes $5 of usage every month, with no credit card required. There's no free trial inside the actor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/shopify-products-json-pagination/" rel="noopener noreferrer"&gt;Shopify products.json pagination&lt;/a&gt;: list every product of a Shopify store.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/scrape-product-reviews-walmart-target-amazon/" rel="noopener noreferrer"&gt;Scrape product reviews from Walmart, Target and Amazon&lt;/a&gt;: the review fields, including which variant each reviewer bought.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/back-in-stock-alert-scraper/" rel="noopener noreferrer"&gt;Back-in-stock alerts&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker builder&lt;/a&gt; has a variants option and produces a ready-to-run input for the actor, with a fixed example of the output shape. It doesn't fetch prices in your browser.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the actor and this site. The Shopify &lt;code&gt;.js&lt;/code&gt; method above needs neither.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: live responses from &lt;code&gt;skullcandy.com&lt;/code&gt; (&lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.json&lt;/code&gt; endpoints) and an actor run on 2026-10-20; per-site variant counts from our own cloud test runs, 2026-10-17 to 2026-10-20.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>python</category>
      <category>webscraping</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Scrape Product Reviews from Walmart, Target and Amazon</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:45:26 +0000</pubDate>
      <link>https://dev.to/odeeb/scrape-product-reviews-from-walmart-target-and-amazon-60h</link>
      <guid>https://dev.to/odeeb/scrape-product-reviews-from-walmart-target-and-amazon-60h</guid>
      <description>&lt;p&gt;To scrape product reviews, collect them from the product's own review source with a fixed sort and a cap: one row per review with its rating, text, date, verified flag and the variant bought. Walmart and Target expose every review page; Amazon shows only its top reviews (8–13) to signed-out visitors. Sort by "Lowest rated" to get complaints first.&lt;/p&gt;

&lt;p&gt;This guide covers what each site lets you reach, the inputs of an Apify actor that does it, a real review row, and a short Python script that turns the lowest-rated reviews into complaint counts without a paid AI model. Everything below was run on 2026-10-20 unless a date says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can reach on each site
&lt;/h2&gt;

&lt;p&gt;The table is from the actor's supported-sites list and our October 2026 cloud tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Reviews you get&lt;/th&gt;
&lt;th&gt;Sort honored in our tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Walmart (&lt;code&gt;walmart.com&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;All pages&lt;/td&gt;
&lt;td&gt;Lowest rated, Most recent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target (&lt;code&gt;target.com&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;All pages&lt;/td&gt;
&lt;td&gt;Lowest rated, Most recent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon (24 storefronts)&lt;/td&gt;
&lt;td&gt;Only the top reviews on the product page, about 8 to 13 per product&lt;/td&gt;
&lt;td&gt;No: Amazon's own order (&lt;code&gt;sortApplied&lt;/code&gt; = "Most relevant")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shopify stores with Yotpo, Judge.me, Okendo or Stamped&lt;/td&gt;
&lt;td&gt;Reviews from the review widget&lt;/td&gt;
&lt;td&gt;All five sorts on a Yotpo store; "Most recent" seen on Judge.me and Okendo stores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WooCommerce stores&lt;/td&gt;
&lt;td&gt;From the store's own Store API&lt;/td&gt;
&lt;td&gt;not tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AliExpress (best-effort site)&lt;/td&gt;
&lt;td&gt;All pages; some reviews are a rating with no text&lt;/td&gt;
&lt;td&gt;No: fell back to "Most relevant"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flipkart (best-effort site)&lt;/td&gt;
&lt;td&gt;All pages&lt;/td&gt;
&lt;td&gt;not tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mercado Libre (best-effort site)&lt;/td&gt;
&lt;td&gt;Catalog URLs (&lt;code&gt;/p/MLM…&lt;/code&gt;) only&lt;/td&gt;
&lt;td&gt;not tested&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Amazon's full review pages ask visitors to sign in. A scraper that respects that limit only gets the reviews printed on the product page. When Amazon shows "Sign in to see customer reviews" instead, the actor reports the product as &lt;code&gt;restricted&lt;/code&gt; rather than as an empty success.&lt;/p&gt;

&lt;p&gt;Not reachable with this actor: Google Shopping reviews, Loox and Reviews.io widgets (the page's structured reviews are used instead), and sites the actor marks unsupported, such as eBay, Best Buy, Home Depot and Etsy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inputs
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Product Scraper Pro actor&lt;/a&gt; has a review section with these fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reviewDetailsUrls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Product pages to read reviews from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reviewListingUrls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Category or search pages: reviews for the products listed on the first page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;keywordReviews&lt;/code&gt; + &lt;code&gt;marketplacesReviews&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A keyword searched on the chosen marketplaces; reviews for the products found on the first results page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sortReview&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Most recent&lt;/code&gt;, &lt;code&gt;Most relevant&lt;/code&gt;, &lt;code&gt;Most helpful&lt;/code&gt;, &lt;code&gt;Highest rated&lt;/code&gt; or &lt;code&gt;Lowest rated&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxReviewResults&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cap for the whole run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxItemsPerStartUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cap per input, so one product can't use up the run's cap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;additionalReviewProperties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds extra per-site fields&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a site doesn't support the sort you asked for, its default order is used. The row's &lt;code&gt;sortApplied&lt;/code&gt; says which order the reviews really follow, and the input's status row says so too.&lt;/p&gt;

&lt;p&gt;A keyword review search, as run today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywordReviews"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usb c cable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"marketplacesReviews"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"www.target.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.walmart.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortReview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lowest rated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItemsPerStartUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxReviewResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status row for each marketplace explained what happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keyword "usb c cable" on www.target.com: 24 product(s) found on the first page; reviews read for 1, 1 had reviews; 2 review(s) delivered. Sort applied per source — Target: Lowest rated. Stopped at maxItemsPerStartUrl (2) for this input. Only the first listing page is read for reviews.
Keyword "usb c cable" on www.walmart.com: 60 product(s) found on the first page (first 60 used); reviews read for 1, 1 had reviews; 2 review(s) delivered. Sort applied per source — Walmart: Lowest rated. Stopped at maxReviewResults (4). Only the first listing page is read for reviews.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What a review row contains
&lt;/h2&gt;

&lt;p&gt;A real Walmart row from today, with the reviewer's name removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.walmart.com/ip/18928659757"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.walmart.com/ip/ONN-10-C-C-BRD-WHT/18928659757"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"datePublished"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-09"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"melted sparked barely got fire out but it melted the place on my phone where c plugs in"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ratingValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"bestRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"helpfulVotes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unhelpfulVotes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"variant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Multipack quantity: 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"syndicatedFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bazaarvoice"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"443517198"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"helpfulVotes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"images"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Multipack quantity: 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18928659757"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Onn 10ft USB-C to USB-C Braided Charging Cable, USB-IF Certified, White - Compatible with iPhone, Samsung, Pixel, iPad Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortApplied"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lowest rated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"walmart-us"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"walmart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scrapedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-20T09:30:03.526Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fields worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rating&lt;/code&gt; is on the scale in &lt;code&gt;bestRating&lt;/code&gt; (5 on these sites).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;variant&lt;/code&gt; is the size, color or pack the reviewer bought, when the site says. It's how you tell whether complaints cluster on one variant. See &lt;a href="https://datatooly.xyz/guides/scrape-product-variants-sizes-colors/" rel="noopener noreferrer"&gt;scraping product variants&lt;/a&gt; for the variant data itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;datePublished&lt;/code&gt; keeps the precision the site gives. Walmart gives a day, Target a full timestamp, and relative dates like "3 months ago" become a month (&lt;code&gt;2026-07&lt;/code&gt;), never an invented day.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;additionalProperties&lt;/code&gt; carries site extras: Target's &lt;code&gt;incentivized&lt;/code&gt; and &lt;code&gt;recommended&lt;/code&gt; flags, and Walmart's &lt;code&gt;syndicatedFrom&lt;/code&gt; (reviews shown on Walmart but collected elsewhere).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;author&lt;/code&gt; holds the reviewer's display name. You rarely need it; drop it before you store reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Python: summarize complaints from the lowest-rated reviews
&lt;/h2&gt;

&lt;p&gt;This script collects the lowest-rated reviews of a Walmart product and a Target product, then counts complaint themes with keyword lists. There's no AI model involved, so it costs nothing beyond the run and gives the same answer every time.&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;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;constructive_calm/ecommerce-product-scraper-pro&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;reviewDetailsUrls&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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;https://www.walmart.com/ip/ONN-10-C-C-BRD-WHT/18928659757&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;url&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;https://www.target.com/p/3-39-usb-c-to-usb-c-charging-cable-dealworthy-8482-white/-/A-89698753&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;sortReview&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;Lowest rated&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;maxItemsPerStartUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# per product, so one product can't use up the run cap
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxReviewResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# run-wide cap; raise both for real analysis
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Complaint themes: a keyword list per theme. Edit these for your category.
&lt;/span&gt;&lt;span class="n"&gt;THEMES&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;stopped working&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopp?ed working|no longer works?|didn.t work|doesn.t work|not working|died|dead&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;physical damage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crack|fray|broke|wires? (?:visible|showing|exposed)|fell apart&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;short lifespan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lasted|weeks?|months? ago|already&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;fit / length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bfit\b|loose|too? short|length&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;overheating / safety&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bhot\b|overheat|melt|spark|smoke|fire&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;wrong item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrong item|received a|not as described|different (?:item|product)|ordered .{0,40}received&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;general quality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheap|flimsy|crap|junk|poor quality&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="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewBody&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;THEMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; reviews, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rated 1-2 stars&lt;/span&gt;&lt;span class="sh"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reviews&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;* sort=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sortApplied&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewBody&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Complaint themes in 1-2 star reviews:&lt;/span&gt;&lt;span class="sh"&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;most_common&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;Output on 2026-10-20 (&lt;code&gt;pip install apify-client&lt;/code&gt;, &lt;code&gt;APIFY_TOKEN&lt;/code&gt; set):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 reviews, 5 rated 1-2 stars
  target     1* sort=Lowest rated 'Bought 3 months ago and it’s already cracked with wires visible in mul'
  target     1* sort=Lowest rated "piece of crap. don't buy!!"
  target     1* sort=Lowest rated 'to short of a cord'
  walmart-us 1* sort=Lowest rated 'melted sparked barely got fire out but it melted the place on my phone'
  walmart-us 1* sort=Lowest rated 'Ordered Braided USB-C to USB Cable. Received Braided USB-C Cable'
Complaint themes in 1-2 star reviews:
  physical damage           1/5
  short lifespan            1/5
  general quality           1/5
  fit / length              1/5
  overheating / safety      1/5
  wrong item                1/5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five reviews is a demonstration, not a finding. For a real read, raise &lt;code&gt;maxItemsPerStartUrl&lt;/code&gt; to 100–200 per product. The Target product had 95 reviews and the Walmart one 38, so a few hundred covers them all. Then read a handful of reviews behind each count before you trust it. Keyword themes miss misspellings ("minths") and sarcasm, and one review can hit several themes.&lt;/p&gt;

&lt;p&gt;Two ways to make the counts more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Split by &lt;code&gt;variant&lt;/code&gt;.&lt;/strong&gt; If most "too short" complaints are on the 3 ft version, that's a sizing problem, not a quality one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare against "Most recent".&lt;/strong&gt; Run the same products sorted by &lt;code&gt;Most recent&lt;/code&gt; every week. A theme that grows in recent reviews points to a new batch or a supplier change, while the lowest-rated sort mostly shows the worst reviews of all time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Holiday season: check the 1-star reviews before the deal
&lt;/h2&gt;

&lt;p&gt;Black Friday is Friday, November 27, 2026, and Cyber Monday is November 30. Before you feature a product in a promotion, or buy a heavily discounted one, pull its lowest-rated reviews and look for safety or failure themes. A deep discount on an item with a cluster of "melted" or "stopped working" reviews isn't a deal. If you sell, a weekly &lt;code&gt;Most recent&lt;/code&gt; run through November and December catches new complaints while there's still time to act, for example by fixing a listing or pausing a promotion. The &lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;holiday sales calendar&lt;/a&gt; has the 2026–2027 sale dates. To check the discount itself, see &lt;a href="https://datatooly.xyz/guides/spot-fake-black-friday-discounts/" rel="noopener noreferrer"&gt;how to spot fake Black Friday discounts&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Pay per event, from the actor's pricing table: one review row is $0.0016 on Apify's FREE plan tier and $0.00056 on GOLD, plus $0.005 per run start. The README's example of 500 Target reviews is 500 × $0.00056 + $0.005 = $0.285 on GOLD; on FREE it's 500 × $0.0016 + $0.005 = $0.805. Review pages that need a residential proxy add a per-MB traffic fee. Apify's free plan includes $5 of usage every month, with no credit card required. There's no free trial inside the actor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/walmart-target-price-scraper/" rel="noopener noreferrer"&gt;Walmart and Target price scraper&lt;/a&gt;: the product rows for the same retailers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatooly.xyz/guides/amazon-price-scraper-asin/" rel="noopener noreferrer"&gt;Amazon price scraper by ASIN&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it without code
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker builder&lt;/a&gt; creates a ready-to-run input for the actor's product section and shows a fixed example of the output. For reviews, use the review fields above in the actor's input form on Apify.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the actor and this site.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: actor runs on 2026-10-20 (review sort and status text), our cloud test runs between 2026-10-17 and 2026-10-20 (per-site sort behavior and Amazon review counts), and the actor's README (supported-sites table, limitations, pricing).&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>python</category>
      <category>webscraping</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Ramadan Sale Price Tracker 2027: Noon &amp; Amazon UAE</title>
      <dc:creator>Omar Eldeeb</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:45:04 +0000</pubDate>
      <link>https://dev.to/odeeb/ramadan-sale-price-tracker-2027-noon-amazon-uae-4f93</link>
      <guid>https://dev.to/odeeb/ramadan-sale-price-tracker-2027-noon-amazon-uae-4f93</guid>
      <description>&lt;p&gt;Ramadan 2027 is expected to start around Monday 8 February 2027, with Eid al-Fitr on 9 or 10 March (astronomical estimates; final dates depend on moon sighting). Retailer "Ramadan sales" start weeks earlier: in 2026 Amazon UAE's ran 27 January to 14 February and ended before Ramadan began. So start weekly price records in mid-December 2026 and check daily from mid-January through Eid.&lt;/p&gt;

&lt;p&gt;This is the Gulf Ramadan page of the cluster. It covers the dates and their uncertainty, why the sale calendar runs ahead of the religious calendar, a phase-by-phase schedule, which sites can be tracked, and a script that measures which categories actually moved. For November's sales in the region, see &lt;a href="https://datatooly.xyz/guides/white-friday-11-11-noon-amazon-uae-prices/" rel="noopener noreferrer"&gt;White Friday and 11.11 on noon and Amazon UAE&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dates: what's known and what isn't
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2026 (actual)&lt;/th&gt;
&lt;th&gt;2027 (approximate)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First day of Ramadan&lt;/td&gt;
&lt;td&gt;Wed 18 February 2026 in the UAE and Saudi Arabia; 19 February in some other countries&lt;/td&gt;
&lt;td&gt;about Mon 8 February 2027 (UAE Astronomy Centre; Umm al-Qura calendar). Some countries may start on 9 February.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eid al-Fitr&lt;/td&gt;
&lt;td&gt;Fri 20 March 2026 in the UAE and Saudi Arabia&lt;/td&gt;
&lt;td&gt;Tue 9 March 2027 if Ramadan has 29 days, Wed 10 March if 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon UAE Ramadan Sale&lt;/td&gt;
&lt;td&gt;27 January - 14 February 2026 (Prime members from 27 January)&lt;/td&gt;
&lt;td&gt;not announced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;noon Ramadan sale&lt;/td&gt;
&lt;td&gt;until 2 February 2026 (reported 26 January)&lt;/td&gt;
&lt;td&gt;not announced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Islamic calendar moves about 10-12 days earlier each Gregorian year, which is why Ramadan 2027 lands about 10 days before Ramadan 2026 did. The start is confirmed by each country's moon-sighting committee on the evening of the 29th of Sha'ban, so you won't know the exact day until the night before. A schedule that is daily for the whole window makes that uncertainty irrelevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sale calendar runs ahead of Ramadan
&lt;/h2&gt;

&lt;p&gt;The 2026 dates above show the pattern: the big e-commerce Ramadan sales were stocking-up events before the month, not sales during it. If retailers follow the same pattern in 2027, the Ramadan sales would start around mid-January 2027. That has two consequences for tracking:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The baseline has to start in December.&lt;/strong&gt; A baseline that starts in late January would already be inside the sale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The baseline overlaps other sales.&lt;/strong&gt; The Dubai Shopping Festival runs 4 December 2026 to 17 January 2027, and the November 11.11 and White Friday sales come before that. In the Gulf, November to March is close to one continuous promotion season. Keep weekly records through all of it, and use the prices a few weeks after Eid as a second reference: if the "Ramadan price" is also the price in late March, it wasn't a sale price.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A phase-by-phase schedule
&lt;/h2&gt;

&lt;p&gt;This prints the 2027 windows from the approximate dates. The pre-Ramadan sale window copies 2026's Amazon UAE timing (starting 22 days before Ramadan); move it when the 2027 sales are announced.&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;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="c1"&gt;# Astronomical estimates (UAE / Umm al-Qura); confirmed only by moon sighting.
&lt;/span&gt;&lt;span class="n"&gt;RAMADAN_START&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;EID_EARLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EID_LATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 29- or 30-day month
&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="c1"&gt;# In 2026 Amazon.ae's Ramadan Sale ran 22 days to 4 days before Ramadan began.
&lt;/span&gt;&lt;span class="n"&gt;phases&lt;/span&gt; &lt;span class="o"&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;baseline (weekly)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="n"&gt;RAMADAN_START&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;RAMADAN_START&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;23&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;pre-Ramadan sales (daily)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;RAMADAN_START&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;RAMADAN_START&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;Ramadan, first weeks (daily)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RAMADAN_START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="n"&gt;EID_EARLY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&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;last 10 nights (2x daily)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;EID_EARLY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="n"&gt;EID_LATE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;Eid holiday (daily)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="n"&gt;EID_EARLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="n"&gt;EID_LATE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&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;rebound check (weekly)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="n"&gt;EID_LATE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="n"&gt;EID_LATE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;phases&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; days)&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseline (weekly)              Mon 14 Dec 2026 -&amp;gt; Sat 16 Jan 2027  (34 days)
pre-Ramadan sales (daily)      Sun 17 Jan 2027 -&amp;gt; Sun 07 Feb 2027  (22 days)
Ramadan, first weeks (daily)   Mon 08 Feb 2027 -&amp;gt; Fri 26 Feb 2027  (19 days)
last 10 nights (2x daily)      Sat 27 Feb 2027 -&amp;gt; Tue 09 Mar 2027  (11 days)
Eid holiday (daily)            Tue 09 Mar 2027 -&amp;gt; Sat 13 Mar 2027  (5 days)
rebound check (weekly)         Sun 14 Mar 2027 -&amp;gt; Wed 31 Mar 2027  (18 days)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overlap between "last 10 nights" and "Eid" on 9 March is deliberate: that day is either the last day of Ramadan or the first day of Eid.&lt;/p&gt;

&lt;p&gt;Cron for these windows (UTC; the UAE is UTC+4 and Saudi Arabia UTC+3, with no daylight saving): weekly &lt;code&gt;0 4 * * 0&lt;/code&gt;, daily &lt;code&gt;0 4 * * *&lt;/code&gt; (08:00 UAE), twice daily &lt;code&gt;0 4,16 * * *&lt;/code&gt; (08:00 and 20:00 UAE). The evening run, after iftar, catches promotions that go live in the evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which sites can be tracked
&lt;/h2&gt;

&lt;p&gt;The examples use the &lt;a href="https://apify.com/constructive_calm/ecommerce-product-scraper-pro?fpr=v77kxu" rel="noopener noreferrer"&gt;E-commerce Product Scraper Pro&lt;/a&gt; actor. Its README rates these Gulf sites best-effort, except noon Saudi Arabia (unsupported), and our runs on 20 October 2026 gave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;noon UAE&lt;/strong&gt; (&lt;code&gt;noon.com/uae-en/&lt;/code&gt;): delivered, in AED. noon is behind Akamai, so the actor renders a page in a browser and then uses residential connections, which carry per-MB and per-render fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;noon Saudi Arabia&lt;/strong&gt; (&lt;code&gt;noon.com/saudi-en/&lt;/code&gt;, &lt;code&gt;/saudi-ar/&lt;/code&gt;): &lt;strong&gt;unsupported&lt;/strong&gt;. Akamai answered "Access Denied" on every route in the actor's October 2026 tests, Saudi residential connections and the browser included, so the README lists it as unsupported. Saudi noon inputs get a status row saying why, aren't fetched, and cost only the run's start fee. Use Amazon Saudi Arabia for Saudi prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon UAE&lt;/strong&gt; (&lt;code&gt;amazon.ae&lt;/code&gt;): delivered, in AED, over residential connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Saudi Arabia&lt;/strong&gt; (&lt;code&gt;amazon.sa&lt;/code&gt;): delivered, in SAR, over datacenter connections (no traffic fee). The default Arabic product pages parse fully: on 20 October 2026, &lt;code&gt;https://www.amazon.sa/dp/B0DGHYPFGK&lt;/code&gt; came back with an Arabic title, &lt;code&gt;inStock: true&lt;/code&gt; and &lt;code&gt;brandName: "Apple"&lt;/code&gt;. English URLs (&lt;code&gt;https://www.amazon.sa/-/en/dp/&amp;lt;ASIN&amp;gt;&lt;/code&gt;) also work if you want English titles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IKEA UAE / Saudi Arabia&lt;/strong&gt;: best-effort in the README; not tested today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gulf supermarket sites such as Carrefour and Lulu aren't in the matrix and haven't been tested; Amazon UAE and Saudi Arabia grocery items are ordinary Amazon product pages, so they should behave like any other ASIN (we haven't tested grocery items specifically).&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure which categories moved
&lt;/h2&gt;

&lt;p&gt;Published campaigns quote "up to 70% off", which says nothing about the typical cut. To answer "what got cheaper?", compare two exports of the same products and group the changes by category. The actor's rows carry a &lt;code&gt;breadcrumbs&lt;/code&gt; list (for example &lt;code&gt;["Home", "Electronics &amp;amp; Mobiles", "Mobiles &amp;amp; Accessories", "Mobile Phones", "Smartphones"]&lt;/code&gt; on noon), which is enough to group on. Amazon UAE and Saudi Arabia rows have an empty &lt;code&gt;breadcrumbs&lt;/code&gt; list, because Amazon shows no category trail on those pages, so the script files them under "(no category)"; if you track Amazon there, map those URLs to your own categories.&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;moves&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&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;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;crumbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;breadcrumbs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(no category)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;          &lt;span class="c1"&gt;# pick the depth that suits your catalog
&lt;/span&gt;    &lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&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="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;matched&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cut&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;raised&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;median %&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pct&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;10.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matched &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; products by URL&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;Run as &lt;code&gt;python category_moves.py baseline.json during.json&lt;/code&gt;. Here it is on two real exports of the same noon UAE search ("samsung"), taken 2 hours apart on 17 October 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category                     matched   cut  raised  median %
Mobile Phones                     11     1       0      0.00
matched 11 of 30 products by URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lessons from that real run. First, the change was tiny (one phone from AED 1,168 to 1,167.55), which is what an ordinary day looks like; a Ramadan sale should show up as a clearly negative median. Second, only 11 of the 30 products were on the search page both times. Search pages reshuffle, so for a Ramadan comparison track a fixed list of product URLs (pasted into &lt;code&gt;detailsUrls&lt;/code&gt;) instead of a search, or every comparison is mostly products coming and going.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Pay per event, prices from the actor's README. FREE tier: $0.0048 per product, $0.008 per browser render, $0.018 per residential MB (billed at 89% of measured traffic, only for items that delivered), $0.005 per run start. GOLD: $0.0008, $0.006, $0.014 and $0.005.&lt;/p&gt;

&lt;p&gt;Per-run examples from real runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;60 noon UAE product URLs&lt;/strong&gt; (18 October 2026: 60 products, 2 browser renders, 8 billed MB). FREE: $0.005 + 60 × $0.0048 + 2 × $0.008 + 8 × $0.018 = &lt;strong&gt;$0.453&lt;/strong&gt;. GOLD: $0.005 + 60 × $0.0008 + 2 × $0.006 + 8 × $0.014 = &lt;strong&gt;$0.177&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;60 Amazon Saudi Arabia products&lt;/strong&gt; (datacenter, no traffic fee). FREE: 60 × $0.0048 + $0.005 = &lt;strong&gt;$0.293&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiply by the number of runs in your schedule. noon's residential traffic makes daily noon runs the expensive part, so if budget matters, check noon every two or three days during the mid-month phase and daily only in the sale and Eid windows. For a run with more than about 50 noon URLs, the README recommends 2 GB of memory (one extra $0.005 start event).&lt;/p&gt;

&lt;p&gt;Apify's free plan gives $5 of usage every month, no credit card required; the actor has no free trial of its own. The free &lt;a href="https://datatooly.xyz/ecommerce-price-tracker/" rel="noopener noreferrer"&gt;e-commerce price tracker query builder&lt;/a&gt; builds the input without code and shows a fixed example of the output shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/white-friday-11-11-noon-amazon-uae-prices/" rel="noopener noreferrer"&gt;White Friday and 11.11 on noon and Amazon UAE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/holiday-sales-calendar-price-tracking/" rel="noopener noreferrer"&gt;Retail sales calendar 2026-2027&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/grocery-price-tracker-scraper/" rel="noopener noreferrer"&gt;Grocery price tracker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatooly.xyz/guides/price-drop-alerts-email-slack/" rel="noopener noreferrer"&gt;Price drop alerts by email or Slack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the Apify actor and this site. The schedule and the category script work with data from any source.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://www.thenationalnews.com/news/uae/2026/10/06/ramadan-2027-uae-dates-eid-al-fitr-holiday/" rel="noopener noreferrer"&gt;The National: expected Ramadan 2027 and Eid al-Fitr dates in the UAE&lt;/a&gt;, &lt;a href="https://gulfnews.com/uae/ramadan/ramadan-2026-these-countries-sighted-the-crescent-moon-today-1.500446556" rel="noopener noreferrer"&gt;Gulf News: countries that began Ramadan 2026 on 18 February&lt;/a&gt;, &lt;a href="https://gulfnews.com/amp/story/uae%2Fwhich-countries-will-celebrate-eid-al-fitr-2026-on-friday-2-1.500479052" rel="noopener noreferrer"&gt;Gulf News: countries celebrating Eid al-Fitr 2026 on Friday&lt;/a&gt;, &lt;a href="https://gulfnews.com/uae/ramadan/uae-ramadan-2026-sales-where-to-shop-for-bargains-1.500405590" rel="noopener noreferrer"&gt;Gulf News: UAE Ramadan 2026 sales&lt;/a&gt;, &lt;a href="https://tradearabia.com/News/487977/Dubai-Shopping-Festival-returns-for-32nd-edition-from-December-4-to-January-17" rel="noopener noreferrer"&gt;TradeArabia: Dubai Shopping Festival 2026-27&lt;/a&gt;, the actor's README (supported sites, pricing, memory) and live actor runs on 2026-10-17, 2026-10-18 and 2026-10-20 (&lt;code&gt;amazon.sa&lt;/code&gt; Arabic page: run &lt;code&gt;rm7jhN0AiKNMb6V6K&lt;/code&gt;, build 0.1.16).&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>python</category>
      <category>webscraping</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
