DEV Community

Cover image for How to scrape Vinted listings and compare prices across countries with Python
Norbert Sobala
Norbert Sobala

Posted on

How to scrape Vinted listings and compare prices across countries with Python

Vinted runs 26 separate marketplaces, and the same jacket can show up on vinted.de, vinted.fr and vinted.it at once. If you resell, track second-hand prices or build a deal alert, you want that data in a table instead of 40 open browser tabs.

This guide uses the Vinted Scraper on Apify. I ran it this morning with a small test: the query levis 501, 25 newest listings each from Germany and France, plus the country price comparison switched on. It finished in about 15 seconds and returned 50 listings and one comparison row. Every number below comes from that run.

Vinted listings for levis 501 exported to a table

What data you get

Each listing is one row. These are the fields I used most in the test run:

Field Example from the run Notes
title Jean Levi's 501 as written by the seller
price 35 item price in local currency
buyerProtectionFee 2.45 Vinted's buyer fee
totalPrice 37.45 what a buyer actually pays before shipping
priceEur, totalPriceEur 35, 37.45 converted to EUR for cross-country work
brand, size, condition Levi's, EU 38, Très bon état condition comes in the marketplace language
favouriteCount 1 a rough demand signal
sellerIsBusiness false private seller vs Vinted Pro
country, marketplace fr, vinted.fr where the listing was found
photoUrls list of image URLs
url, id link to the listing
recordType item country-comparison for summary rows

Turn on Listing details and you also get the full description, all photos, category path, colour, material, shipping price and sold/reserved status. Seller profiles adds seller country, rating, review count, items sold and followers. Both cost extra per record, so leave them off until you need them.

The comparison row is a separate record. For my query it held min, 25th percentile, median, average, 75th percentile and max price per country, the cheapest country and the link to the cheapest listing.

What the test run showed

A few things stood out in the 50 rows:

  • Median total price (item plus buyer fee) was 26.95 EUR on both sites. The cheapest listing cost 8.05 EUR on both.
  • 19 of the 25 listing IDs from vinted.de were also in the vinted.fr results. Vinted shows cross-border listings on EU sites, so "the German market" and "the French market" overlap a lot.
  • 44 listings came from private sellers and 6 from business accounts.
  • 41 of 50 had zero favourites. These were the newest listings, so that is expected.
  • 42 titles contained "501". The other 8 were other Levi's models (504, unlabeled jeans), because Vinted search is fuzzy.

The overlap is the practical lesson. If you want to compare real local prices, use the Seller countries filter or turn on seller profiles, so the comparison is grouped by where the seller is located instead of which domain the listing appeared on. The README says the same, and my run confirmed it.

Option 1: no code

  1. Open the Vinted Scraper page and click Try for free. You need a free Apify account.
  2. The easiest input is a URL. Set your filters on any Vinted site in the browser (brand, size, price, condition), copy the address bar and paste it into Vinted URLs. Category, brand, single listing and seller wardrobe links work too.
  3. Or type keywords into Search queries, one per line, and pick the Countries. Each query runs in every selected country.
  4. Set Max listings per search. I used 25. 0 means everything, and the actor splits the search into price ranges to get past Vinted's 960-result cap.
  5. Optional: tick Compare prices between countries if you picked two or more countries.
  6. Click Start, then export the dataset as CSV, Excel or JSON.

Input form of the Vinted Scraper

Main input options

  • Search queries and Vinted URLs: what to scrape. You can mix both.
  • Countries or All 26 countries: which marketplaces to search.
  • Sort by: newest_first (default), relevance, price_low_to_high, price_high_to_low.
  • Categories, Brands, Sizes, Condition, Colours, Material IDs, Min/Max price: the same filters as on the website. Names or IDs both work; sizes behave best together with a category.
  • Exclude words in title and Title must contain: cleans up fuzzy search. Excluded listings are free. In my run, titleMustInclude: ["501"] would have dropped the 8 off-model rows.
  • Seller type, Seller countries, Min favourites: filter by private vs business, seller location and demand.
  • Listing details and Seller profiles: paid enrichment, described above.
  • Only new listings, Also return price drops, Also detect sold / removed listings, Monitor name: monitoring mode, covered below.

Option 2: Python with apify-client and pandas

Install the two packages:

pip install apify-client pandas
Enter fullscreen mode Exit fullscreen mode

Put your Apify API token in an environment variable called APIFY_TOKEN (Console, Settings, API & Integrations). Then:

import os
from apify_client import ApifyClient
import pandas as pd

client = ApifyClient(os.environ["APIFY_TOKEN"])

run_input = {
    "searchQueries": ["levis 501"],
    "countries": ["de", "fr"],
    "maxItemsPerSearch": 25,
    "sortBy": "newest_first",
    "compareCountries": True,
}

run = client.actor("liveclaude/vinted-scraper").call(run_input=run_input)
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())

listings = pd.DataFrame([i for i in items if i.get("recordType") == "item"])
comparison = [i for i in items if i.get("recordType") == "country-comparison"]

# price summary per marketplace
print(
    listings.groupby("marketplace")["totalPriceEur"]
    .describe()[["count", "min", "50%", "max"]]
)

# how many listings appear on both sites
de_ids = set(listings.loc[listings["country"] == "de", "id"])
fr_ids = set(listings.loc[listings["country"] == "fr", "id"])
print("Shared listings:", len(de_ids & fr_ids))

# ten cheapest listings
cols = ["marketplace", "title", "size", "condition", "totalPriceEur", "url"]
print(listings.sort_values("totalPriceEur")[cols].head(10))

for row in comparison:
    for c in row["countries"]:
        print(c["marketplace"], "median", c["medianPriceEur"], "EUR")

listings.to_csv("vinted_levis_501.csv", index=False)
Enter fullscreen mode Exit fullscreen mode

With my input this printed a median of 26.95 EUR for both marketplaces and 19 shared listings. Your numbers will differ because Vinted listings change every minute.

Two small notes. The condition column is localized ("Sehr gut" on vinted.de, "Très bon état" on vinted.fr), so map it to one language before you group by it. And filter on recordType as shown, because the comparison row has a different shape from the listing rows.

Scheduling, monitoring and integrations

The part I would use day to day is monitoring. Switch on Only new listings, give the run a Monitor name, and create a schedule in Apify Console (every 15 minutes or every hour). Each run then returns only listings it has not seen before. Every record gets a listingStatus of new, price-drop, sold or removed. Price drops include the previous price and the drop percentage. Sold and removed items include first seen date, last seen date and days on market, which is useful sell-through data if you resell.

Already seen listings are not charged, so a monitor that finds 5 new items costs a fraction of a cent.

From there you can push results to Google Sheets, Slack, Make, Zapier, n8n or a webhook through the Integrations tab. The actor can also be called from AI agents through the Apify MCP server.

Pricing per 1,000 results

Pay per event, proxies included. Current prices from the actor page:

Event Free plan Starter Scale Business
Listing $2.00 $1.20 $0.80 $0.50
Listing details (optional) $2.00 $1.50 $1.00 $0.80
Seller profile (optional, once per seller) $1.50 $1.00 $0.80 $0.60

A country comparison row costs about $0.01 each. My test charged 50 listings plus 1 comparison row, which is roughly $0.11 on the Free plan. Listings removed by your title or seller filters are free, and you can set a maximum cost per run so it stops at your limit.

Responsible use

The actor reads public pages only. It does not log in and does not need cookies. Vinted listings belong to real people, most of them private sellers, so keep personal data out of your pipeline unless you have a clear legal basis under GDPR. For price research you rarely need anything beyond price, brand, size, condition and country. I left seller IDs and seller profiles out of the screenshots and the code above for that reason. Keep request volume sensible and read Vinted's terms before you build anything commercial on top of the data.

This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.

Top comments (0)