<?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: ByteHarvester</title>
    <description>The latest articles on DEV Community by ByteHarvester (@projectdev).</description>
    <link>https://dev.to/projectdev</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%2F3882331%2F29d95d82-a74d-4a72-99b6-6e8e8e917279.png</url>
      <title>DEV Community: ByteHarvester</title>
      <link>https://dev.to/projectdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/projectdev"/>
    <language>en</language>
    <item>
      <title>73% of the Shopify stores I checked still load scripts that stop on 1 March 2027</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Sat, 24 Oct 2026 11:29:08 +0000</pubDate>
      <link>https://dev.to/projectdev/73-of-the-shopify-stores-i-checked-still-load-scripts-that-stop-on-1-march-2027-e3e</link>
      <guid>https://dev.to/projectdev/73-of-the-shopify-stores-i-checked-still-load-scripts-that-stop-on-1-march-2027-e3e</guid>
      <description>&lt;p&gt;Disclosure: I build Citebeam, and the free checker linked below is mine.&lt;/p&gt;

&lt;p&gt;Shopify will stop injecting ScriptTags into storefronts on &lt;strong&gt;1 March 2027&lt;/strong&gt;. I wanted to know how many real stores still depend on them, so I measured it: of 45 public Shopify storefronts I checked, &lt;strong&gt;33 (73%) still load at least one ScriptTag&lt;/strong&gt;. I built a free checker for this at &lt;a href="https://citebeam-three.vercel.app/script-tags?utm_source=devto&amp;amp;utm_campaign=scripttags" rel="noopener noreferrer"&gt;Citebeam&lt;/a&gt;, an independent Shopify app (not affiliated with Shopify). You paste a store address and it shows what will stop and what to ask your vendors. This post is the data behind it, and the two-minute manual version if you would rather not use any tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is changing
&lt;/h2&gt;

&lt;p&gt;From the Shopify developer changelog (published 24 August 2026):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since &lt;strong&gt;1 October 2026&lt;/strong&gt;, apps can no longer create or update ScriptTags. &lt;code&gt;scriptTagCreate&lt;/code&gt; and &lt;code&gt;scriptTagUpdate&lt;/code&gt; return an error, and the REST resource rejects &lt;code&gt;POST&lt;/code&gt; and &lt;code&gt;PUT&lt;/code&gt;. This applies to all API versions, so pinning an old version does not defer it.&lt;/li&gt;
&lt;li&gt;Existing ScriptTags keep running until &lt;strong&gt;1 March 2027&lt;/strong&gt;, when Shopify stops injecting them into storefronts.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;scriptTags&lt;/code&gt; query and &lt;code&gt;scriptTagDelete&lt;/code&gt; still work, so apps can audit and clean up.&lt;/li&gt;
&lt;li&gt;The replacements are &lt;strong&gt;app embed blocks&lt;/strong&gt; in a theme app extension (for general JavaScript) and &lt;strong&gt;web pixels&lt;/strong&gt; (for analytics and conversion tracking only).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This covers ScriptTags scoped to the online store. Scripts on the order status page fall under a separate, earlier deprecation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I measured it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;45 stores: one theme demo store plus 44 drawn at random from a list of live stores (30 on custom domains, 14 on &lt;code&gt;*.myshopify.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;For each store I fetched only what any visitor can fetch: &lt;code&gt;robots.txt&lt;/code&gt;, the public home page, and &lt;code&gt;/account/login&lt;/code&gt; (only where robots.txt allows it). Honest user agent, robots.txt respected, no retries, no proxy.&lt;/li&gt;
&lt;li&gt;Shopify injects ScriptTags into the page head as an &lt;code&gt;asyncLoad&lt;/code&gt; function with a &lt;code&gt;var urls = [ ... ]&lt;/code&gt; list. That list parsed as JSON on all 33 pages that have it. I counted the URLs in it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Limits, so you can weigh the numbers: I looked at the &lt;strong&gt;home page only&lt;/strong&gt;, so scripts that load on other page types are not counted. The sample is &lt;strong&gt;small, and it leans toward smaller stores&lt;/strong&gt;. The list shows what a store injects, not which app owns it, so I can only name the host a script is served from. I could not compare against any store's admin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I found
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;33 of 45 stores (73%)&lt;/strong&gt; inject at least one ScriptTag. 12 (27%) have none.&lt;/li&gt;
&lt;li&gt;95 ScriptTag URLs in total. Among the stores that have any: median 2, mean 2.9, &lt;strong&gt;max 11&lt;/strong&gt; in a single store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;29 of 45 stores (64%)&lt;/strong&gt; also load at least one theme app embed asset, so many stores are mid-way: some apps already moved, some did not. 26 stores have both.&lt;/li&gt;
&lt;li&gt;Most common hosts the scripts are served from, by number of stores: &lt;code&gt;nfcube.com&lt;/code&gt; 8, &lt;code&gt;chimpstatic.com&lt;/code&gt; 6, &lt;code&gt;hextom.com&lt;/code&gt; 5, and &lt;code&gt;trustpilot.com&lt;/code&gt;, &lt;code&gt;avada.io&lt;/code&gt; and &lt;code&gt;printful.com&lt;/code&gt; 3 each. Another 25 or so domains appear once: chat, reviews, pop-ups, back-in-stock, email capture, upsell, affiliate and analytics apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A script served from a vendor's domain does not mean that vendor has not migrated. Many vendors already offer an app embed, and the old ScriptTag may just be a leftover. For any domain you see, &lt;strong&gt;check with the vendor&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually breaks
&lt;/h2&gt;

&lt;p&gt;A ScriptTag is how many older apps put something on your storefront without editing the theme: a pop-up, a chat bubble, review stars, a back-in-stock form, an upsell. When injection stops, whatever that script drew or did stops loading, unless the app has moved to an app embed. And even after a vendor ships an app embed, it is off until someone switches it on in the theme editor. A vendor saying "we migrated" and your storefront showing the widget are two separate facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your own store in two minutes (no tool needed)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open your home page in a private window, view the page source (Ctrl+U or Cmd+Option+U).&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;asyncLoad&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Right below it you will see &lt;code&gt;var urls = [ ... ]&lt;/code&gt;. Every URL in that list is a ScriptTag. If the block is not there, you have none on the home page.&lt;/li&gt;
&lt;li&gt;Look at the domains and match them to apps you installed. Theme app embeds look different (&lt;code&gt;cdn.shopify.com/extensions/...&lt;/code&gt;) and do not appear in that list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or let the free checker do the reading: &lt;a href="https://citebeam-three.vercel.app/script-tags?utm_source=devto&amp;amp;utm_campaign=scripttags" rel="noopener noreferrer"&gt;paste your store address, no sign-up&lt;/a&gt;. It makes two requests to your public storefront, lists the ScriptTags with the vendor host behind each, shows any theme app extensions, and checks the legacy account question below. It is free, and it only reads pages any visitor can open.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do about it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask each vendor&lt;/strong&gt; which of its features use a ScriptTag today and what its plan is for 1 March 2027. You want to hear "app embed" or "theme app extension" and a date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch on the app embed&lt;/strong&gt; in the theme editor once the vendor ships it, and test the page.&lt;/li&gt;
&lt;li&gt;Analytics and conversion scripts should move to a &lt;strong&gt;web pixel&lt;/strong&gt;, which needs no action from the merchant.&lt;/li&gt;
&lt;li&gt;If a vendor has no plan, you have until 1 March 2027 to replace the app, which is a lot easier in November than on 28 February.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  One more deadline: legacy customer accounts
&lt;/h2&gt;

&lt;p&gt;Shopify deprecated legacy customer accounts on 26 February 2026. The final sunset date is to be announced later in 2026. In my sample, &lt;strong&gt;8 of the 43 stores I could read (19%)&lt;/strong&gt; still showed the classic login form, the signal that a store is still on legacy accounts (the other 35 redirect to the new sign-in; 2 stores' robots.txt does not allow &lt;code&gt;/account/login&lt;/code&gt;, so I did not fetch it). The checker reports this too.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you look after more than one store
&lt;/h2&gt;

&lt;p&gt;Agencies and freelancers can run the same check across a whole client list with the Apify actor &lt;a href="https://apify.com/highbrow_fame/shopify-script-tag-checker" rel="noopener noreferrer"&gt;Shopify Script Tag Checker&lt;/a&gt;. Give it a list of store domains (or a CSV link) and you get one row per store: a risk level, the ScriptTags and the vendor hosts behind them, the number of app embeds, whether the store is still on legacy customer accounts, and plain-English next steps. It checks a store for $0.01, so a 50-store client list is 50 cents, and stores it cannot check are free. It is going public on the Apify Store now.&lt;/p&gt;

&lt;p&gt;You can see a public example run here: &lt;a href="https://apify.com/highbrow_fame/shopify-script-tag-checker/examples/will-20-shopify-stores-break-1-march-2027" rel="noopener noreferrer"&gt;Will 20 Shopify stores break on 1 March 2027?&lt;/a&gt;. And here is a &lt;a href="https://youtu.be/3VxW1hmAaPc" rel="noopener noreferrer"&gt;short YouTube walkthrough&lt;/a&gt; of the checker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your next step
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check one store now: &lt;a href="https://citebeam-three.vercel.app/script-tags?utm_source=devto&amp;amp;utm_campaign=scripttags" rel="noopener noreferrer"&gt;free ScriptTag checker&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Run it on every client store: &lt;a href="https://apify.com/highbrow_fame/shopify-script-tag-checker" rel="noopener noreferrer"&gt;Apify actor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Forward this to the person who installed the apps, and put "ask vendors about 1 March 2027" on this week's list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deadline is fixed and the vendor answers take time. A short list of questions sent today beats a broken storefront in March.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Where are the tech jobs in Germany? Jobs by city, remote share and salaries (22 October 2026)</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:51:56 +0000</pubDate>
      <link>https://dev.to/projectdev/where-are-the-tech-jobs-in-germany-jobs-by-city-remote-share-and-salaries-22-october-2026-563p</link>
      <guid>https://dev.to/projectdev/where-are-the-tech-jobs-in-germany-jobs-by-city-remote-share-and-salaries-22-october-2026-563p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actors used to collect this data. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;München leads the German tech job market this week with 275 open software developer jobs on XING, ahead of Berlin (239).&lt;/strong&gt; Across the ten biggest job cities, 421 of 1,452 software developer jobs were posted or refreshed in the last seven days. Of the newly posted or refreshed tech jobs in my sample, 9.9% are fully remote and 53.2% hybrid; only 35.3% show a salary published by the employer, and 54.3% carry only an estimate computed by XING.&lt;/p&gt;

&lt;p&gt;If you are hunting for a developer, data or DevOps job in Germany, or benchmarking your own hiring, "which city, how much, how remote" is spread across thousands of ads. This report counts them from two job boards with the same searches every time, so changes between editions are real changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software developer jobs by city
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F21126o22vjrt1g8qqlkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F21126o22vjrt1g8qqlkm.png" alt="Open software developer jobs by city on XING" width="799" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;City&lt;/th&gt;
&lt;th&gt;Software developer jobs open&lt;/th&gt;
&lt;th&gt;Posted or refreshed in last 7 days&lt;/th&gt;
&lt;th&gt;Data engineer jobs open&lt;/th&gt;
&lt;th&gt;DevOps jobs open&lt;/th&gt;
&lt;th&gt;Hybrid or remote share (new jobs)&lt;/th&gt;
&lt;th&gt;Salary published by employer&lt;/th&gt;
&lt;th&gt;Change in open software jobs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;München&lt;/td&gt;
&lt;td&gt;275&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;151&lt;/td&gt;
&lt;td&gt;51.6%&lt;/td&gt;
&lt;td&gt;43%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Berlin&lt;/td&gt;
&lt;td&gt;239&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;205&lt;/td&gt;
&lt;td&gt;63.8%&lt;/td&gt;
&lt;td&gt;21.3%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hamburg&lt;/td&gt;
&lt;td&gt;187&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;td&gt;65.2%&lt;/td&gt;
&lt;td&gt;37.1%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Köln&lt;/td&gt;
&lt;td&gt;147&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;59.7%&lt;/td&gt;
&lt;td&gt;39.1%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frankfurt am Main&lt;/td&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;77.5%&lt;/td&gt;
&lt;td&gt;40.4%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stuttgart&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;td&gt;43.2%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nürnberg&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;69.3%&lt;/td&gt;
&lt;td&gt;37.3%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dresden&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;46.8%&lt;/td&gt;
&lt;td&gt;17.2%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Düsseldorf&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;59.5%&lt;/td&gt;
&lt;td&gt;39.3%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leipzig&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;61.6%&lt;/td&gt;
&lt;td&gt;30.8%&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Each role column counts the jobs XING returns for that keyword in that city, so a job can appear in more than one role. Shares come from a sample of up to 20 new jobs per search; cities with fewer than 10 sampled jobs show n/a. The last column compares with the previous edition (this is the first edition, shown as a dash).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  By role
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role (search keyword)&lt;/th&gt;
&lt;th&gt;Open jobs in the 10 cities&lt;/th&gt;
&lt;th&gt;Posted or refreshed in last 7 days&lt;/th&gt;
&lt;th&gt;Fully remote / hybrid (new jobs)&lt;/th&gt;
&lt;th&gt;Salary published&lt;/th&gt;
&lt;th&gt;Median published salary (per year)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data &amp;amp; AI ("data engineer")&lt;/td&gt;
&lt;td&gt;602&lt;/td&gt;
&lt;td&gt;203&lt;/td&gt;
&lt;td&gt;2.9% / 53.6%&lt;/td&gt;
&lt;td&gt;17.1%&lt;/td&gt;
&lt;td&gt;€77,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevOps &amp;amp; cloud ("devops")&lt;/td&gt;
&lt;td&gt;1,037&lt;/td&gt;
&lt;td&gt;382&lt;/td&gt;
&lt;td&gt;17% / 58.5%&lt;/td&gt;
&lt;td&gt;55.3%&lt;/td&gt;
&lt;td&gt;€80,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IT security ("it-sicherheit")&lt;/td&gt;
&lt;td&gt;1,038&lt;/td&gt;
&lt;td&gt;343&lt;/td&gt;
&lt;td&gt;11.6% / 57.8%&lt;/td&gt;
&lt;td&gt;39.5%&lt;/td&gt;
&lt;td&gt;€90,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IT admin &amp;amp; support ("it-administrator")&lt;/td&gt;
&lt;td&gt;1,482&lt;/td&gt;
&lt;td&gt;438&lt;/td&gt;
&lt;td&gt;7.6% / 59.4%&lt;/td&gt;
&lt;td&gt;40.6%&lt;/td&gt;
&lt;td&gt;€62,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software development ("softwareentwickler")&lt;/td&gt;
&lt;td&gt;1,452&lt;/td&gt;
&lt;td&gt;421&lt;/td&gt;
&lt;td&gt;9.7% / 38.9%&lt;/td&gt;
&lt;td&gt;23.8%&lt;/td&gt;
&lt;td&gt;€70,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2rvfeo20gajtjx7xpo7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2rvfeo20gajtjx7xpo7h.png" alt="Remote, hybrid and on-site share by role" width="799" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On XING, "published" means the employer stated a salary in the ad; for most ads XING shows its own estimate instead, which I count separately and do not treat as a salary disclosure. The median uses published salaries with a yearly range between €20,000 and €300,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second source: stellenanzeigen.de, the complete list
&lt;/h2&gt;

&lt;p&gt;stellenanzeigen.de is a smaller board with fewer tech ads, which means I can read all of them. This week it listed &lt;strong&gt;580 distinct tech jobs&lt;/strong&gt; across 27 tech keywords, 177 of them posted in the last seven days. Home office is possible or full in &lt;strong&gt;56.7%&lt;/strong&gt; of them (1.2% fully remote), and &lt;strong&gt;13.6%&lt;/strong&gt; state a salary, with a median advertised yearly salary of €67,500 (61 ads with a yearly range).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Jobs&lt;/th&gt;
&lt;th&gt;Posted in last 7 days&lt;/th&gt;
&lt;th&gt;Home office possible or full&lt;/th&gt;
&lt;th&gt;Salary stated&lt;/th&gt;
&lt;th&gt;Median salary (per year)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data &amp;amp; AI&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;48.6%&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;€80,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevOps &amp;amp; cloud&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;td&gt;€75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IT security&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;65.5%&lt;/td&gt;
&lt;td&gt;16.4%&lt;/td&gt;
&lt;td&gt;€70,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAP &amp;amp; ERP&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;92.7%&lt;/td&gt;
&lt;td&gt;8.5%&lt;/td&gt;
&lt;td&gt;€80,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IT admin &amp;amp; support&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;47.9%&lt;/td&gt;
&lt;td&gt;18.8%&lt;/td&gt;
&lt;td&gt;€56,770&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software development&lt;/td&gt;
&lt;td&gt;132&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;60.6%&lt;/td&gt;
&lt;td&gt;18.9%&lt;/td&gt;
&lt;td&gt;€65,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most common workplaces on stellenanzeigen.de (a job with several workplaces counts for each city):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;City&lt;/th&gt;
&lt;th&gt;Jobs&lt;/th&gt;
&lt;th&gt;Share of tech jobs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hamburg&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frankfurt am Main&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;4.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dortmund&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;4.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Berlin&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;3.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Köln&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;3.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;München&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;3.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Düsseldorf&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;2.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finsterwalde&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stuttgart&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Karlsruhe&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sources:&lt;/strong&gt; the &lt;a href="https://apify.com/highbrow_fame/xing-jobs" rel="noopener noreferrer"&gt;XING Jobs Scraper&lt;/a&gt; and the &lt;a href="https://apify.com/highbrow_fame/stellenanzeigen-jobs" rel="noopener noreferrer"&gt;stellenanzeigen.de Scraper&lt;/a&gt; on Apify, run on 2026-10-22 with fixed inputs: 5 role keywords ("data engineer", "devops", "it-sicherheit", "it-administrator", "softwareentwickler") in ten cities (Berlin, München, Hamburg, Frankfurt am Main, Köln, Stuttgart, Düsseldorf, Leipzig, Dresden, Nürnberg) on XING; 27 tech keywords across all of Germany on stellenanzeigen.de.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counts:&lt;/strong&gt; the open-job and new-job numbers on XING are the totals XING reports for each search at run time. The stellenanzeigen.de figures are counted from the complete list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Samples:&lt;/strong&gt; work mode and salary shares for XING come from up to 20 jobs per search posted or refreshed in the last 7 days (XING's own date filter) (801 distinct jobs in total), so read them as estimates of the new-job mix, not of every open ad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What the keywords catch:&lt;/strong&gt; a keyword search matches ads that mention the term, so some matches are only loosely related. The same searches run every week, which keeps editions comparable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregates only:&lt;/strong&gt; no job titles, ad texts, company names or contact details are used or published here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to get this data yourself, every day
&lt;/h2&gt;

&lt;p&gt;Everything above was collected by these Apify Actors from a fixed input, plus some arithmetic. You can run the same collection on your own Apify account in a couple of clicks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apify.com/highbrow_fame/xing-jobs" rel="noopener noreferrer"&gt;XING Jobs Scraper&lt;/a&gt;: $0.80 per 1,000 results on the free plan, you pay only for results delivered.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apify.com/highbrow_fame/stellenanzeigen-jobs" rel="noopener noreferrer"&gt;stellenanzeigen.de Scraper&lt;/a&gt;: $1.20 per 1,000 results on the free plan, you pay only for results delivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Collecting the data for this edition costs about $1.92 on Apify's free plan&lt;/strong&gt; (you pay per result delivered, nothing for a search that returns nothing), so a weekly refresh is about $8 a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily monitor in three steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Actor, paste an input like the one below, and switch on &lt;strong&gt;Only new&lt;/strong&gt; (&lt;code&gt;onlyNew&lt;/code&gt;). The first run returns everything; every later run returns only what no earlier run of the same watch list delivered, and skipped items are not charged.&lt;/li&gt;
&lt;li&gt;Save the input as a task and put it on a schedule (for example the cron expression &lt;code&gt;0 7 * * *&lt;/code&gt;, every day at 07:00 UTC).&lt;/li&gt;
&lt;li&gt;Send the result where you work: e-mail, Slack or Google Sheets, from the task's &lt;em&gt;Integrations&lt;/em&gt; tab.
&lt;/li&gt;
&lt;/ol&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;"keywords"&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="s2"&gt;"softwareentwickler"&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;"locations"&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="s2"&gt;"Berlin"&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;"maxListingsPerSearch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onlyNew"&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;"monitorKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"softwareentwickler-berlin"&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;This input is for the stellenanzeigen.de Scraper, which has the only-new switch (&lt;code&gt;onlyNew&lt;/code&gt; plus a &lt;code&gt;monitorKey&lt;/code&gt; that names your watch list). On the XING scraper, use &lt;strong&gt;Posted within&lt;/strong&gt; (&lt;code&gt;postedWithinDays&lt;/code&gt;: 1 or 7) with a daily schedule to get the same effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want your own cut: your stack, your city, your salary band?&lt;/strong&gt; Run the &lt;a href="https://apify.com/highbrow_fame/xing-jobs" rel="noopener noreferrer"&gt;XING Jobs Scraper&lt;/a&gt; or the &lt;a href="https://apify.com/highbrow_fame/stellenanzeigen-jobs" rel="noopener noreferrer"&gt;stellenanzeigen.de Scraper&lt;/a&gt; on Apify, type your keywords and towns, and get every job with its salary, work mode and posting date as a spreadsheet. The input forms are prefilled and the first run takes a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next edition:&lt;/strong&gt; follow me here on dev.to for the next weekly count, and use the daily monitor above if you want new jobs in your inbox every morning.&lt;/p&gt;

</description>
      <category>data</category>
      <category>germany</category>
      <category>jobs</category>
      <category>career</category>
    </item>
    <item>
      <title>EU customs identifiers from 1 Nov 2026: audit SKU coverage of many Shopify stores with one API call</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:50:03 +0000</pubDate>
      <link>https://dev.to/projectdev/eu-customs-identifiers-from-1-nov-2026-audit-sku-coverage-of-many-shopify-stores-with-one-api-call-5a3d</link>
      <guid>https://dev.to/projectdev/eu-customs-identifiers-from-1-nov-2026-audit-sku-coverage-of-many-shopify-stores-with-one-api-call-5a3d</guid>
      <description>&lt;p&gt;If you look after more than one Shopify store, the EU customs deadline of &lt;strong&gt;1 November 2026&lt;/strong&gt; is a catalog problem multiplied by your client list. The &lt;strong&gt;Shopify AI Readiness Audit&lt;/strong&gt; on Apify answers the first, cheapest question for all of them in one call: how many variants in each store have a usable SKU, which SKUs are duplicated, and does the product page print a valid GTIN. It costs &lt;strong&gt;$0.01 per store&lt;/strong&gt; (500 stores = $5), and stores it cannot read are free. &lt;strong&gt;&lt;a href="https://apify.com/highbrow_fame/shopify-ai-readiness-audit" rel="noopener noreferrer"&gt;Run it on your own list of stores&lt;/a&gt;&lt;/strong&gt;. If you want to see it work first, open the ready-made &lt;a href="https://apify.com/highbrow_fame/shopify-ai-readiness-audit/examples/eu-customs-id-check-20-shopify-stores" rel="noopener noreferrer"&gt;EU customs ID check example task for 20 Shopify stores&lt;/a&gt; and press Start.&lt;/p&gt;

&lt;p&gt;I am not giving legal advice, and I will say plainly what this can and cannot see further down. First, why SKUs are the right place to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SKUs, and why now
&lt;/h2&gt;

&lt;p&gt;From 1 November 2026, EU customs declarations for online sales of goods to consumers need product identifiers for each item: your own product code, a manufacturer or supplier code, and a standardised code such as a GTIN where the product has one. For a Shopify merchant, your own product code is the &lt;strong&gt;variant SKU&lt;/strong&gt;. A variant with no SKU, a placeholder such as &lt;code&gt;N/A&lt;/code&gt;, or a SKU shared by several variants cannot identify anything.&lt;/p&gt;

&lt;p&gt;For one store you fix that in a spreadsheet. For an agency with 40 clients, a developer building a catalog-cleanup service, or an analyst comparing a market, opening every store by hand does not scale. The public storefront already shows enough to size the job: every Shopify store that leaves it open serves its product list at &lt;code&gt;/products.json&lt;/code&gt;, SKUs included.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 1,071 stores looked like
&lt;/h2&gt;

&lt;p&gt;In October 2026 we read the public data of 1,071 live Shopify stores (the first 50 products of each feed plus one product page). No store is named here. Two numbers matter for this deadline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;50.3% of products have at least one variant with no SKU.&lt;/strong&gt; 79.6% of the stores have at least one such product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8.4% of the stores print a GTIN in the Product JSON-LD of the product page we read.&lt;/strong&gt; That is a lower bound, not the share of stores with barcodes: the public feed does not show barcodes, and a theme only prints one if it is set up to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Half the products in that sample have a SKU gap. If your client list looks like the sample, a large part of it needs work before the deadline, and you want to know which part.&lt;/p&gt;

&lt;h2&gt;
  
  
  One API call for the whole list
&lt;/h2&gt;

&lt;p&gt;The audit is an Apify Actor, so any language with an HTTP client can drive it. In Python, with the &lt;code&gt;apify-client&lt;/code&gt; package (&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;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;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;highbrow_fame/shopify-ai-readiness-audit&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;stores&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;theme-dawn-demo.myshopify.com&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;client-one.example.com&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;client-two.example.com&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;maxProducts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="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;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;status&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;ok&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;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;input&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;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# unreadable stores are free
&lt;/span&gt;        &lt;span class="k"&gt;continue&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;store&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;euSkuCoveragePct&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;euVariantsWithoutSku&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;euDuplicateSkus&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;euGtinInJsonLd&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap the list for your own, or pass &lt;code&gt;csvUrl&lt;/code&gt; with a public link to a file with one store per line. Up to 5,000 stores fit in a run. You can also paste the same list into the Actor's input form on the Store page and download the rows as JSON, CSV or Excel, no code needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What one row looks like
&lt;/h2&gt;

&lt;p&gt;This is the audit of Shopify's own demo store, &lt;code&gt;theme-dawn-demo.myshopify.com&lt;/code&gt; (a store Shopify publishes for its free Dawn theme, so nobody's real shop), trimmed to the fields that matter here:&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;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"theme-dawn-demo.myshopify.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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"readinessScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"euSkuCoveragePct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.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;"euVariantsWithoutSku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;159&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"euVariantsSampled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;266&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"euDuplicateSkus"&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;"euGtinInJsonLd"&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;Read it like this: 159 of the 266 sampled variants have no SKU (so 40.2% coverage), no SKU value is used twice, and the product page prints a GTIN with a valid check digit. The overall readiness score is 95 out of 100, because the other parts (product feed completeness, Product structured data, AI crawler access) are in good shape. The EU fields do &lt;strong&gt;not&lt;/strong&gt; change that score, and that is the point: a store can look healthy and still have a SKU gap, and now the gap has its own columns. When SKUs are missing, one of the row's top-three fixes says so in plain English ("Give every variant its own SKU").&lt;/p&gt;

&lt;p&gt;Sort the table by &lt;code&gt;euSkuCoveragePct&lt;/code&gt; ascending and you have your work queue for the next days.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;$0.01 per store audited&lt;/strong&gt;, charged only for stores with status &lt;code&gt;ok&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;20 stores = $0.20. 500 stores = $5. 1,000 stores = $10.&lt;/li&gt;
&lt;li&gt;Stores that cannot be audited are free: product feed not public, robots.txt disallows it, blocked, rate limited, not a Shopify store, empty catalog.&lt;/li&gt;
&lt;li&gt;Set a &lt;em&gt;Maximum charge&lt;/em&gt; on the run and it stops cleanly when the budget is used up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest caveat
&lt;/h2&gt;

&lt;p&gt;This is a &lt;strong&gt;readiness proxy&lt;/strong&gt;, not a customs check. The public product feed carries no barcodes and no manufacturer codes, so the Actor cannot tell you that a catalog is complete. A clean &lt;code&gt;euSkuCoveragePct&lt;/code&gt; of 100 means your own product codes are in order; it does not mean the manufacturer codes and barcodes are, and &lt;code&gt;euGtinInJsonLd&lt;/code&gt; describes the single product page that is read, not the whole catalog. It is also not legal advice: which of your parcels are in scope and what data your carrier needs is for your carrier or customs broker to confirm.&lt;/p&gt;

&lt;p&gt;The Actor is polite by design: it reads robots.txt first and obeys it, makes at most three requests per store, identifies itself, and never retries after a refusal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agencies and freelancers&lt;/strong&gt; who want one comparable table for a client list instead of 40 browser tabs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers and app builders&lt;/strong&gt; who need a lead list sorted by how much a store has to fix, or who want a SKU-readiness check inside their own tool through the API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analysts&lt;/strong&gt; who want to measure the catalog quality of a market segment, the way the 1,071-store sample above was read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Python client is documented in the &lt;a href="https://docs.apify.com/api/client/python" rel="noopener noreferrer"&gt;Apify docs&lt;/a&gt;. If you would rather call Apify Actors from an AI assistant than from Python, the &lt;a href="https://github.com/projectworks007/apify-data-mcp" rel="noopener noreferrer"&gt;apify-data-mcp project on GitHub&lt;/a&gt; shows how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do it before the 1st
&lt;/h2&gt;

&lt;p&gt;1 November is close. A first run of 20 stores costs $0.20 and takes seconds: &lt;strong&gt;&lt;a href="https://apify.com/highbrow_fame/shopify-ai-readiness-audit" rel="noopener noreferrer"&gt;open the Actor, paste your stores, press Start&lt;/a&gt;&lt;/strong&gt;. If it saves you an afternoon of tab-hopping, a short review on the Store page tells me what to improve next. And if you remember only one thing from this post: start with the SKUs, and &lt;strong&gt;&lt;a href="https://apify.com/highbrow_fame/shopify-ai-readiness-audit" rel="noopener noreferrer"&gt;audit the whole list in one go&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>python</category>
      <category>europe</category>
    </item>
    <item>
      <title>EU customs identifiers from 1 Nov 2026: what 1,071 Shopify stores look like</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Fri, 23 Oct 2026 12:38:46 +0000</pubDate>
      <link>https://dev.to/projectdev/eu-customs-identifiers-from-1-nov-2026-what-1071-shopify-stores-look-like-2a58</link>
      <guid>https://dev.to/projectdev/eu-customs-identifiers-from-1-nov-2026-what-1071-shopify-stores-look-like-2a58</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I build Citebeam, the Shopify app mentioned in this post. The numbers use public data only and the method is open, so you can check them yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From &lt;strong&gt;1 November 2026&lt;/strong&gt;, EU customs declarations for online sales of imported goods to consumers have to carry product identifiers for each item, and customs can hold parcels that lack required data. &lt;strong&gt;Citebeam&lt;/strong&gt;, the Shopify app I build, has a free checker that shows how your catalog looks on the identifier basics in about ten seconds: &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/check?utm_source=devto&amp;amp;utm_campaign=eu-pid" rel="noopener noreferrer"&gt;run it on your store, no sign-up&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I am not giving legal advice. Which of your parcels are in scope, and in what format the data goes, is for your carrier or customs broker to confirm, and carriers have started publishing what they want from merchants. What I can give you is data: what the identifier basics look like across 1,071 live Shopify stores, what Shopify itself says about its fields, and a 20-minute pass that gets a catalog ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three identifiers, in Shopify terms
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your own product code (M-PID).&lt;/strong&gt; In Shopify that is the variant SKU. Every variant needs its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The manufacturer's or supplier's code (NS-PID).&lt;/strong&gt; A model or part number. Shopify now lets you enter it as a variant barcode of type NS-PID (Admin API 2026-10 and the admin).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A standardised identifier (S-PID).&lt;/strong&gt; A GTIN, EAN, UPC or ISBN, &lt;strong&gt;where the product has one&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What 1,071 stores look like
&lt;/h2&gt;

&lt;p&gt;In October 2026 I read the public data of 1,071 live Shopify stores (42,560 products): the public product feed (first 50 products) and one product page each. No store is named. Two numbers matter here, and both are public-data proxies, not customs data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4tp89wq3260ypi1bqdv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4tp89wq3260ypi1bqdv5.png" alt="Share of products failing each product-data rule" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;50.3% of products have at least one variant with no SKU.&lt;/strong&gt; 79.6% of the stores have at least one such product, and in 50.2% of the stores most products have one. The SKU is the usual M-PID, and a SKU that is blank or shared by several variants cannot identify one variant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8.4% of the stores print a GTIN in the Product JSON-LD of the product page I read&lt;/strong&gt; (10.6% of the pages that carry Product JSON-LD at all). Read that as a lower bound, not as the share of stores that have barcodes: the public feed does not show barcodes, and a theme prints one into its structured data only if it is set up to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The manufacturer's code cannot be seen from outside at all. That one only exists in your admin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Shopify says about its own fields (as of 23 October)
&lt;/h2&gt;

&lt;p&gt;Shopify staff have answered several of the open questions on the developer forum. The short version, with the sources at the end of this section:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A barcode with no declared type ("Custom") that holds a valid GTIN already counts as the S-PID.&lt;/strong&gt; You should not tick "Has no standardized identifier" for those variants. Staff added that the safer move is still to set the barcode's type to GTIN, EAN, UPC or ISBN rather than rely on Shopify recognising it. The declaration is for variants whose barcodes are SKUs, internal codes or other values that are not valid GTINs. (Staff, 20 October.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If a variant has no NS-PID, Shopify fills one in&lt;/strong&gt; from its other barcodes, the SKU or the variant ID. Your supplier's real code is better, and you should keep &lt;strong&gt;one&lt;/strong&gt; NS-PID barcode per variant: which one is used when there are several is not documented. (Staff, 20 October.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Has no standardized identifier" cannot be set in bulk from the admin today.&lt;/strong&gt; There is no CSV column and no bulk-editor column. Through the API it is in the unstable version and in the 2027-01 release candidate, which becomes the stable version on 1 January. A placeholder such as "None" in the barcode field does not record it. (Staff, 19 to 22 October.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passing the declaration on to carriers&lt;/strong&gt; was "not live just yet" on 20 October, so ask your carrier how they take it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sources: Shopify's &lt;a href="https://shopify.dev/docs/api/admin-graphql/latest/enums/BarcodeType" rel="noopener noreferrer"&gt;BarcodeType documentation&lt;/a&gt; and the developer-forum threads &lt;a href="https://community.shopify.dev/t/about-eu-new-barcode-requirements-and-barcode-types/38703" rel="noopener noreferrer"&gt;About EU new barcode requirements and barcode types&lt;/a&gt; and &lt;a href="https://community.shopify.dev/t/how-to-bulk-edit-the-new-has-no-standardized-identifier-checkbox/38683" rel="noopener noreferrer"&gt;How to bulk edit the new "Has no standardized identifier" checkbox&lt;/a&gt;. This is Shopify's description of its own fields, so check it against the current docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 20-minute pass before 1 November
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SKUs first.&lt;/strong&gt; Export Products as CSV. In the Variant SKU column, filter the blanks, then flag duplicates (in Google Sheets, &lt;code&gt;=COUNTIF(A:A, A2)&amp;gt;1&lt;/code&gt; on that column). Replace placeholders such as N/A or 0. Fix products with many variants first: that is where a parent SKU gets copied to every size and colour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Barcodes second.&lt;/strong&gt; For each variant decide: a real GTIN goes in the barcode field, as digits only; if the product has none, declare it on the variant instead of typing a placeholder. A mistyped barcode is worse than none, so check the last digit. This is the check digit test Citebeam uses:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GTIN-8/12/13/14 check digit (GS1 mod 10)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validGtin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&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="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{8}&lt;/span&gt;&lt;span class="sr"&gt;$|^&lt;/span&gt;&lt;span class="se"&gt;\d{12,14}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&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="dl"&gt;""&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;Number&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;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&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;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&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="mi"&gt;3&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="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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manufacturer codes third.&lt;/strong&gt; If you make the product, the code is yours to assign. If you resell it, ask the supplier for the maker's model or part number, and enter it as an NS-PID barcode. Never invent one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HS code and country of origin.&lt;/strong&gt; They sit on the variant (Shipping section) and carriers ask for them too. The origin is where the item was made, not where you ship from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand your carrier a file&lt;/strong&gt;, one line per variant: handle, variant title, SKU, barcode, vendor, manufacturer code, HS code, country of origin.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Skip the spreadsheet
&lt;/h2&gt;

&lt;p&gt;Doing this across hundreds of variants by hand is the job Citebeam is built for. &lt;strong&gt;The free checker&lt;/strong&gt; reports your SKU coverage, missing and duplicated SKUs, and whether a product page publishes a GTIN, from public data only. The Citebeam app goes further from inside your Shopify admin: it reads every variant (it only reads, it stores nothing from this check and needs no new permission), lists the variants with a missing or duplicated SKU, a barcode that fails the check digit or is not a GTIN, no vendor, no manufacturer code, and gives you that carrier file as a CSV with the manufacturer-code column ready for you to fill from your suppliers.&lt;/p&gt;

&lt;p&gt;The data behind the study, the method and its limits are public: &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/research/ai-shopping-readiness-2026?utm_source=devto&amp;amp;utm_campaign=eu-pid#eu-customs-identifiers" rel="noopener noreferrer"&gt;the research page, including the EU customs identifiers section&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I measured (short version)
&lt;/h2&gt;

&lt;p&gt;Stores came from Common Crawl's October 2026 index of myshopify.com addresses; I probed 1,461 and 1,071 qualified (public feed, at least 10 products). Per store: robots.txt, up to 50 products from the public feed and one product page, with at most 3 requests, an honest user agent and robots.txt respected. For the 58.6% of stores with more than 50 products, only the first 50 are represented, and structured data comes from one product page per store without running JavaScript. The sample leans toward smaller and newer merchants.&lt;/p&gt;

&lt;p&gt;1 November is close. Start with the SKUs: &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/check?utm_source=devto&amp;amp;utm_campaign=eu-pid" rel="noopener noreferrer"&gt;run the free check&lt;/a&gt;&lt;/strong&gt; and you will know in ten seconds how big that job is for your store.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>customs</category>
      <category>europe</category>
    </item>
    <item>
      <title>Coles vs Woolworths vs Aldi: weekly grocery price index (22 October 2026)</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Fri, 23 Oct 2026 08:50:10 +0000</pubDate>
      <link>https://dev.to/projectdev/coles-vs-woolworths-vs-aldi-weekly-grocery-price-index-22-october-2026-33mc</link>
      <guid>https://dev.to/projectdev/coles-vs-woolworths-vs-aldi-weekly-grocery-price-index-22-october-2026-33mc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actors used to collect this data. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Aldi is the cheapest of the three for this week's basket of 31 staples ($105.40), Coles the dearest ($128.57, 22% more).&lt;/strong&gt; With Aldi at 100, Coles is at 122 and Woolworths at 117.5. Between the two big chains, Coles is 3.8% dearer than Woolworths on this basket.&lt;/p&gt;

&lt;p&gt;Supermarket price comparisons are usually one-off snapshots. This one is rebuilt every week from the shelf prices the three chains show online, for the same 31 items every time, so the trend is visible. Prices are for the cheapest matching product per item (usually a home brand), compared per kilogram or litre, so pack sizes do not distort the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  This week's basket
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sjedn2watjt3028c3ac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sjedn2watjt3028c3ac.png" alt="Grocery basket total at Coles, Woolworths and Aldi" width="800" height="169"&gt;&lt;/a&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;Coles&lt;/th&gt;
&lt;th&gt;Woolworths&lt;/th&gt;
&lt;th&gt;Aldi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basket total (31 items)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$128.57&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$123.81&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$105.40&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index (Aldi = 100)&lt;/td&gt;
&lt;td&gt;122&lt;/td&gt;
&lt;td&gt;117.5&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basket at regular prices (without specials)&lt;/td&gt;
&lt;td&gt;$129.86&lt;/td&gt;
&lt;td&gt;$135.20&lt;/td&gt;
&lt;td&gt;$105.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Items where the cheapest match is on special&lt;/td&gt;
&lt;td&gt;5 of 31&lt;/td&gt;
&lt;td&gt;3 of 31&lt;/td&gt;
&lt;td&gt;10 of 31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Share of all matching products on special&lt;/td&gt;
&lt;td&gt;23.3%&lt;/td&gt;
&lt;td&gt;15.3%&lt;/td&gt;
&lt;td&gt;29.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Items where this chain has the lowest price (ties count for each)&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;On special: Coles and Woolworths flag a product as on special when it has a discount or multi-buy deal. Aldi shows no "was" price, so its basket is the same with or without specials; Aldi products on its Lower Prices, Super Savers and Limited Time Only shelves are flagged as promotions here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxc61s39ndnej1usyk37q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxc61s39ndnej1usyk37q.png" alt="Basket cost by category" width="799" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Week-over-week changes appear from the second edition on: this report stores every edition, and the next one will show which items moved.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The basket, item by item
&lt;/h2&gt;

&lt;p&gt;Price per kilogram or litre of the cheapest matching product (an asterisk means that product is on special today). The basket buys the quantity shown.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Coles&lt;/th&gt;
&lt;th&gt;Woolworths&lt;/th&gt;
&lt;th&gt;Aldi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full cream milk&lt;/td&gt;
&lt;td&gt;2 L&lt;/td&gt;
&lt;td&gt;$1.70&lt;/td&gt;
&lt;td&gt;$1.65&lt;/td&gt;
&lt;td&gt;$1.65*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eggs, 12 pack&lt;/td&gt;
&lt;td&gt;0.7 kg&lt;/td&gt;
&lt;td&gt;$8.50&lt;/td&gt;
&lt;td&gt;$7.50&lt;/td&gt;
&lt;td&gt;$7.10*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Butter&lt;/td&gt;
&lt;td&gt;0.25 kg&lt;/td&gt;
&lt;td&gt;$18.00&lt;/td&gt;
&lt;td&gt;$16.80&lt;/td&gt;
&lt;td&gt;$14.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasty / cheddar cheese block&lt;/td&gt;
&lt;td&gt;0.5 kg&lt;/td&gt;
&lt;td&gt;$9.30&lt;/td&gt;
&lt;td&gt;$10.50&lt;/td&gt;
&lt;td&gt;$8.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Natural yoghurt&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$7.50&lt;/td&gt;
&lt;td&gt;$4.20&lt;/td&gt;
&lt;td&gt;$3.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;White sandwich bread&lt;/td&gt;
&lt;td&gt;0.7 kg&lt;/td&gt;
&lt;td&gt;$6.60&lt;/td&gt;
&lt;td&gt;$7.40&lt;/td&gt;
&lt;td&gt;$3.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long grain white rice&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spaghetti&lt;/td&gt;
&lt;td&gt;0.5 kg&lt;/td&gt;
&lt;td&gt;$1.90*&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain flour&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;White sugar&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$1.30&lt;/td&gt;
&lt;td&gt;$1.30&lt;/td&gt;
&lt;td&gt;$1.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rolled oats&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$2.30&lt;/td&gt;
&lt;td&gt;$2.10&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corn flakes&lt;/td&gt;
&lt;td&gt;0.75 kg&lt;/td&gt;
&lt;td&gt;$5.60&lt;/td&gt;
&lt;td&gt;$8.40&lt;/td&gt;
&lt;td&gt;$5.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canned diced tomatoes&lt;/td&gt;
&lt;td&gt;0.8 kg&lt;/td&gt;
&lt;td&gt;$2.38&lt;/td&gt;
&lt;td&gt;$2.38&lt;/td&gt;
&lt;td&gt;$2.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baked beans&lt;/td&gt;
&lt;td&gt;0.84 kg&lt;/td&gt;
&lt;td&gt;$3.10&lt;/td&gt;
&lt;td&gt;$3.80&lt;/td&gt;
&lt;td&gt;$3.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canned tuna&lt;/td&gt;
&lt;td&gt;0.38 kg&lt;/td&gt;
&lt;td&gt;$10.53&lt;/td&gt;
&lt;td&gt;$9.19&lt;/td&gt;
&lt;td&gt;$9.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peanut butter&lt;/td&gt;
&lt;td&gt;0.375 kg&lt;/td&gt;
&lt;td&gt;$13.10*&lt;/td&gt;
&lt;td&gt;$13.30*&lt;/td&gt;
&lt;td&gt;$6.00*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra virgin olive oil&lt;/td&gt;
&lt;td&gt;0.5 L&lt;/td&gt;
&lt;td&gt;$14.00&lt;/td&gt;
&lt;td&gt;$13.00*&lt;/td&gt;
&lt;td&gt;$8.80*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frozen peas&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$4.40&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$2.49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vanilla ice cream&lt;/td&gt;
&lt;td&gt;2 L&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$2.50*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Royal Gala apples&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$7.90&lt;/td&gt;
&lt;td&gt;$7.90&lt;/td&gt;
&lt;td&gt;$6.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Washed potatoes&lt;/td&gt;
&lt;td&gt;2 kg&lt;/td&gt;
&lt;td&gt;$3.85&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$2.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brown onions&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$2.65&lt;/td&gt;
&lt;td&gt;$2.75&lt;/td&gt;
&lt;td&gt;$2.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrots&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$1.73&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$2.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tomatoes&lt;/td&gt;
&lt;td&gt;0.5 kg&lt;/td&gt;
&lt;td&gt;$3.90*&lt;/td&gt;
&lt;td&gt;$5.90&lt;/td&gt;
&lt;td&gt;$4.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chicken breast fillets&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$8.50*&lt;/td&gt;
&lt;td&gt;$9.50&lt;/td&gt;
&lt;td&gt;$8.99*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beef mince&lt;/td&gt;
&lt;td&gt;0.5 kg&lt;/td&gt;
&lt;td&gt;$15.40*&lt;/td&gt;
&lt;td&gt;$15.40&lt;/td&gt;
&lt;td&gt;$12.98*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rindless bacon&lt;/td&gt;
&lt;td&gt;0.25 kg&lt;/td&gt;
&lt;td&gt;$17.60&lt;/td&gt;
&lt;td&gt;$17.60*&lt;/td&gt;
&lt;td&gt;$13.98*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orange juice&lt;/td&gt;
&lt;td&gt;2 L&lt;/td&gt;
&lt;td&gt;$2.75&lt;/td&gt;
&lt;td&gt;$2.10&lt;/td&gt;
&lt;td&gt;$2.00*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frozen mixed vegetables&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$2.80&lt;/td&gt;
&lt;td&gt;$2.80&lt;/td&gt;
&lt;td&gt;$2.79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frozen potato chips&lt;/td&gt;
&lt;td&gt;1 kg&lt;/td&gt;
&lt;td&gt;$3.15&lt;/td&gt;
&lt;td&gt;$3.15&lt;/td&gt;
&lt;td&gt;$2.99*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dishwashing liquid&lt;/td&gt;
&lt;td&gt;1 L&lt;/td&gt;
&lt;td&gt;$1.40&lt;/td&gt;
&lt;td&gt;$1.40&lt;/td&gt;
&lt;td&gt;$1.40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; the &lt;a href="https://apify.com/highbrow_fame/coles-au-products" rel="noopener noreferrer"&gt;Coles&lt;/a&gt;, &lt;a href="https://apify.com/highbrow_fame/woolworths-au-products" rel="noopener noreferrer"&gt;Woolworths&lt;/a&gt; and &lt;a href="https://apify.com/highbrow_fame/aldi-au-products" rel="noopener noreferrer"&gt;ALDI&lt;/a&gt; scrapers on Apify, run on 2026-10-22 with a fixed list of 31 search terms, the first 20 to 30 results per search at each chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Like for like:&lt;/strong&gt; a product counts for an item only if its name matches that item (for example "full cream" milk, not lactose-free, flavoured or A2) and it has a unit price. Only items with a match at all three chains enter the totals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheapest match:&lt;/strong&gt; for each item the lowest unit price among the matching products is used. That favours home brands, which is what a price-conscious shopper buys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Online shelf prices:&lt;/strong&gt; the prices are those each chain's website shows without choosing a store, in Australian dollars. Coles prices vary a little from store to store. Prices include any special that is active that day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not an official index:&lt;/strong&gt; this is a small, fixed basket of staples, not the whole shop, and not a substitute for ABS or ACCC figures. Aldi's range is narrower, which is why some items have fewer matches there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate prices only:&lt;/strong&gt; product names and prices are shown; no personal data is involved.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to get this data yourself, every day
&lt;/h2&gt;

&lt;p&gt;Everything above was collected by these Apify Actors from a fixed input, plus some arithmetic. You can run the same collection on your own Apify account in a couple of clicks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apify.com/highbrow_fame/coles-au-products" rel="noopener noreferrer"&gt;Coles Australia Scraper&lt;/a&gt;: $1.00 per 1,000 results on the free plan, you pay only for results delivered.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apify.com/highbrow_fame/woolworths-au-products" rel="noopener noreferrer"&gt;Woolworths Australia Scraper&lt;/a&gt;: $0.70 per 1,000 results on the free plan, you pay only for results delivered.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apify.com/highbrow_fame/aldi-au-products" rel="noopener noreferrer"&gt;Aldi Australia Scraper&lt;/a&gt;: $1.00 per 1,000 results on the free plan, you pay only for results delivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Collecting the data for this edition costs about $1.49 on Apify's free plan&lt;/strong&gt; (you pay per result delivered, nothing for a search that returns nothing), so a weekly refresh is about $6 a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily monitor in three steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Actor, paste an input like the one below, and switch on &lt;strong&gt;Only new&lt;/strong&gt; (&lt;code&gt;onlyNew&lt;/code&gt;). The first run returns everything; every later run returns only what no earlier run of the same watch list delivered, and skipped items are not charged.&lt;/li&gt;
&lt;li&gt;Save the input as a task and put it on a schedule (for example the cron expression &lt;code&gt;0 7 * * *&lt;/code&gt;, every day at 07:00 UTC).&lt;/li&gt;
&lt;li&gt;Send the result where you work: e-mail, Slack or Google Sheets, from the task's &lt;em&gt;Integrations&lt;/em&gt; tab.
&lt;/li&gt;
&lt;/ol&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;"searchTerms"&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="s2"&gt;"milk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"eggs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bread"&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;"maxProductsPerInput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onlyNew"&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;"monitorKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coles-staples"&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 same fields work in the Woolworths scraper. With &lt;code&gt;onlyNew&lt;/code&gt; on, a product whose price changed since the last run comes back again with the old and new price in &lt;code&gt;changes&lt;/code&gt;, so the monitor doubles as a price-drop alert. The ALDI scraper has no only-new switch: schedule it and compare the runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want your own basket, with your brands, your store and your postcode?&lt;/strong&gt; Run the &lt;a href="https://apify.com/highbrow_fame/coles-au-products" rel="noopener noreferrer"&gt;Coles&lt;/a&gt;, &lt;a href="https://apify.com/highbrow_fame/woolworths-au-products" rel="noopener noreferrer"&gt;Woolworths&lt;/a&gt; or &lt;a href="https://apify.com/highbrow_fame/aldi-au-products" rel="noopener noreferrer"&gt;ALDI&lt;/a&gt; scraper on Apify: type the products you buy, press Start, and get prices, unit prices and specials as a spreadsheet. The input forms are prefilled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next edition:&lt;/strong&gt; follow me here on dev.to for the next weekly index, or switch on the daily monitor above and get price changes the moment they happen.&lt;/p&gt;

</description>
      <category>data</category>
      <category>australia</category>
      <category>groceries</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>I checked 1,071 Shopify stores for ChatGPT shopping readiness. Here is what is missing before Black Friday</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Fri, 23 Oct 2026 08:39:22 +0000</pubDate>
      <link>https://dev.to/projectdev/i-checked-1071-shopify-stores-for-chatgpt-shopping-readiness-here-is-what-is-missing-before-black-108i</link>
      <guid>https://dev.to/projectdev/i-checked-1071-shopify-stores-for-chatgpt-shopping-readiness-here-is-what-is-missing-before-black-108i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I build Citebeam, the Shopify app mentioned in this post. The data is public-feed only and the method is open, so you can check it yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Black Friday is on November 27, 2026. More shoppers now ask an AI assistant what to buy, and an assistant can only recommend what it can read. &lt;strong&gt;Citebeam&lt;/strong&gt; is the Shopify app I build to check a catalog for exactly that, and its checker is free: &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/check?utm_source=devto&amp;amp;utm_campaign=study" rel="noopener noreferrer"&gt;run it on your store in ten seconds, no sign-up&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I wanted real numbers instead of guesses, so I measured 1,071 live Shopify stores (42,560 products) in October 2026, using only public data. No store is named. Here is what came out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fourpes57tigicgg9y2uv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fourpes57tigicgg9y2uv.png" alt="Distribution of store readiness scores" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The good news: the basics are there
&lt;/h2&gt;

&lt;p&gt;85.8% of the stores score 80 or more out of 100 on the product-data checks (median 89.1). Every product has a title and a vendor. If your store is in that group, you are ahead of the 14.2% that are not, and the rest of this post is about the last mile. The last mile is where the details are patchy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gaps an AI assistant trips over
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. SKUs and barcodes.&lt;/strong&gt; About half of all products (50.3%) have at least one variant with no SKU. On the product pages I checked, only 8.4% of stores publish a GTIN or other barcode in their structured data. Identifiers are how a product gets matched across feeds and shops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Thin descriptions and missing labels.&lt;/strong&gt; 21.7% of products have a description under 150 characters, and 6.9% have no description at all. 64.2% of stores have at least one thin description somewhere in the catalog. 37.9% of products have no product type and 38.2% have no tags. These fields tell an assistant what a product is and who it is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. No Product structured data.&lt;/strong&gt; 79.7% of sampled product pages carry schema.org Product data in JSON-LD, with price and availability in nearly all of them. That leaves 20.3% of stores without it: 16.3% use older microdata only and 4% have no Product markup in the delivered HTML. Shipping details (1.2%) and return policy (1.1%) are almost never there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmc4a73uot5co84hrnp1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmc4a73uot5co84hrnp1p.png" alt="What a product page tells machines" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Crawlers are not the problem.&lt;/strong&gt; Of 1,069 robots.txt files, 0 block OpenAI's search crawler (OAI-SearchBot) on product pages, 0 block PerplexityBot and 1 blocks GPTBot. The door is open. What is missing is complete data behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4tp89wq3260ypi1bqdv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4tp89wq3260ypi1bqdv5.png" alt="Share of products failing each rule" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your 5-step fix list before the sale
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Give every variant a SKU, and add the manufacturer barcode (GTIN, UPC, ISBN) where you have one. Never invent one.&lt;/li&gt;
&lt;li&gt;Write descriptions of at least 150 characters: what it is, who it is for, materials, size, care. Start with your top sellers.&lt;/li&gt;
&lt;li&gt;Fill in product type and tags.&lt;/li&gt;
&lt;li&gt;Check that your product page outputs Product structured data with price and availability in the page HTML (Google's Rich Results Test shows what yours prints).&lt;/li&gt;
&lt;li&gt;Keep AI crawlers allowed in robots.txt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Doing this by hand across hundreds of products is the job Citebeam is built for: it scans the whole catalog from inside your Shopify admin, ranks the gaps by importance and applies the fixes it can after you preview them. &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/check?utm_source=devto&amp;amp;utm_campaign=study" rel="noopener noreferrer"&gt;Start with the free check&lt;/a&gt;&lt;/strong&gt; and fix your best sellers first, while there are still a few weeks left.&lt;/p&gt;

&lt;h2&gt;
  
  
  The half this study cannot see
&lt;/h2&gt;

&lt;p&gt;My data is about your catalog, the part you control. It does not tell you what ChatGPT or Perplexity actually answer, or which competitors and outside sources they name instead of you. If you want that measured, I run a one-off audit on Apify: 16 buyer questions on ChatGPT, Perplexity and Gemini, 96 answers for $4.80, showing your brand against your competitors and the sources they cite (a paid Apify plan is needed for the full size). &lt;strong&gt;&lt;a href="https://apify.com/highbrow_fame/ai-search-visibility-tracker/examples/full-ai-visibility-audit-5-dollars" rel="noopener noreferrer"&gt;Full AI visibility audit&lt;/a&gt;&lt;/strong&gt;. Pair it with the catalog fixes above and you know both what to repair and whether it moved anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I measured (short version)
&lt;/h2&gt;

&lt;p&gt;Stores came from Common Crawl's October 2026 index of myshopify.com addresses; I probed 1,461 and 1,071 qualified (public feed, at least 10 products). Per store: robots.txt, up to 50 products from the public feed and one product page, with at most 3 requests, an honest user agent and robots.txt respected. For the 58.6% of stores with more than 50 products, only the first 50 are represented. Scores use the same rules as Citebeam's free checker, except image alt text, which Shopify's public feed does not expose, so it is not measured here. Two sub-samples (stores on their own domain and stores served on the myshopify.com address) show the same picture (mean score 88.4 and 86.8).&lt;/p&gt;

&lt;p&gt;Limits you should know before quoting this: the sample leans toward smaller and newer merchants, structured data comes from one product page per store without running JavaScript, and the study measures data completeness, not what any assistant recommends. Charts, full method and the anonymous aggregates are on the research page: &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/research/ai-shopping-readiness-2026" rel="noopener noreferrer"&gt;ai-shopping-readiness-2026&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ready to see your own number? &lt;strong&gt;&lt;a href="https://citebeam-three.vercel.app/check?utm_source=devto&amp;amp;utm_campaign=study" rel="noopener noreferrer"&gt;Run the free check&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>ai</category>
      <category>seo</category>
    </item>
    <item>
      <title>Vienna rent prices per sqm by district: October 2026 (live ImmoScout24 data)</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:28:42 +0000</pubDate>
      <link>https://dev.to/projectdev/vienna-rent-prices-per-m2-by-district-october-2026-live-immoscout24-data-3k55</link>
      <guid>https://dev.to/projectdev/vienna-rent-prices-per-m2-by-district-october-2026-live-immoscout24-data-3k55</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actors used to collect this data. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Vienna's median asking rent is €22.72 per m² in October 2026, about €1,590 a month for a typical flat.&lt;/strong&gt; The most expensive district is Innere Stadt (1010) at €26.74 per m², followed by Donaustadt (€26.67); the cheapest is Simmering (1110) at €18.77.&lt;/p&gt;

&lt;p&gt;If you are comparing Vienna districts before a move, negotiating a lease or building a market model, the number you want is rent per square metre by district, and the listing pages do not show it. This report computes it from live ImmoScout24.at rental listings, for all 23 districts, and I will publish a new edition regularly so you can see the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rent per m² by district
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmhzzpl9iqqq0fkzpl5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmhzzpl9iqqq0fkzpl5i.png" alt="Vienna median asking rent per m² by district, October 2026" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Orange: the three most expensive districts. Green: the three cheapest. The grey figure is the change against the previous edition (this is the first edition, so there is none yet, shown as a dash).&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Postcode&lt;/th&gt;
&lt;th&gt;District&lt;/th&gt;
&lt;th&gt;Median €/m²&lt;/th&gt;
&lt;th&gt;Middle half (25%-75%)&lt;/th&gt;
&lt;th&gt;Median rent €/month&lt;/th&gt;
&lt;th&gt;Median size m²&lt;/th&gt;
&lt;th&gt;Rental flats on the portal&lt;/th&gt;
&lt;th&gt;Change in median €/m²&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1010&lt;/td&gt;
&lt;td&gt;Innere Stadt&lt;/td&gt;
&lt;td&gt;26.74&lt;/td&gt;
&lt;td&gt;22.89 - 30.67&lt;/td&gt;
&lt;td&gt;3,448&lt;/td&gt;
&lt;td&gt;147&lt;/td&gt;
&lt;td&gt;211&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1220&lt;/td&gt;
&lt;td&gt;Donaustadt&lt;/td&gt;
&lt;td&gt;26.67&lt;/td&gt;
&lt;td&gt;22.05 - 34.53&lt;/td&gt;
&lt;td&gt;1,532&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;628&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1190&lt;/td&gt;
&lt;td&gt;Döbling&lt;/td&gt;
&lt;td&gt;25.13&lt;/td&gt;
&lt;td&gt;22.74 - 30.89&lt;/td&gt;
&lt;td&gt;2,548&lt;/td&gt;
&lt;td&gt;95.9&lt;/td&gt;
&lt;td&gt;217&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1040&lt;/td&gt;
&lt;td&gt;Wieden&lt;/td&gt;
&lt;td&gt;24.58&lt;/td&gt;
&lt;td&gt;22.77 - 32.59&lt;/td&gt;
&lt;td&gt;3,860&lt;/td&gt;
&lt;td&gt;148.3&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1020&lt;/td&gt;
&lt;td&gt;Leopoldstadt&lt;/td&gt;
&lt;td&gt;23.85&lt;/td&gt;
&lt;td&gt;21.46 - 29.15&lt;/td&gt;
&lt;td&gt;1,834&lt;/td&gt;
&lt;td&gt;75.9&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1030&lt;/td&gt;
&lt;td&gt;Landstraße&lt;/td&gt;
&lt;td&gt;23.08&lt;/td&gt;
&lt;td&gt;20.00 - 24.64&lt;/td&gt;
&lt;td&gt;1,699&lt;/td&gt;
&lt;td&gt;81.8&lt;/td&gt;
&lt;td&gt;476&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1180&lt;/td&gt;
&lt;td&gt;Währing&lt;/td&gt;
&lt;td&gt;22.84&lt;/td&gt;
&lt;td&gt;21.27 - 25.58&lt;/td&gt;
&lt;td&gt;2,850&lt;/td&gt;
&lt;td&gt;131.9&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1140&lt;/td&gt;
&lt;td&gt;Penzing&lt;/td&gt;
&lt;td&gt;22.75&lt;/td&gt;
&lt;td&gt;18.35 - 26.11&lt;/td&gt;
&lt;td&gt;1,490&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1050&lt;/td&gt;
&lt;td&gt;Margareten&lt;/td&gt;
&lt;td&gt;22.54&lt;/td&gt;
&lt;td&gt;19.02 - 25.54&lt;/td&gt;
&lt;td&gt;1,600&lt;/td&gt;
&lt;td&gt;66.4&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;td&gt;Favoriten&lt;/td&gt;
&lt;td&gt;22.34&lt;/td&gt;
&lt;td&gt;20.31 - 23.66&lt;/td&gt;
&lt;td&gt;1,125&lt;/td&gt;
&lt;td&gt;50.8&lt;/td&gt;
&lt;td&gt;395&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1070&lt;/td&gt;
&lt;td&gt;Neubau&lt;/td&gt;
&lt;td&gt;22.31&lt;/td&gt;
&lt;td&gt;20.05 - 24.33&lt;/td&gt;
&lt;td&gt;2,129&lt;/td&gt;
&lt;td&gt;97.6&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1130&lt;/td&gt;
&lt;td&gt;Hietzing&lt;/td&gt;
&lt;td&gt;22.31&lt;/td&gt;
&lt;td&gt;19.48 - 26.52&lt;/td&gt;
&lt;td&gt;1,542&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;116&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1120&lt;/td&gt;
&lt;td&gt;Meidling&lt;/td&gt;
&lt;td&gt;21.52&lt;/td&gt;
&lt;td&gt;18.63 - 23.37&lt;/td&gt;
&lt;td&gt;1,249&lt;/td&gt;
&lt;td&gt;58.8&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1080&lt;/td&gt;
&lt;td&gt;Josefstadt&lt;/td&gt;
&lt;td&gt;21.16&lt;/td&gt;
&lt;td&gt;19.92 - 24.11&lt;/td&gt;
&lt;td&gt;2,309&lt;/td&gt;
&lt;td&gt;102.1&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;td&gt;Brigittenau&lt;/td&gt;
&lt;td&gt;21.15&lt;/td&gt;
&lt;td&gt;17.75 - 27.64&lt;/td&gt;
&lt;td&gt;1,545&lt;/td&gt;
&lt;td&gt;68.8&lt;/td&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1170&lt;/td&gt;
&lt;td&gt;Hernals&lt;/td&gt;
&lt;td&gt;21.09&lt;/td&gt;
&lt;td&gt;18.52 - 23.65&lt;/td&gt;
&lt;td&gt;1,598&lt;/td&gt;
&lt;td&gt;69.1&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1160&lt;/td&gt;
&lt;td&gt;Ottakring&lt;/td&gt;
&lt;td&gt;20.87&lt;/td&gt;
&lt;td&gt;18.49 - 22.98&lt;/td&gt;
&lt;td&gt;1,149&lt;/td&gt;
&lt;td&gt;54.2&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1060&lt;/td&gt;
&lt;td&gt;Mariahilf&lt;/td&gt;
&lt;td&gt;20.25&lt;/td&gt;
&lt;td&gt;17.84 - 25.23&lt;/td&gt;
&lt;td&gt;2,148&lt;/td&gt;
&lt;td&gt;94.5&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1090&lt;/td&gt;
&lt;td&gt;Alsergrund&lt;/td&gt;
&lt;td&gt;20.15&lt;/td&gt;
&lt;td&gt;17.91 - 24.47&lt;/td&gt;
&lt;td&gt;1,972&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1210&lt;/td&gt;
&lt;td&gt;Floridsdorf&lt;/td&gt;
&lt;td&gt;19.84&lt;/td&gt;
&lt;td&gt;18.09 - 22.06&lt;/td&gt;
&lt;td&gt;1,049&lt;/td&gt;
&lt;td&gt;53.1&lt;/td&gt;
&lt;td&gt;340&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1150&lt;/td&gt;
&lt;td&gt;Rudolfsheim-Fünfhaus&lt;/td&gt;
&lt;td&gt;19.63&lt;/td&gt;
&lt;td&gt;18.09 - 25.51&lt;/td&gt;
&lt;td&gt;1,435&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1230&lt;/td&gt;
&lt;td&gt;Liesing&lt;/td&gt;
&lt;td&gt;19.28&lt;/td&gt;
&lt;td&gt;17.81 - 21.05&lt;/td&gt;
&lt;td&gt;1,199&lt;/td&gt;
&lt;td&gt;63.6&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1110&lt;/td&gt;
&lt;td&gt;Simmering&lt;/td&gt;
&lt;td&gt;18.77&lt;/td&gt;
&lt;td&gt;16.99 - 20.40&lt;/td&gt;
&lt;td&gt;1,141&lt;/td&gt;
&lt;td&gt;63.5&lt;/td&gt;
&lt;td&gt;112&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Rent per m² by flat size: under 40 m² cost the most
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fey60t9lcw7y7e5ospwt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fey60t9lcw7y7e5ospwt0.png" alt="Median asking rent per m² by flat size" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;under 40 m²: €28.05 per m² (median rent €900 a month, 52 listings)&lt;/li&gt;
&lt;li&gt;40-60 m²: €22.72 per m² (median rent €1,099 a month, 281 listings)&lt;/li&gt;
&lt;li&gt;60-90 m²: €21.53 per m² (median rent €1,598 a month, 301 listings)&lt;/li&gt;
&lt;li&gt;90 m² and up: €22.80 per m² (median rent €3,148 a month, 381 listings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The under 40 m² band is the most expensive per square metre (€28.05) and the 60-90 m² band the cheapest (€21.53). A small flat is a cheaper flat per month, not per square metre, so compare districts using flats of the same size.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; the ImmoScout24 Austria Scraper on Apify, run on 2026-10-22 with a fixed input: all 23 Vienna postcodes, rent, flats, sorted newest first, the newest 50 listings per district (1015 listings in total after removing duplicates, implausible rows and listings beyond the agency cap). The portal itself listed 3,880 rental flats in Vienna that day (column "Rental flats on the portal").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asking rents, not contracts:&lt;/strong&gt; these are the rents advertised by landlords and agents, as ImmoScout24 shows them (monthly rent including operating costs). Signed contracts, old tenancies and regulated rents are lower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;City median:&lt;/strong&gt; each listing is weighted by how many rental flats its district has on the portal, so small districts do not count as much as large ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No single advertiser decides a district:&lt;/strong&gt; one agency or developer counts for at most 5 listings per district, because a developer who posts a whole new-build block on one day would otherwise set the median of its district (listings used per district: 30 to 50).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregates only:&lt;/strong&gt; no landlord names, streets or listing texts are used or published here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caveat 1:&lt;/strong&gt; peripheral districts with a lot of new-build rental supply can rank higher than their address suggests; the data used here does not separate new-builds from older flats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caveat 2:&lt;/strong&gt; the newest 50 listings are a sample of a district's market, not all of it. Differences of a euro between neighbouring districts are within noise; the gaps of five euros and more are not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to get this data yourself, every day
&lt;/h2&gt;

&lt;p&gt;Everything above was collected by an Apify Actor from a fixed input, plus some arithmetic. You can run the same collection on your own Apify account in a couple of clicks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apify.com/highbrow_fame/immoscout24-at-properties" rel="noopener noreferrer"&gt;ImmoScout24 Austria Scraper&lt;/a&gt;: $1.50 per 1,000 results on the free plan, you pay only for results delivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Collecting the data for this edition costs about $1.73 on Apify's free plan&lt;/strong&gt; (you pay per result delivered, nothing for a search that returns nothing), so a monthly refresh costs about the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily monitor in three steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Actor, paste an input like the one below, and switch on &lt;strong&gt;Only new&lt;/strong&gt; (&lt;code&gt;onlyNew&lt;/code&gt;). The first run returns everything; every later run returns only what no earlier run of the same watch list delivered, and skipped items are not charged.&lt;/li&gt;
&lt;li&gt;Save the input as a task and put it on a schedule (for example the cron expression &lt;code&gt;0 7 * * *&lt;/code&gt;, every day at 07:00 UTC).&lt;/li&gt;
&lt;li&gt;Send the result where you work: e-mail, Slack or Google Sheets, from the task's &lt;em&gt;Integrations&lt;/em&gt; tab.
&lt;/li&gt;
&lt;/ol&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;"locations"&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="s2"&gt;"Wien"&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;"transaction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"propertyType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"newest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxListingsPerSearch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onlyNew"&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;"monitorKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vienna-rent-flats"&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;&lt;strong&gt;Want the numbers for your own street, price range or flat size?&lt;/strong&gt; Open the &lt;a href="https://apify.com/highbrow_fame/immoscout24-at-properties" rel="noopener noreferrer"&gt;ImmoScout24 Austria Scraper&lt;/a&gt; on Apify, set the district and filters, and run it: the input form is prefilled, one click starts a run, and the dataset comes back as CSV, Excel or JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next edition:&lt;/strong&gt; follow me here on dev.to to get the next Vienna rent update, and run the monitor above if you would rather have the new listings in your inbox the same morning they appear.&lt;/p&gt;

&lt;p&gt;More copy-paste examples (Python, n8n workflow, LangChain tool): &lt;a href="https://github.com/projectworks007/apify-data-mcp/tree/main/examples/immoscout24-at-properties" rel="noopener noreferrer"&gt;ImmoScout24 Austria integration examples on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>realestate</category>
      <category>austria</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Historical stock prices and crypto quotes API in 10 lines of Python</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Thu, 22 Oct 2026 04:05:08 +0000</pubDate>
      <link>https://dev.to/projectdev/historical-stock-prices-and-crypto-quotes-api-in-10-lines-of-python-ko1</link>
      <guid>https://dev.to/projectdev/historical-stock-prices-and-crypto-quotes-api-in-10-lines-of-python-ko1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actor used in this tutorial. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You want today's price for a handful of tickers, a year of daily closes for a backtest, and the sector and margins for a screen, without signing up for three different market-data plans. The &lt;strong&gt;Yahoo Finance Scraper&lt;/strong&gt; (&lt;code&gt;highbrow_fame/yahoo-finance-quotes&lt;/code&gt;) on Apify returns all three for stocks on any exchange, ETFs, crypto, currencies and indexes, from a single input, for $1.00 per 1,000 symbols on the free Apify plan. Price history and company data cost nothing extra. Here is the ten-line version, then the options, then a small portfolio report you can run after the close every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10 lines
&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;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;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;highbrow_fame/yahoo-finance-quotes&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;symbols&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;AAPL&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;VOD.L&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;SAP.DE&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;SPY&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;BTC-USD&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;EURUSD=X&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;^GSPC&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;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;q&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="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&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;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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;9&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;q&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;12&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; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;q&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;changePercent&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;0&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&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;%  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;q&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;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exchange&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;Symbols are written exactly as Yahoo Finance writes them: &lt;code&gt;VOD.L&lt;/code&gt; for London, &lt;code&gt;SAP.DE&lt;/code&gt; for Xetra, &lt;code&gt;7203.T&lt;/code&gt; for Tokyo, &lt;code&gt;BTC-USD&lt;/code&gt; for Bitcoin, &lt;code&gt;EURUSD=X&lt;/code&gt; for a currency pair, &lt;code&gt;GC=F&lt;/code&gt; for gold futures and &lt;code&gt;^GSPC&lt;/code&gt; for the S&amp;amp;P 500. Quotes come 50 symbols to a request, so a 300-symbol watchlist is a handful of requests rather than 300. A symbol Yahoo does not know comes back as a free record with &lt;code&gt;error&lt;/code&gt; set and no &lt;code&gt;price&lt;/code&gt;, which is why the loop skips those.&lt;/p&gt;

&lt;p&gt;Each record carries the full quote: &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;change&lt;/code&gt;, &lt;code&gt;changePercent&lt;/code&gt;, &lt;code&gt;previousClose&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;dayLow&lt;/code&gt;, &lt;code&gt;dayHigh&lt;/code&gt;, &lt;code&gt;volume&lt;/code&gt;, &lt;code&gt;bid&lt;/code&gt;, &lt;code&gt;ask&lt;/code&gt;, &lt;code&gt;marketTime&lt;/code&gt; and &lt;code&gt;marketState&lt;/code&gt;, the ranges &lt;code&gt;fiftyTwoWeekLow&lt;/code&gt;, &lt;code&gt;fiftyTwoWeekHigh&lt;/code&gt;, &lt;code&gt;fiftyDayAverage&lt;/code&gt; and &lt;code&gt;twoHundredDayAverage&lt;/code&gt;, valuation fields &lt;code&gt;marketCap&lt;/code&gt;, &lt;code&gt;trailingPE&lt;/code&gt;, &lt;code&gt;forwardPE&lt;/code&gt;, &lt;code&gt;epsTrailingTwelveMonths&lt;/code&gt; and &lt;code&gt;priceToBook&lt;/code&gt;, and &lt;code&gt;dividendRate&lt;/code&gt;, &lt;code&gt;dividendYield&lt;/code&gt;, &lt;code&gt;dividendDate&lt;/code&gt;, &lt;code&gt;earningsDate&lt;/code&gt; and &lt;code&gt;analystRating&lt;/code&gt;. &lt;code&gt;exchangeDelayMinutes&lt;/code&gt; tells you whether the quote is real-time or delayed for that exchange.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: price history
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;includeHistory: true&lt;/code&gt; and pick a range and interval. &lt;code&gt;historyRange&lt;/code&gt; takes &lt;code&gt;1d&lt;/code&gt;, &lt;code&gt;5d&lt;/code&gt;, &lt;code&gt;1mo&lt;/code&gt;, &lt;code&gt;3mo&lt;/code&gt;, &lt;code&gt;6mo&lt;/code&gt;, &lt;code&gt;1y&lt;/code&gt;, &lt;code&gt;2y&lt;/code&gt;, &lt;code&gt;5y&lt;/code&gt;, &lt;code&gt;10y&lt;/code&gt;, &lt;code&gt;ytd&lt;/code&gt; or &lt;code&gt;max&lt;/code&gt;; &lt;code&gt;historyInterval&lt;/code&gt; takes &lt;code&gt;1m&lt;/code&gt;, &lt;code&gt;5m&lt;/code&gt;, &lt;code&gt;15m&lt;/code&gt;, &lt;code&gt;30m&lt;/code&gt;, &lt;code&gt;60m&lt;/code&gt;, &lt;code&gt;1d&lt;/code&gt;, &lt;code&gt;1wk&lt;/code&gt; or &lt;code&gt;1mo&lt;/code&gt;, with minute intervals available for recent days only. Exact dates go in &lt;code&gt;historyFrom&lt;/code&gt; and &lt;code&gt;historyTo&lt;/code&gt; as &lt;code&gt;YYYY-MM-DD&lt;/code&gt; and override the range. The history lands in &lt;code&gt;history.points&lt;/code&gt;, a list of &lt;code&gt;{date, open, high, low, close, adjClose, volume}&lt;/code&gt;, plus &lt;code&gt;history.dividends&lt;/code&gt; and &lt;code&gt;history.splits&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="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;highbrow_fame/yahoo-finance-quotes&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;symbols&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;AAPL&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;BTC-USD&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;includeHistory&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;historyRange&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;1y&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;historyInterval&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;1d&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;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&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;history&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;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;points&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;points&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;first&lt;/span&gt;&lt;span class="p"&gt;,&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;points&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;points&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;total_return&lt;/span&gt; &lt;span class="o"&gt;=&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjClose&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;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;adjClose&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;1&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;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; days, &lt;/span&gt;&lt;span class="si"&gt;{&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;date&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; to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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="n"&gt;total_return&lt;/span&gt;&lt;span class="si"&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="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% total return, &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;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dividends&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; dividends&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;If you work in pandas, &lt;code&gt;pd.DataFrame(points).set_index("date")&lt;/code&gt; gives you a frame per symbol. Dividends and splits are already applied in &lt;code&gt;adjClose&lt;/code&gt;, so return maths uses that column; &lt;code&gt;close&lt;/code&gt; is the raw print.&lt;/p&gt;

&lt;p&gt;For a growing local archive, store the last date you have per symbol and pass it as &lt;code&gt;historyFrom&lt;/code&gt; on the next run, so each day fetches only the missing points. Intraday work uses the minute intervals with a short range such as &lt;code&gt;5d&lt;/code&gt;; Yahoo keeps minute bars for recent days only, so archive them as you go if you need them later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: company data
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;includeFundamentals: true&lt;/code&gt; adds three objects per symbol: &lt;code&gt;profile&lt;/code&gt; (sector, industry, website, employees, business summary), &lt;code&gt;financials&lt;/code&gt; (revenue, growth, margins, EBITDA, cash, debt, free cash flow, ROE, analyst targets and recommendation) and &lt;code&gt;keyStatistics&lt;/code&gt; (enterprise value, beta, PEG, short interest). Crypto, currencies and indexes are not companies, so they say so in &lt;code&gt;fundamentalsUnavailable&lt;/code&gt; instead of returning empty objects.&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="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;highbrow_fame/yahoo-finance-quotes&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;symbols&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;AAPL&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;SAP.DE&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;BTC-USD&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;includeFundamentals&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="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;q&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;fundamentalsUnavailable&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;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fundamentalsUnavailable&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;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&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;profile&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;q&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;financials&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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="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;sector&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="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;industry&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;| margins:&lt;/span&gt;&lt;span class="sh"&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;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;profitMargins&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;| target:&lt;/span&gt;&lt;span class="sh"&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;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;targetMeanPrice&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;| rec:&lt;/span&gt;&lt;span class="sh"&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;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;recommendationKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner keys of &lt;code&gt;financials&lt;/code&gt; and &lt;code&gt;keyStatistics&lt;/code&gt; follow Yahoo's own names, so print one record once with &lt;code&gt;json.dumps(q, indent=2)&lt;/code&gt; to see exactly which fields your symbols carry before you build a screen on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small portfolio report
&lt;/h2&gt;

&lt;p&gt;Put the holdings in a dict, fetch one run, and print value, day change and weight. This is the script to schedule after the close:&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="n"&gt;HOLDINGS&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;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAP.DE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SPY&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC-USD&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VOD.L&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;highbrow_fame/yahoo-finance-quotes&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;symbols&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;HOLDINGS&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;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;q&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="bp"&gt;None&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;qty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HOLDINGS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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="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;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&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;q&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;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;qty&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;q&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;q&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;day_change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&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;change&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;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;day_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&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;changePercent&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;total&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;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;value&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="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;symbol&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;9&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ccy&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;5&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&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;price&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;12&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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;14&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day&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;12&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&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="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="nf"&gt;sorted&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="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;r&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;value&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="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;symbol&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;9&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;5&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;qty&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;8&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;12&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="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;value&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;14&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="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;day_change&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;12&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="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;value&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;total&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.1&lt;/span&gt;&lt;span class="o"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total (mixed currencies): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&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;Values are in each symbol's own currency, so VOD.L is in GBp and SAP.DE in EUR; add the &lt;code&gt;EURUSD=X&lt;/code&gt; and &lt;code&gt;GBPUSD=X&lt;/code&gt; symbols to the same run and multiply if you want one base currency. Wrap the script in a cron entry for 22:30 UTC on weekdays, or save the input as a task in the Apify Console and schedule it there, then read the newest dataset from your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same call with curl
&lt;/h2&gt;

&lt;p&gt;No Python at hand? The synchronous endpoint runs the Actor and returns the dataset in one request:&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;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/highbrow_fame~yahoo-finance-quotes/run-sync-get-dataset-items?token=&amp;lt;YOUR_APIFY_TOKEN&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"symbols": ["AAPL", "BTC-USD"], "includeHistory": true, "historyRange": "1mo"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;&amp;amp;format=csv&lt;/code&gt; to the URL for a spreadsheet-ready response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost per 1,000 symbols
&lt;/h2&gt;

&lt;p&gt;The Actor charges $0.001 per symbol delivered on the free Apify plan, which is $1.00 per 1,000 symbols, falling to $0.70 per 1,000 on Gold and higher plans. The price is the same with or without history and company data, and unknown symbols are not charged. The seven-symbol quote run at the top costs $0.007; a 500-symbol watchlist refreshed every trading day costs $0.50 a day.&lt;/p&gt;

&lt;p&gt;Try it with the prefilled input: &lt;a href="https://apify.com/highbrow_fame/yahoo-finance-quotes" rel="noopener noreferrer"&gt;https://apify.com/highbrow_fame/yahoo-finance-quotes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More copy-paste examples (Python, n8n workflow, LangChain tool): &lt;a href="https://github.com/projectworks007/apify-data-mcp/tree/main/examples/yahoo-finance-quotes" rel="noopener noreferrer"&gt;Yahoo Finance integration examples on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>finance</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Austria apartments for rent: a daily new-listings feed from ImmoScout24 (Python)</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Wed, 21 Oct 2026 08:03:21 +0000</pubDate>
      <link>https://dev.to/projectdev/austria-apartments-for-rent-a-daily-new-listings-feed-from-immoscout24-python-2gpc</link>
      <guid>https://dev.to/projectdev/austria-apartments-for-rent-a-daily-new-listings-feed-from-immoscout24-python-2gpc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actor used in this tutorial. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finding a flat to rent in Vienna rewards whoever sees a listing first, and the comparisons you actually want, like rent per square metre by district, are not on the listing page. This tutorial builds a daily feed of new ImmoScout24 Austria rentals with the &lt;strong&gt;ImmoScout24 Austria Scraper&lt;/strong&gt; (&lt;code&gt;highbrow_fame/immoscout24-at-properties&lt;/code&gt;) on Apify. You set the filters once, a Python script runs every morning, keeps only listings it has not shown you before, computes rent per m², and writes a CSV you can open or push to Google Sheets. The Actor charges $1.50 per 1,000 properties on the free plan, so a 100-listing daily check costs about 15 cents a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a listing record contains
&lt;/h2&gt;

&lt;p&gt;Every property comes back with &lt;code&gt;price&lt;/code&gt; (the monthly rent for rentals), &lt;code&gt;priceText&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;, &lt;code&gt;pricePerSqm&lt;/code&gt;, &lt;code&gt;areaSqm&lt;/code&gt;, &lt;code&gt;rooms&lt;/code&gt;, &lt;code&gt;postcode&lt;/code&gt;, &lt;code&gt;town&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;transaction&lt;/code&gt;, &lt;code&gt;propertyType&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;features&lt;/code&gt; (for example &lt;code&gt;BALCONY&lt;/code&gt;, &lt;code&gt;GARDEN&lt;/code&gt;, &lt;code&gt;FREE_OF_COMMISSION&lt;/code&gt;), &lt;code&gt;sellerType&lt;/code&gt;, &lt;code&gt;agency&lt;/code&gt;, &lt;code&gt;listedAt&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;listingId&lt;/code&gt;. Reduced listings carry &lt;code&gt;oldPrice&lt;/code&gt; and &lt;code&gt;priceCutPercent&lt;/code&gt;. Private owners are anonymous: no name, no street, no map point, only postcode and town. With &lt;code&gt;includeDetails: true&lt;/code&gt; you also get &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;floor&lt;/code&gt;, &lt;code&gt;yearBuilt&lt;/code&gt;, &lt;code&gt;condition&lt;/code&gt;, &lt;code&gt;heating&lt;/code&gt;, the energy certificate (&lt;code&gt;heatingDemand&lt;/code&gt;, &lt;code&gt;heatingDemandClass&lt;/code&gt;, &lt;code&gt;energyEfficiencyClass&lt;/code&gt;), &lt;code&gt;availableFrom&lt;/code&gt;, &lt;code&gt;hasCommission&lt;/code&gt;, &lt;code&gt;oneTimeCosts&lt;/code&gt; and &lt;code&gt;monthlyCosts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: set the filters
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;locations&lt;/code&gt; accepts Austrian cities, towns, Vienna districts, postcodes and states, so "Wien" covers the whole city and "1070" or "Neubau" narrows it to one district. The filters are plain fields:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;SEARCH&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;locations&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;Wien&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;transaction&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;rent&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;propertyType&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;flat&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;minArea&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minRooms&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;Balkon&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;sortBy&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;newest&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;maxListingsPerSearch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;includeDetails&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="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;highbrow_fame/immoscout24-at-properties&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;SEARCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;listings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;item&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;error&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;item&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="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;listings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flats&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;If you already have a search set up on immobilienscout24.at with every filter you want, paste its results URL into &lt;code&gt;searchUrls&lt;/code&gt; instead and leave the field filters out. &lt;code&gt;sortBy: "newest"&lt;/code&gt; is the right order for a feed: the newest 100 of a large search are the ones that changed since yesterday. &lt;code&gt;maxPrice&lt;/code&gt; is the monthly rent in EUR.&lt;/p&gt;

&lt;p&gt;The same input works outside Vienna. &lt;code&gt;["Graz", "Linz"]&lt;/code&gt; searches two cities in one run, &lt;code&gt;["Tirol"]&lt;/code&gt; a whole state, and because the place search reads "Salzburg" as the state, type the postcode &lt;code&gt;5020&lt;/code&gt; for the city itself. Each place is one search, so &lt;code&gt;maxListingsPerSearch&lt;/code&gt; applies per place. When you reach the shortlist stage, re-run the chosen &lt;code&gt;searchUrls&lt;/code&gt; with &lt;code&gt;includeDetails: true&lt;/code&gt; to get &lt;code&gt;monthlyCosts&lt;/code&gt;, &lt;code&gt;oneTimeCosts&lt;/code&gt;, &lt;code&gt;hasCommission&lt;/code&gt;, &lt;code&gt;availableFrom&lt;/code&gt; and the energy certificate, which together tell you what the flat really costs to move into and to heat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: feed mode in 15 lines
&lt;/h2&gt;

&lt;p&gt;The Actor has no built-in only-new switch, so the feed lives in your script: remember every &lt;code&gt;listingId&lt;/code&gt; you have delivered in a key-value store in your Apify account and skip the ones you have seen. The first run seeds the memory and reports everything.&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="n"&gt;store&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;key_value_stores&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_or_create&lt;/span&gt;&lt;span class="p"&gt;(&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;immoscout-wien-feed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;kv&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;key_value_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&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;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seen-ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;seen&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;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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;record&lt;/span&gt; &lt;span class="k"&gt;else&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;new_listings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;listingId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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;seen&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;listingId&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;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listings&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="nf"&gt;set_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seen-ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;seen&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;new_listings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new since last run&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 the script once a day from cron, a GitHub Actions schedule, or any other timer, and &lt;code&gt;new_listings&lt;/code&gt; is your feed. If you ever change the filters, give the store a new name so the feed starts clean. Keep &lt;code&gt;maxListingsPerSearch&lt;/code&gt; a little larger than a day's worth of new listings for your filters; if &lt;code&gt;len(listings)&lt;/code&gt; keeps hitting the limit, raise it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: rent per m² and the district comparison
&lt;/h2&gt;

&lt;p&gt;The Actor already fills &lt;code&gt;pricePerSqm&lt;/code&gt; when the site shows it. Compute it yourself from &lt;code&gt;price&lt;/code&gt; and &lt;code&gt;areaSqm&lt;/code&gt; as a fallback, then compare each new flat with the median of its postcode across everything you fetched today:&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;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="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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rent_per_sqm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&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;l&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;pricePerSqm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pricePerSqm&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&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;areaSqm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&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;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;areaSqm&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="n"&gt;by_postcode&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;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listings&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="nf"&gt;rent_per_sqm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&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;by_postcode&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postcode&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;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;district_median&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pc&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;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;pc&lt;/span&gt;&lt;span class="p"&gt;,&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;by_postcode&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;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_listings&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="nf"&gt;rent_per_sqm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rentPerSqm&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="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vsDistrictMedianPct&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="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;round&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;district_median&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postcode&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;1&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="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="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postcode&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="n"&gt;district_median&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;pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vsDistrictMedianPct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;vs&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;pct&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% vs district&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;pct&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="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no district median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;cut&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; (cut &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priceCutPercent&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;% from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oldPrice&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&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;oldPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&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;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postcode&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;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;town&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;l&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; EUR &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;l&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;areaSqm&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="s"&gt;?&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;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; m2 &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="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; EUR/m2 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;cut&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;   &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A flat at &lt;code&gt;-18%&lt;/code&gt; against its postcode median deserves a look today, not next week. A &lt;code&gt;priceCutPercent&lt;/code&gt; on a listing you saw last week is the other signal worth acting on, so you may want to keep reduced listings in the feed even when the ID is already known.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: CSV and Google Sheets export
&lt;/h2&gt;

&lt;p&gt;Write the enriched feed to a CSV that opens straight in Excel, Numbers or Sheets. The &lt;code&gt;features&lt;/code&gt; list is joined into one cell:&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;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="n"&gt;FIELDS&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;listedAt&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;postcode&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;town&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;areaSqm&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;rooms&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;rentPerSqm&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;vsDistrictMedianPct&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;oldPrice&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;priceCutPercent&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;sellerType&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;agency&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;features&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;path&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;wien-rent-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&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="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;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="n"&gt;FIELDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extrasaction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_listings&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;writerow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;features&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="n"&gt;l&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;features&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;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;wrote&lt;/span&gt;&lt;span class="sh"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Google Sheets you have two routes. The no-code one: in the Apify Console, save the input as a task and add the Google Sheets integration under the task's Integrations tab, which appends every run's dataset to a sheet. The code one: upload the CSV above with &lt;code&gt;gspread&lt;/code&gt; or the Sheets API from the same cron job. The raw dataset is also available as CSV from the API, if you want it without any Python: &lt;code&gt;https://api.apify.com/v2/datasets/&amp;lt;datasetId&amp;gt;/items?format=csv&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running costs
&lt;/h2&gt;

&lt;p&gt;The Actor charges $0.0015 per property delivered on the free Apify plan, which is $1.50 per 1,000 properties, and less on paid plans down to $1.05 per 1,000. Opening listings for their details costs nothing extra, only time. A 100-listing daily run is $0.15 a day, or about $4.50 a month, and a place ImmoScout24 does not know comes back as a free record rather than a charged one.&lt;/p&gt;

&lt;p&gt;Try it with the prefilled input: &lt;a href="https://apify.com/highbrow_fame/immoscout24-at-properties" rel="noopener noreferrer"&gt;https://apify.com/highbrow_fame/immoscout24-at-properties&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More copy-paste examples (Python, n8n workflow, LangChain tool): &lt;a href="https://github.com/projectworks007/apify-data-mcp/tree/main/examples/immoscout24-at-properties" rel="noopener noreferrer"&gt;ImmoScout24 Austria integration examples on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>realestate</category>
      <category>automation</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>OfferUp price tracker: get new listings for any search as a webhook alert</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Tue, 20 Oct 2026 09:01:38 +0000</pubDate>
      <link>https://dev.to/projectdev/offerup-price-tracker-get-new-listings-for-any-search-as-a-webhook-alert-3l97</link>
      <guid>https://dev.to/projectdev/offerup-price-tracker-get-new-listings-for-any-search-as-a-webhook-alert-3l97</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actor used in this tutorial. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Refreshing an OfferUp search every morning to catch a fairly priced road bike or a used camera body is a job for a script, not a person. This tutorial builds that script around the &lt;strong&gt;OfferUp Scraper&lt;/strong&gt; (&lt;code&gt;highbrow_fame/offerup-listings&lt;/code&gt;) on Apify. It has a feed mode that returns only listings you have not seen before, it can POST a summary to a Slack or Discord webhook on its own, and it charges $1.50 per 1,000 listings delivered, so a daily check of one search costs cents rather than dollars a month. You will end up with a scheduled daily run, a webhook alert, and a short Python filter that only pings you for price drops.&lt;/p&gt;

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

&lt;p&gt;One record per listing: &lt;code&gt;listingId&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;originalPrice&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;, &lt;code&gt;isFirmPrice&lt;/code&gt;, &lt;code&gt;condition&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;postedAt&lt;/code&gt;, &lt;code&gt;localPickup&lt;/code&gt;, &lt;code&gt;shippingAvailable&lt;/code&gt;, &lt;code&gt;shippingPrice&lt;/code&gt;, &lt;code&gt;sellerType&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt; and &lt;code&gt;imageCount&lt;/code&gt;. For vehicles you also get &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;trim&lt;/code&gt;, &lt;code&gt;mileage&lt;/code&gt; and &lt;code&gt;titleStatus&lt;/code&gt; as separate fields. Phone numbers and private sellers' names are not delivered, and contact details inside descriptions are stripped. In feed mode each new listing also carries &lt;code&gt;firstSeenAt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: define the search
&lt;/h2&gt;

&lt;p&gt;You can paste a search URL from offerup.com into &lt;code&gt;searchUrls&lt;/code&gt;, or describe the search with fields. The field version is easier to version-control:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;SEARCH&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;keywords&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;road bike&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;locations&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;Austin, TX&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;radiusMiles&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;20&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;category&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;sports-and-outdoors&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;minPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conditions&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;used&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;open-box&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;sortBy&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;newest&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;maxListingsPerSearch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDetails&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="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;highbrow_fame/offerup-listings&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;SEARCH&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&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;error&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="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;item&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;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;condition&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;9&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;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&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;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&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;item&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="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;        &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;locations&lt;/code&gt; takes US ZIP codes or "City, ST". &lt;code&gt;radiusMiles&lt;/code&gt; is a string from the set 5, 10, 20, 30 or 50. &lt;code&gt;sortBy: "newest"&lt;/code&gt; matters for a daily run: OfferUp's default relevance order reshuffles between runs, while newest-first is stable, so the same input returns a predictable window. &lt;code&gt;includeDetails&lt;/code&gt; adds description, condition, post date and firm-price flag in one extra request per 50 listings at no extra charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: switch on feed mode and the webhook
&lt;/h2&gt;

&lt;p&gt;Two fields turn a search into a feed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onlyNewListings: true&lt;/code&gt; makes the Actor remember every listing it delivered for this exact input, in a key-value store in your own Apify account named &lt;code&gt;feed-&amp;lt;actor&amp;gt;-&amp;lt;hash&amp;gt;&lt;/code&gt;. The first run returns everything. Every later run with the same input returns only listings not seen before, and seen items are neither saved nor charged. Memory lasts 60 days per item.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alertWebhookUrl&lt;/code&gt; makes the Actor POST one JSON message when new items appear: &lt;code&gt;{actor, newCount, sampleItems, datasetUrl, runId}&lt;/code&gt; with up to five sample items, plus a &lt;code&gt;text&lt;/code&gt; field so a Slack or Discord incoming webhook renders it directly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FEED&lt;/span&gt; &lt;span class="o"&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;SEARCH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;onlyNewListings&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alertWebhookUrl&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://hooks.slack.com/services/T000/B000/XXXX&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the incoming webhook in Slack (Apps → Incoming Webhooks) or Discord (channel settings → Integrations → Webhooks) and paste its URL. Treat the URL as a secret: it sits in the run input, and the sample items are sent to it. Change any filter and the hash changes, so a new feed starts from scratch, which is what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: schedule it daily on Apify
&lt;/h2&gt;

&lt;p&gt;The cleanest place to run the feed is Apify itself, so nothing on your laptop has to stay awake. In the Apify Console open the Actor, fill the input above, save it as a task, and add a schedule from the task's Schedules tab. Or create the schedule from Python once:&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="n"&gt;actor_id&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;highbrow_fame/offerup-listings&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="nb"&gt;id&lt;/span&gt;

&lt;span class="n"&gt;schedule&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;schedules&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&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;offerup-road-bike-austin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cron_expression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0 13 * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/Chicago&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_exclusive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actions&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;type&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;RUN_ACTOR&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;actorId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;actor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runInput&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;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FEED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contentType&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;application/json; charset=utf-8&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="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;schedule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;next run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_run_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on, every day at 13:00 Central the Actor searches, drops everything it has seen, and posts to your channel only when there is something new. Schedules themselves carry no charge; you pay only for the runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: price-drop filtering in a few lines of Python
&lt;/h2&gt;

&lt;p&gt;The webhook tells you about every new listing in range. Sometimes you only want the ones that were marked down, or that sit well under the market. OfferUp exposes the earlier asking price as &lt;code&gt;originalPrice&lt;/code&gt;, so a price drop is simply &lt;code&gt;price &amp;lt; originalPrice&lt;/code&gt;. Run this from cron in place of the Apify schedule; it calls the same feed input, so deduplication still happens in the Actor:&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;DISCORD_WEBHOOK&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://discord.com/api/webhooks/0000/XXXX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;CEILING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;  &lt;span class="c1"&gt;# alert on anything at or under this price even without a cut
&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;highbrow_fame/offerup-listings&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;FEED&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="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&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;error&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;item&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="bp"&gt;None&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;dropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&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="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;item&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;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;item&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;item&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;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;CEILING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tag&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;was $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dropped&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;under ceiling&lt;/span&gt;&lt;span class="sh"&gt;"&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;append&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;item&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;tag&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;item&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="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&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;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&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="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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="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="n"&gt;hits&lt;/span&gt;&lt;span class="p"&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DISCORD_WEBHOOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;content&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;New OfferUp price alerts:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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;hits&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The input is the unchanged &lt;code&gt;FEED&lt;/code&gt;, so the Actor keeps the same feed memory and still posts its full new-listings summary to Slack, while this script sends only the price drops to Discord. If you want a single channel, drop &lt;code&gt;alertWebhookUrl&lt;/code&gt; from the input before the first run and let the script do all the posting. Swap the Discord &lt;code&gt;content&lt;/code&gt; payload for &lt;code&gt;{"text": ...}&lt;/code&gt; if the target is Slack. Extend the filter however you like: &lt;code&gt;isFirmPrice&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt; on listings open to offers, &lt;code&gt;postedAt&lt;/code&gt; lets you skip anything older than a day, and for cars &lt;code&gt;mileage&lt;/code&gt; and &lt;code&gt;year&lt;/code&gt; are plain integers you can compare.&lt;/p&gt;

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

&lt;p&gt;The Actor charges $0.0015 per listing delivered, which is $1.50 per 1,000 listings, with listing details included at no extra charge. Feed mode only bills the new listings, so after the first run a daily check of a 60-listing search typically delivers a handful of records a day and costs well under a dollar a month. Links that are not OfferUp searches and places OfferUp cannot find come back as free records.&lt;/p&gt;

&lt;p&gt;Try it with the prefilled input: &lt;a href="https://apify.com/highbrow_fame/offerup-listings" rel="noopener noreferrer"&gt;https://apify.com/highbrow_fame/offerup-listings&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>webscraping</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Generative engine optimization (GEO): check if ChatGPT, Perplexity and Gemini cite your brand</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Mon, 19 Oct 2026 09:42:01 +0000</pubDate>
      <link>https://dev.to/projectdev/generative-engine-optimization-geo-check-if-chatgpt-perplexity-and-gemini-cite-your-brand-15eh</link>
      <guid>https://dev.to/projectdev/generative-engine-optimization-geo-check-if-chatgpt-perplexity-and-gemini-cite-your-brand-15eh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built the Apify Actor used in this tutorial. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a buyer asks ChatGPT or Perplexity "which CRM should a 10-person sales team use" and the answer names three other companies, you have a visibility problem that no Google ranking report will show you. This tutorial fixes the measurement half of that problem with the &lt;strong&gt;AI Search Visibility Tracker&lt;/strong&gt; (&lt;code&gt;highbrow_fame/ai-search-visibility-tracker&lt;/code&gt;) on Apify. In about ten minutes you will know, for your own questions, whether the AI engines cite your domain, what share of the citations you hold against named competitors, and the exact URLs the models read. The first check is a free sample run, so you can see real output before paying a cent for results.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GEO means in plain terms
&lt;/h2&gt;

&lt;p&gt;Generative engine optimization (GEO) is the practice of getting your brand into the answers that AI assistants generate. Classic SEO targets a ranked list of links. GEO targets a paragraph of prose with a short list of sources behind it. The model writes that paragraph from the pages it retrieves, so the lever is the same as it always was: be present on the pages the model reads. The difference is that you cannot see those pages in a rank tracker. You need the source list for each answer, and you need it repeatedly, because AI answers change from run to run.&lt;/p&gt;

&lt;p&gt;That is what the Actor returns: one record per question, per engine, per sample, with the answer text, every cited URL, and a match against the domains you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: the free sample run
&lt;/h2&gt;

&lt;p&gt;Install the client and start a sample run. &lt;code&gt;sampleRun: true&lt;/code&gt; runs your first three queries three times each on the included Perplexity engine. The result fee is zero for a sample run; only the platform run-start fee of $0.0004 at the default memory applies.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;QUERIES&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;CRM for a 10-person sales team recommendations&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;simple CRM with a free plan for startups&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;CRM that integrates with Slack for small agencies&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;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;sampleRun&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QUERIES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brandDomains&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;lumenly.example&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;competitorDomains&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;brightdesk.example&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;pipeflow.example&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;engines&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;perplexity-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;language&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;en&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;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;highbrow_fame/ai-search-visibility-tracker&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;records&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="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records in dataset&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;lumenly.example&lt;/code&gt; and the competitor domains for real bare hostnames (no &lt;code&gt;https://&lt;/code&gt;, no &lt;code&gt;www.&lt;/code&gt;). If you pass only &lt;code&gt;brand&lt;/code&gt;, the Actor writes a starter set of questions for that domain, but your own buyer questions always beat generated ones. Keep the sample on the default engine: Perplexity needs no API key from you and answers in 24 languages, so &lt;code&gt;"language": "de"&lt;/code&gt; gives you German sources for the German market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: read brandCited, citation share and the source URLs
&lt;/h2&gt;

&lt;p&gt;Every engine call writes a record with &lt;code&gt;"type": "citation"&lt;/code&gt;. The three fields that matter are the brand match, the &lt;code&gt;citationShare&lt;/code&gt; map, and the &lt;code&gt;citations&lt;/code&gt; list with the URLs the model used. The brand verdict is exposed as &lt;code&gt;brandCited&lt;/code&gt;, with the per-domain detail in &lt;code&gt;brandMatches&lt;/code&gt;, so the helper below reads whichever is present.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;brand_cited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brandCited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brandCited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&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;cited&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;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rec&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;brandMatches&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;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;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&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;rec&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;type&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;citation&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="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;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engine&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;rec&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="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;  brand cited:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;brand_cited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&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;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;share&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rec&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;citationShare&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;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="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&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;share&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; hits  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sharePct&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;% share&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rec&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;citations&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;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;  source:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the sample runs three samples per query, you also get one &lt;code&gt;stability&lt;/code&gt; record per query. It tells you how many of the three samples cited each domain. A domain at &lt;code&gt;1.0&lt;/code&gt; is the model's settled answer. A domain at &lt;code&gt;0.33&lt;/code&gt; showed up once and may be noise. Filter on it before you draw conclusions:&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&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;rec&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;type&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;stability&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;stable&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="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;stability&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;d&lt;/span&gt;&lt;span class="p"&gt;,&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;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stability&lt;/span&gt;&lt;span class="sh"&gt;"&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;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;stability&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;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.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;rec&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;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;stable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;summary&lt;/code&gt; record at the end of the dataset closes the run and is never billed, same as &lt;code&gt;stability&lt;/code&gt;, &lt;code&gt;delta&lt;/code&gt; and error records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: track it weekly with the previous-run delta
&lt;/h2&gt;

&lt;p&gt;A snapshot is interesting once. The weekly change is what goes into a report. Pass the dataset ID of last week's run as &lt;code&gt;previousRunDatasetId&lt;/code&gt; and the Actor writes a &lt;code&gt;delta&lt;/code&gt; record with the domains gained and lost per query. The script below keeps the last dataset ID in a key-value store in your Apify account, so it works unchanged from any weekly cron job or CI schedule.&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="n"&gt;store&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;key_value_stores&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_or_create&lt;/span&gt;&lt;span class="p"&gt;(&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;geo-tracker-state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;kv&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;key_value_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;weekly_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;queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QUERIES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brandDomains&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;lumenly.example&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;competitorDomains&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;brightdesk.example&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;pipeflow.example&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;engines&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;perplexity-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;samplesPerQuery&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;previous&lt;/span&gt; &lt;span class="o"&gt;=&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;get_record&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-dataset-id&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;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;weekly_input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;previousRunDatasetId&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;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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;highbrow_fame/ai-search-visibility-tracker&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;weekly_input&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="nf"&gt;set_record&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-dataset-id&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rec&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;type&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;delta&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gained&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;GAINED&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="n"&gt;domains&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&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="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="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;LOST  &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="n"&gt;domains&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;unchanged queries:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unchangedQueryCount&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;A line such as &lt;code&gt;LOST  "simple CRM with a free plan for startups" ['lumenly.example']&lt;/code&gt; is the sentence that gets read in a Monday meeting. Post it to Slack from the same script if you like.&lt;/p&gt;

&lt;p&gt;Mind the included allowance on the default engine: a free Apify plan covers 12 engine calls per run, a paid plan 200. Queries beyond that come back as &lt;code&gt;not_measured&lt;/code&gt; records, unbilled, with a note on how to lift the limit. Four queries at three samples fits the free allowance exactly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: what to do when competitors are cited instead
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;competitorMatches&lt;/code&gt; map tells you which rival domains were cited for each question. The instinct is to rewrite your own landing page. Resist it for a moment and look at the &lt;code&gt;citations&lt;/code&gt; list instead. Those URLs are the pages the model retrieved: a review site, a comparison article, a community thread, a vendor blog. If a competitor appears there and you do not, that list is your work list.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review and directory pages&lt;/strong&gt; cited across several queries: get listed and get reviews there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison articles&lt;/strong&gt; on third-party sites: pitch an inclusion, or publish your own honest comparison on your domain so it can be retrieved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community threads&lt;/strong&gt;: answer the question there with real substance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own pages&lt;/strong&gt; that never appear: check that the page answers the literal question in its first paragraph, with the product name and the use case spelled out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then re-run next week and read the delta. Nobody can force a citation, and this Actor measures rather than promises, but the source list turns a vague "be more visible to AI" into a short list of pages to earn.&lt;/p&gt;

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

&lt;p&gt;A full run charges $0.05 per citation record, which is $50 per 1,000 citation records. One record is one engine answering one question once, so ten questions on one engine at one sample cost $0.50, and the same at three samples cost $1.50. Stability, delta, summary and error records are free. The sample run has no result fee at all.&lt;/p&gt;

&lt;p&gt;Try it with the prefilled input: &lt;a href="https://apify.com/highbrow_fame/ai-search-visibility-tracker" rel="noopener noreferrer"&gt;https://apify.com/highbrow_fame/ai-search-visibility-tracker&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>marketing</category>
      <category>python</category>
    </item>
    <item>
      <title>Black Friday 2026 Shopify checklist: can ChatGPT read your products? (12 points + a free one-click tool)</title>
      <dc:creator>ByteHarvester</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:08:58 +0000</pubDate>
      <link>https://dev.to/projectdev/black-friday-2026-shopify-checklist-can-chatgpt-read-your-products-12-points-a-free-one-click-78f</link>
      <guid>https://dev.to/projectdev/black-friday-2026-shopify-checklist-can-chatgpt-read-your-products-12-points-a-free-one-click-78f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built Citebeam, the Shopify app mentioned in this post. This post was written with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Black Friday is on November 27, 2026 and Cyber Monday on November 30. Merchants are preparing right now, and more shoppers are asking an AI assistant what to buy instead of typing keywords into a search box. Shopify says its Catalog shares merchants' product data with AI shopping channels such as ChatGPT, Microsoft Copilot and Google Gemini. What those channels can show depends on what is in your product data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free check first:&lt;/strong&gt; see in seconds how ready your Shopify store is for ChatGPT shopping → &lt;a href="https://citebeam-three.vercel.app/check" rel="noopener noreferrer"&gt;Citebeam free readiness checker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/ai-visibility-vs-competitors" rel="noopener noreferrer"&gt;Citebeam&lt;/a&gt; checks your Shopify catalog in one click, shows what AI shopping channels can and cannot read, and fixes the common gaps for you. The catalog check is free.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Free catalog check before the sale: search "Citebeam" on the Shopify App Store, or go straight to &lt;a href="https://apps.shopify.com/ai-visibility-vs-competitors" rel="noopener noreferrer"&gt;apps.shopify.com/ai-visibility-vs-competitors&lt;/a&gt;.&lt;/strong&gt; A few weeks are left. Fix your top sellers before the traffic arrives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nobody controls what an AI recommends. But products with missing data are the easiest ones for an assistant to skip, and fixing data helps normal search and your storefront too. The checklist below is useful on its own, with or without an app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an AI assistant can read from a product
&lt;/h2&gt;

&lt;p&gt;Shopify's help center describes which product fields Catalog shares and which ones AI platforms consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title and description.&lt;/strong&gt; What the product is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images and alt text.&lt;/strong&gt; Images are shared; alt text is a written description of each one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price and availability.&lt;/strong&gt; Whether it can be bought, and for how much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options and variants.&lt;/strong&gt; Size, color and so on, including the option names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category, product type, vendor, collections and tags.&lt;/strong&gt; The "what kind of thing is this" fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Barcodes.&lt;/strong&gt; GTIN, UPC, EAN or ISBN, which can help a platform recognize a specific, known item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A published product page with a public URL.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An assistant can only work with what is actually in these fields. An empty category, a one-line description and images without alt text give it very little to go on. (Many themes also add schema.org markup to product pages; Google's Rich Results Test shows what yours outputs. Treat it as a bonus.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Black Friday 2026 Shopify checklist: 12 points to finish before November 27
&lt;/h2&gt;

&lt;p&gt;Start with your top sellers and the products you will promote, then continue by traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required (a product can be left out of Catalog without these)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A clear title.&lt;/strong&gt; Say what it is, not just a model name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;At least one image.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A price above zero.&lt;/strong&gt; Shopify documents that free products are left out of Catalog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Published and not hidden from search engines.&lt;/strong&gt; Check the product status, the online store sales channel and the search-engine-hiding setting. Stores in password-protected mode are left out too.
&lt;em&gt;Citebeam flags unpublished and hidden products automatically and marks them as not eligible.&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Details that help&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Shopify product category&lt;/strong&gt; from the standard taxonomy, plus the category attributes (color, material, size) it offers.
&lt;em&gt;Citebeam flags missing categories automatically and can set one in one click, with preview and undo.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real description.&lt;/strong&gt; What it is, who it is for, materials, dimensions, care. Two short paragraphs beat one line. Do not paste the supplier's text word for word.
&lt;em&gt;Citebeam flags thin descriptions (under 150 characters) so you know which to rewrite first.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The manufacturer's barcode on every variant.&lt;/strong&gt; Use the real GTIN, UPC, EAN or ISBN. Never invent one. The last digit of a GTIN is a check digit, so a numeric barcode that fails it is usually a typo.
&lt;em&gt;Citebeam flags missing barcodes and barcodes that fail the check digit. It never fills barcodes in for you.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Properly named variant options.&lt;/strong&gt; "Size" and "Color", not "Option 1".&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vendor, product type, a collection and a few tags.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alt text on every product image.&lt;/strong&gt; Describe what the image shows in plain words, as you would to someone on the phone.
&lt;em&gt;Citebeam flags images without alt text and can write it in one click, with preview and undo.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An SEO title (up to 70 characters) and description (about 160).&lt;/strong&gt;
&lt;em&gt;Citebeam flags missing SEO text and can fill it in one click, with preview and undo.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability.&lt;/strong&gt; Out-of-stock products are less useful to shoppers. If something will not be restocked before the sale, think about whether it should stay published.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid keyword stuffing, inflated claims and made-up barcodes. Accurate data helps; padded data does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skip the afternoons: run the check with Citebeam
&lt;/h2&gt;

&lt;p&gt;Going through 12 points by hand for every product is tedious once you pass a few dozen. &lt;a href="https://apps.shopify.com/ai-visibility-vs-competitors" rel="noopener noreferrer"&gt;Citebeam: AI Visibility&lt;/a&gt; is the Shopify app I built to do it for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing to set up.&lt;/strong&gt; It reads your products and runs 20 checks per product, grouped as required, important and recommended, using the fields and rules Shopify documents. A few thresholds are the app's own, and it says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 0-100 readiness score, plus your top issues and what to do about each.&lt;/strong&gt; Required problems come first, and you can open the list of products behind each issue. Gaps do not hide in a long catalog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-click fixes for four fields:&lt;/strong&gt; product category, product type, SEO title and description, and image alt text. You see a preview first, nothing is saved until you click Apply, and every change can be undone. Titles, descriptions and barcodes stay in your hands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest about barcodes.&lt;/strong&gt; It flags missing or malformed ones and never makes one up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free to start.&lt;/strong&gt; The free plan covers the catalog check for up to 250 products and 25 one-click fixes a month. Paid plans are listed on the App Store page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Citebeam helps you find and close the gaps in minutes instead of afternoons. The readiness score shows how complete your data is against Shopify's documented rules, product by product, so you always know what to fix next. The result: a catalog with no easy reasons for an AI assistant to skip it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/ai-visibility-vs-competitors" rel="noopener noreferrer"&gt;Run the free catalog check now&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Black Friday plan for the weeks left
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;This week:&lt;/strong&gt; run the check and fix the blockers: unpublished products, missing images, hidden products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next:&lt;/strong&gt; categories, descriptions and barcodes for top sellers and sale items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The week before November 27:&lt;/strong&gt; alt text and SEO text, then a last look at stock and prices.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sale will not wait, and fixing the data on your top sellers first is the part you can control. Start the free catalog check today: search "Citebeam" on the Shopify App Store or open &lt;a href="https://apps.shopify.com/ai-visibility-vs-competitors" rel="noopener noreferrer"&gt;apps.shopify.com/ai-visibility-vs-competitors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://citebeam-three.vercel.app/check" rel="noopener noreferrer"&gt;Run the free Citebeam readiness check on your store&lt;/a&gt;, then fix the gaps with Citebeam before Black Friday.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: Shopify Help Center pages on Shopify Catalog, Catalog requirements, optimizing products for Catalog, product category, and adding keywords for SEO.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>ai</category>
      <category>blackfriday</category>
    </item>
  </channel>
</rss>
