Python IKEA product extraction workflow with validation checks and CSV output

An IKEA scraper extracts public product information into structured records using Python. This article concerns product data, not physical kitchen, dough, bench, window, or cleaning scrapers.

Our quick run on October 18, 2026 collected search results and checked three product pages. The result is a reproducible starter with CSV output, explicit missing fields, and a small snapshot of network behavior, not a benchmark.

Start with authorization: IKEA’s US terms require prior express written consent from an authorized executive for automated monitoring or scraping. Review the applicable market terms before running the example.


Key Facts

  • Our quick run on October 18, 2026 collected 48 search results.
  • All 48 collected products had a price.
  • Ratings appeared for 44 of the 48 collected products.
  • Three residential-route product responses contained InStock in Product JSON-LD.
  • The three measured product responses averaged 115,309 transferred bytes.
  • The tested script exports CSV and JSON.
  • One small snapshot cannot establish comparative block rates.

What should an IKEA scraper collect?

Collect enough information to identify the exact product, interpret its price, and establish when the observation happened. Keep availability nullable until you have evidence for the relevant fulfillment context.

  • Identity: article number, variant description, product URL, and name.
  • Commercial context: price, currency, country market, and any visible offer conditions.
  • Observation context: UTC timestamp, source, and extraction status.
  • Optional fields: rating, review count, and separately qualified availability.

Treat article numbers as text so leading zeros survive exports. Keep color, dimensions, and package composition separate when building a comparison dataset; a shared product-family name is insufficient evidence of an identical item.

The starter exports item_id, name, price, currency, rating, reviews, availability, and url. It combines the variant description into the name and does not export market or timestamps. Add those to your surrounding collection records before using the output for historical analysis.

Schema.org’s Product vocabulary documents properties such as sku, offers, and aggregateRating. That vocabulary describes possible structured data; it does not establish which fields IKEA publishes. Our three successful product responses contained Product JSON-LD, which is the evidence for this example.


How do you build an IKEA scraper with Python?

Python can extract IKEA product fields by fetching an authorized public response, parsing its structured data, and exporting selected values. Our tested script reads the search page’s public JSON response, then checks Product JSON-LD on three returned product URLs.

The recorded environment was Linux, Python 3.12.3, curl_cffi 0.15.0, and requests 2.31.0. The executable example imports requests from curl_cffi; it does not use the separately installed Requests package or Beautiful Soup.

For an HTML-first implementation, Requests documents response handling and proxy configuration, while Beautiful Soup documents parsing and searching HTML. Those are useful building blocks, but they are not interchangeable with this script’s tested import and arguments.

Step 1: Install the dependency with pip install curl_cffi. For reproduction, use the recorded version rather than assuming a later release behaves identically.

Step 2: Save the following as ikea_scraper.py. Configure PROXY_URL through your secret-management process when an authorized proxy route is needed. The credential-shaped text in the source comment is illustrative, not a working credential.

import csv, json, os, random, re, time
from curl_cffi import requests   # pip install curl_cffi: browser-like TLS fingerprint

PROXY = os.environ.get("PROXY_URL")  # e.g. http://USER__cr.us:[email protected]:823
PROXIES = {"http": PROXY, "https": PROXY} if PROXY else None
# The IKEA search page loads its results from this public JSON endpoint
SEARCH_API = "https://sik.search.blue.cdtapps.com/us/en/search-result-page"
LD_JSON = re.compile(r'<script[^>]*application/ld\+json[^>]*>(.*?)</script>', re.S)


def fetch(url, params=None):
    try:
        r = requests.get(url, params=params, impersonate="chrome", proxies=PROXIES, timeout=40)
    except Exception as exc:
        print(f"{url} -> {type(exc).__name__}")
        return None
    blocked = r.status_code == 403 or "Attention Required" in r.text[:3000]
    print(f"{r.url[:80]} -> HTTP {r.status_code}, {len(r.content) // 1024} KB"
          + (", blocked (Cloudflare)" if blocked else ""))
    return None if blocked or r.status_code != 200 else r


def search(query, size=48):
    r = fetch(SEARCH_API, {"q": query, "size": size, "types": "PRODUCT"})
    if not r:
        return []
    main = r.json()["searchResultPage"]["products"]["main"]
    print(f"search '{query}': {len(main['items'])} of {main['max']} products")
    rows = []
    for item in main["items"]:
        p = item.get("product") or {}
        price = p.get("salesPrice") or {}
        rows.append({
            "item_id": p.get("itemNo"),
            "name": f"{p.get('name')} {p.get('typeName')}, {p.get('validDesignText', '')}".strip(", "),
            "price": price.get("numeral"),
            "currency": price.get("currencyCode"),
            "rating": p.get("ratingValue"),
            "reviews": p.get("ratingCount"),
            "availability": None,          # filled from the product page below
            "url": p.get("pipUrl"),
        })
    return rows


def product_details(url):
    """Product pages carry a schema.org Product block (JSON-LD) with stock status."""
    r = fetch(url)
    if not r:
        return {}
    for block in LD_JSON.findall(r.text):
        data = json.loads(block)
        for obj in data if isinstance(data, list) else [data]:
            if obj.get("@type") == "Product":
                offer = obj.get("offers") or {}
                return {"availability": (offer.get("availability") or "").split("/")[-1],
                        "price": offer.get("price"), "sku": obj.get("sku")}
    return {}


if __name__ == "__main__":
    rows = search("desk")
    if not rows:
        raise SystemExit("No products collected; check the fetch() output first.")
    for row in rows[:3]:                 # a few product pages, politely
        time.sleep(random.uniform(8, 12))
        details = product_details(row["url"])
        row["availability"] = details.get("availability") or "not checked/blocked"
    with open("ikea_products.csv", "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
        w.writeheader()
        w.writerows(rows)
    with open("ikea_products.json", "w", encoding="utf-8") as f:
        json.dump(rows, f, indent=2)
    print(f"saved {len(rows)} products")

Step 3: Run python3 ikea_scraper.py with PROXY_URL set for the residential route. The recorded command form was PROXY_URL=... python3 ikea_scraper.py, with the secret value omitted here.

The search response supplied the product URLs through pipUrl. The three checked article numbers were 80213074, 00473546, and 90214308. Use returned URLs rather than constructing guessed product slugs; search order can change, so a later run may check different products.

Our output files, ikea_products.csv and ikea_products.json, contained 48 rows. Only three received availability checks; the other 45 retained null availability. SQLite storage was not tested. For a later database implementation, preserve append-only observations keyed by market, article number, variant, and observation time.

Related Walmart extraction and Costco extraction tutorials offer retailer-specific context. Do not transfer their selectors or access assumptions to IKEA.


How do you validate extracted product data?

Use the 4-step product validation check: identity, market, completeness, and freshness. A successful HTTP response is only the beginning of a usable product record.

A 200 response is only the start of a usable record

  • Step 1, identity: compare the search article number with the product-page sku and inspect the variant.
  • Step 2, market: confirm the intended country, language, and currency before joining records.
  • Step 3, completeness: distinguish absent fields, unchecked fields, failed requests, and parsing failures.
  • Step 4, freshness: record when each source was fetched and retain earlier observations.

Our quick run on October 18, 2026 used one Linux server and a US residential route, public responses only, no login, and no challenge solving. Approximately 17 requests went to IKEA hosts, with pauses of 5-12 seconds across the session. The script itself pauses 8-12 seconds before each product request.

The six product-page observations below are a snapshot, not a benchmark. UTC times, elapsed values, and exact transferred bytes were recorded for the residential responses. Datacenter timings and exact transferred counts were unrecorded; their approximately 4 KB response bodies must not be substituted for transfer measurements.

UTC, October 18 Article number Route HTTP Challenge indicator Elapsed ms Transferred bytes Fields returned or missing Parse success
Unrecorded 80213074 Datacenter 403 Block page Unrecorded Unrecorded Product fields unavailable No
Unrecorded 00473546 Datacenter 403 Block page Unrecorded Unrecorded Product fields unavailable No
Unrecorded 90214308 Datacenter 403 Block page Unrecorded Unrecorded Product fields unavailable No
16:07:47 80213074 Residential 200 None observed 1812 114949 Name, sku, price, currency, availability, aggregate rating; none missing among these Yes
16:07:59 00473546 Residential 200 None observed 15084 114716 Name, sku, price, currency, availability, aggregate rating; none missing among these Yes
16:08:25 90214308 Residential 200 None observed 1492 116261 Name, sku, price, currency, availability, aggregate rating; none missing among these Yes

The separate search request returned 48 products: 48 with price and 44 with rating. ALEX drawer-unit results lacked ratings. The residential search response at 16:07:38 took 958 ms and transferred 59,186 bytes; the search host also returned HTTP 200 through the datacenter route.

Sources for the extraction results are our ikea_scraper.py run and its ikea_products.csv and ikea_products.json outputs; transport figures come from the accompanying run measurements. The script’s printed body length is not a transferred-byte counter. It also reads product-page price and sku without comparing or exporting them, so successful parsing does not mean identity and price reconciliation passed.


What should you do when requests fail?

First distinguish an access denial from missing rendered content or a parser error. Stop on a denial or challenge; investigate an empty successful response without automatically increasing traffic.

Our datacenter requests to www.ikea.com returned HTTP 403 with a Cloudflare block message using both Requests and curl_cffi. Through the residential route, plain Requests also opened the site. This suggests a route-dependent restriction, but the small sample cannot isolate the exact blocking mechanism.

The successful search HTML was approximately 849-863 KB yet contained no product results. The results arrived through the search page’s public JSON request. This was a content-loading issue, distinct from the datacenter block.

Playwright’s Python library can drive a browser when rendering is necessary. Its locator documentation explains targeting page elements and waiting behavior. A browser fallback was not tested here; use it only when authorized rendering supplies required fields, not as a response to denied access.

The starter sets a 40-second request timeout and has no automatic retry loop. Its challenge check is narrow, and returning None ends that fetch rather than stopping the whole job. For unattended operation, require a job-level stop on denial, bounded retries for transient failures, and explicit parsing-error records.

For diagnosis, see our guides to 403 responses and Python request retries. Pacing is a load-control choice, not permission or a guarantee of access.


How can you track IKEA prices across countries?

Compare matching article numbers and variants while preserving each market’s original currency and timestamp. Calculate a converted comparison separately, with its exchange-rate date and assumptions attached.

Keep tax treatment, member eligibility, promotion dates, package quantity, delivery charges, and possible customs costs explicit. When those details are unknown, label the comparison incomplete instead of presenting a definitive cheapest-market recommendation.

The tested script collects one US search response and does not perform currency conversion or cross-market matching. Its combined name field is useful for inspection but insufficient for reliable variant matching at scale. A country-targeted network route also does not replace selecting the correct market URL.

Preserve old observations rather than overwriting yesterday’s price. Store the raw amount alongside any normalized value, and separate a price change from a changed product match or missing response.

Our guides to retailer price tracking and building a price comparison tool discuss the surrounding workflow. Validate a small set of known matches before expanding coverage.


How can you monitor IKEA stock availability?

Automatic stock monitoring needs separate observations for store pickup, collection, and delivery. A generic InStock value cannot establish whether an item can reach a particular address or be collected from a selected store.

IKEA’s UK availability guidance asks shoppers to select a store for store stock and enter a postcode for delivery and pickup options. It also recommends checking again before traveling because availability can change.

Our three successful product responses contained InStock in JSON-LD. We did not test store quantities, delivery promises, collection slots, or checkout availability. The script therefore produces a page-level indication, not a fulfillment guarantee.

For authorized monitoring, define the location and fulfillment method first. Choose a conservative schedule based on the decision you need to make, preserve the last successful observation, and send a notification only after a validated change. A failed request should produce an unknown state, never an out-of-stock alert.

Include the observation time and source in each notification. Avoid collecting reviewer names: they appeared in review blocks during our run but were excluded from the selected output fields.


Is there an official IKEA API or export alternative?

We did not locate official documentation for a generally available IKEA product API. That does not prove none exists; ask IKEA about authorized access or exports for your intended market and use.

The search host observed in our run is a website dependency, not evidence of a supported developer contract. Its response reported 190 matches for desk, but the script collected only 48. An attempted start offset returned HTTP 400, so this example makes no claim of complete pagination or catalog coverage.

ReefAPI’s IKEA documentation includes search and product-detail examples, including a product_id and country request. It is a third-party alternative, not an official IKEA interface. We did not test it; validate sample output, provenance, permissions, and current commercial terms before relying on it.


Which extraction approach fits your project?

Choose the smallest approach that satisfies your authorized data requirements. Evaluate field accuracy and maintenance effort before optimizing request volume.

Approach Use X when Avoid X when
Requests/parser Use Requests/parser when needed fields exist in HTML. Avoid Requests/parser when rendering is required.
Playwright Use Playwright when rendering supplies missing fields. Avoid Playwright when plain HTML suffices.
Page monitor Use a page monitor when watching a few changes. Avoid a page monitor when building structured catalogs.
Hosted extractor Use a hosted extractor when verified output fits requirements. Avoid a hosted extractor when provenance or limits are unclear.
Residential proxy Use a residential proxy when authorized work needs another network route. Avoid a residential proxy when expecting parsing or guaranteed access.

Our residential route used DataImpulse. At its starting residential rate of $1 per GB, the measured HTML-only estimate is 115,309 bytes x 1,000 / 1,000,000,000 x $1, approximately $0.12 per 1,000 product pages.

Using decoded body sizes instead would suggest approximately $1.13, illustrating why transferred bytes and decoded content must remain separate. The search response measured approximately $0.06 per 1,000 calls at the same rate, with 48 products in the observed response.

These estimates exclude browser assets, retries, billing overhead, compute, storage, and maintenance. They are extrapolations from three product transfers and one measured search transfer, not invoices or future cost guarantees.

Budget for parser changes, missing-field review, and source checks. DataImpulse supplies network routes, not a scraping API, managed unblocker, VPN, or data vendor. Your application still owns extraction, validation, and stopping rules.


What legal and contractual issues should you assess?

Assess permission, contractual restrictions, and applicable law before collection. Public visibility does not settle those questions.

IKEA’s US terms prohibit automated monitoring, copying, or scraping without prior express written consent from an authorized executive. Market terms differ. Obtain the required permission and document the scope of your intended collection.

The wildcard group in IKEA’s robots.txt disallows several search-query paths. Evaluate the exact host, path, and user agent; robots rules are not legal permission and do not automatically transfer between hosts.

The 2022 hiQ v. LinkedIn opinion addressed public-profile access and the CFAA at the preliminary-injunction stage. It is not universal scraping authorization.

EU database rights can concern substantial extraction and repeated systematic extraction. Assess copyright separately, minimize personal data, and avoid account content. Our web scraping legal overview offers additional context, not a decision for your project.

the reader decides, check local law


Frequently Asked Questions

What is an IKEA product-data scraper?

It is software that converts product responses into records suitable for analysis or storage. Useful records preserve product identity, price context, source, and observation time.

How can Python extract IKEA product fields?

Fetch an authorized response, inspect its structure, and map selected fields into rows. This example uses search JSON for discovery and Product JSON-LD for three availability checks, then exports CSV and JSON.

Does IKEA provide a public product API?

We did not locate documentation establishing a generally available official product API. A website’s accessible JSON response should not be mistaken for a supported external API agreement.

How should IKEA prices be compared across countries?

Confirm the article and variant match before comparing amounts. Retain original currencies and record conversion timing, tax assumptions, package quantities, and delivery costs separately.

Can IKEA stock availability be monitored automatically?

An authorized workflow can record changes in the availability signals it can actually observe. Our example checks only page-level structured data, so it cannot confirm store inventory or delivery eligibility.

Why are some availability cells empty?

The script checks only the first three search results. Remaining rows are unchecked, while the text not checked/blocked indicates that a requested detail value could not be obtained.

Does a residential proxy guarantee successful extraction?

No. A network route does not validate a response, repair a parser, establish permission, or guarantee future access. Our successful residential observations describe only this small run.


Plan a small, measurable collection

If your authorized workflow needs a residential route, consider DataImpulse and start with a small sample. Measure transferred bytes, validate the fields you need, and expand only when access, data quality, and maintenance costs are understood.

Last updated: October 18, 2026.


Share article: