DEV Community

BotHot
BotHot

Posted on

Check a Shopify product CSV for missing prices and images before you import it

Before you import a product CSV into Shopify, it helps to know which products have no price or no image. Shopify may accept the file anyway, and you only notice later that a product shows up without a picture or at 0.00.

This script reads a product CSV and lists every product that has a missing, zero or unreadable price, or no image at all. It only reads the file and never changes it. It uses only Python's standard library.

One product, several rows

In Shopify's product CSV a product can span several rows: one per variant, plus extra rows that only carry another image. So "no image" cannot be checked per row. The script groups rows by Handle and asks two questions per product: does at least one row have a price, and does at least one row have an image URL?

The script

#!/usr/bin/env python3
import argparse
import csv
import sys

REQUIRED = ["Handle", "Variant Price", "Image Src"]


def read_rows(path):
    with open(path, newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        missing = [c for c in REQUIRED if c not in (reader.fieldnames or [])]
        if missing:
            sys.exit(f"Missing column(s): {', '.join(missing)}")
        return list(reader)


def check(rows):
    """Return {handle: [problems]} for products with no price or no image."""
    products = {}
    for row in rows:
        handle = row["Handle"].strip()
        if not handle:
            continue
        p = products.setdefault(handle, {"title": "", "prices": [], "images": 0})
        if row.get("Title"):
            p["title"] = row["Title"]
        price = (row.get("Variant Price") or "").strip()
        if price:
            p["prices"].append(price)
        if (row.get("Image Src") or "").strip():
            p["images"] += 1

    problems = {}
    for handle, p in products.items():
        found = []
        if not p["prices"]:
            found.append("no price")
        else:
            for price in p["prices"]:
                try:
                    if float(price.replace(",", ".")) <= 0:
                        found.append(f"price {price}")
                        break
                except ValueError:
                    found.append(f"unreadable price {price!r}")
                    break
        if p["images"] == 0:
            found.append("no image")
        if found:
            problems[handle] = found
    return products, problems


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("csv_file")
    args = parser.parse_args()

    products, problems = check(read_rows(args.csv_file))
    print(f"{len(products)} products checked, {len(problems)} with problems")
    for handle, found in sorted(problems.items()):
        print(f"  {handle}: {', '.join(found)}")


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Trying it

I tested it on a small made-up file (not real shop data):

Handle,Title,Variant Price,Image Src
blue-mug,Blue Mug,12.50,https://example.com/a.jpg
blue-mug,,,https://example.com/b.jpg
red-mug,Red Mug,,https://example.com/c.jpg
tote-bag,Tote Bag,19.90,
sticker-pack,Sticker Pack,0,https://example.com/e.jpg
poster-a3,Poster A3,"9,90",https://example.com/f.jpg
notebook,Notebook,abc,
Enter fullscreen mode Exit fullscreen mode
$ python3 check_missing.py sample.csv
6 products checked, 4 with problems
  notebook: unreadable price 'abc', no image
  red-mug: no price
  sticker-pack: price 0
  tote-bag: no image
Enter fullscreen mode Exit fullscreen mode

The blue mug is fine even though its second row has no price: the image row belongs to the same product, and the first row has a price. The poster with the price 9,90 is accepted because the script also reads a comma as the decimal separator. If a column is missing from the file, the script stops with a message instead of guessing.

Limits

  • It checks only price and image URL presence. It does not test whether the image URL actually loads.
  • Variants of one product are treated together: if only one variant has a price, the product passes. Check per variant if that matters for your shop.
  • Column names can differ between exports. Check your own file's header first.

An easy next step is to run it automatically before every import and stop if the list is not empty.

I write these drafts with the help of AI tools and check the code and the results myself before publishing.

Top comments (0)