<?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: Niccolò Colombini</title>
    <description>The latest articles on DEV Community by Niccolò Colombini (@quicknicco).</description>
    <link>https://dev.to/quicknicco</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3858284%2Fa6179666-3449-4bb9-8752-f4100699ac07.jpg</url>
      <title>DEV Community: Niccolò Colombini</title>
      <link>https://dev.to/quicknicco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/quicknicco"/>
    <language>en</language>
    <item>
      <title>Shopify Automation: How I Managed an 80,000-Product Catalog with Python &amp; Pandas</title>
      <dc:creator>Niccolò Colombini</dc:creator>
      <pubDate>Fri, 03 Apr 2026 22:18:07 +0000</pubDate>
      <link>https://dev.to/quicknicco/shopify-automation-how-i-managed-an-80000-product-catalog-with-python-pandas-47o9</link>
      <guid>https://dev.to/quicknicco/shopify-automation-how-i-managed-an-80000-product-catalog-with-python-pandas-47o9</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2F0s1ilvw07kiv2rgp8lc5.webp" 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.amazonaws.com%2Fuploads%2Farticles%2F0s1ilvw07kiv2rgp8lc5.webp" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Manually managing an e-commerce catalog with 80,000 products, 11 different suppliers, and 5 languages is not just inefficient — it's a direct risk to your business margins. Each supplier sends price lists in different formats (&lt;code&gt;.csv&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;) with inconsistently named columns, making it practically impossible to keep prices, wholesale costs, and stock availability updated by hand.&lt;/p&gt;

&lt;p&gt;To solve this, I built a Python and Pandas workflow based on three technical pillars: backup management, dynamic price optimization, and automatic product onboarding.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Data Architecture: Safety and Golden Backup
&lt;/h2&gt;

&lt;p&gt;Catalog integrity starts with two strategic exports from &lt;strong&gt;Matrixify&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safety Backup (Daily):&lt;/strong&gt; Contains only the essential columns (barcode, ID, handle, SKU, price, and quantity). This is the file processed daily for fast syncs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Golden Backup (Weekly):&lt;/strong&gt; The complete "source of truth" for the entire site, imported into a SQL database for deeper analysis and duplicate management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each script cross-references the supplier file with the corresponding Safety Backup, ensuring that only fresh, relevant data is touched — without ever overwriting the full catalog unnecessarily.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Dynamic Pricing and Margin Algorithm
&lt;/h2&gt;

&lt;p&gt;The selling price calculation is not linear. It follows brand-specific business rules, handled by a single method — &lt;code&gt;apply_discount_or_margin&lt;/code&gt; — that covers two main scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard Logic:&lt;/strong&gt; The script starts from the wholesale cost, applies a markup, and verifies that the minimum guaranteed margin is respected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Imposed Price Logic:&lt;/strong&gt; For brands that define their own retail price, the script validates the residual margin and checks that the applied discount does not exceed contractual thresholds.
&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_discount_or_margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# None for brands with imposed prices
&lt;/span&gt;    &lt;span class="n"&gt;markup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&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="n"&gt;min_margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# Minimum guaranteed margin in €
&lt;/span&gt;    &lt;span class="n"&gt;max_margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;extra_discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Surgical Operations with Pandas
&lt;/h2&gt;

&lt;p&gt;Scripts are designed to intervene only where needed, avoiding unnecessary catalog overwrites.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Surgical" SEO Update:&lt;/strong&gt; Instead of reloading the entire database, the script identifies only products with missing meta-tags. This is done via a &lt;code&gt;merge(how='left')&lt;/code&gt; against the backup; only incomplete records are filtered and updated, keeping Shopify imports fast and lightweight.
&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;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;backup_df&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 HTML&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;backup_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEO Title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;backup_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEO Description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;to_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backup_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New Product Detection:&lt;/strong&gt; The system compares supplier barcodes against the Safety Backup. Using a &lt;code&gt;merge(how='right')&lt;/code&gt; and filtering for &lt;code&gt;right_only&lt;/code&gt; rows in the &lt;code&gt;_merge&lt;/code&gt; column, the script isolates products not yet in the system and automatically populates them with prices, quantities, and SEO-ready descriptions.
&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;new_products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;supplier_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;supplier_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Barcode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backup_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Barcode&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;h2&gt;
  
  
  4. Orchestration: Crontab, SFTP, and Matrixify
&lt;/h2&gt;

&lt;p&gt;The scripts don't run manually — the entire workflow is fully automated through three components working in sync:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Crontab&lt;/strong&gt; schedules and triggers each Python script on my NAS server at defined intervals — daily for price and quantity updates, with separate jobs for SEO and new product detection.&lt;/li&gt;
&lt;li&gt;The scripts output the processed CSV files directly to a &lt;strong&gt;dedicated SFTP server&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matrixify&lt;/strong&gt; is connected to the SFTP and runs on a precisely configured schedule, automatically picking up the files and importing them into Shopify.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a zero-touch pipeline: from the supplier's price list landing on the server to the products being updated on Shopify, no manual intervention is needed at any stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Crontab on NAS]
      ↓ triggers Python scripts
[Processed CSV files]
      ↓ deposited to SFTP
[Matrixify scheduled import]
      ↓ auto-imports into Shopify
[Live catalog updated]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;This approach reduces hours of manual work to a few minutes of execution, eliminating human errors on markups and keeping the catalog continuously updated and SEO-optimized.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Do you manage an e-commerce catalog with Python, or have you built a similar workflow?&lt;/strong&gt; Share your approach in the comments — I'd love to compare notes.&lt;/p&gt;

&lt;h1&gt;
  
  
  python #ecommerce #shopify #automation #pandas
&lt;/h1&gt;

</description>
      <category>automation</category>
      <category>dataengineering</category>
      <category>productivity</category>
      <category>python</category>
    </item>
  </channel>
</rss>
