<?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: Norbert Sobala</title>
    <description>The latest articles on DEV Community by Norbert Sobala (@norbertso).</description>
    <link>https://dev.to/norbertso</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%2F4194910%2F2bd76f8d-af63-4c5e-a4a7-009c9f9d1375.jpeg</url>
      <title>DEV Community: Norbert Sobala</title>
      <link>https://dev.to/norbertso</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/norbertso"/>
    <language>en</language>
    <item>
      <title>How to scrape 1688.com wholesale prices, MOQ and suppliers with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Wed, 11 Nov 2026 08:26:30 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-1688com-wholesale-prices-moq-and-suppliers-with-python-1iho</link>
      <guid>https://dev.to/norbertso/how-to-scrape-1688com-wholesale-prices-moq-and-suppliers-with-python-1iho</guid>
      <description>&lt;p&gt;If you sell on Amazon, Shopify or Allegro, the number you care about most is the factory price. For a lot of consumer goods that price lives on 1688.com, the Chinese B2B marketplace run by Alibaba where manufacturers sell to resellers. The site is in Chinese, prices are in yuan, and the useful bits (quantity tiers, minimum order, how long a supplier has been around) are spread over search cards.&lt;/p&gt;

&lt;p&gt;This guide shows how to pull that into a table with the &lt;a href="https://apify.com/flarewow/1688-scraper" rel="noopener noreferrer"&gt;1688 Scraper&lt;/a&gt; on Apify, first without code and then with Python and pandas. I tested it on one keyword, 蓝牙耳机 (Bluetooth earphones), with 40 results. Every number below comes from that run.&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%2Fj24sjnbyrp9bng7jzovi.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%2Fj24sjnbyrp9bng7jzovi.png" alt="1688 Scraper results for Bluetooth earphones" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the run returned
&lt;/h2&gt;

&lt;p&gt;The run took 82 seconds and saved 40 offers. A few things stood out once the data was in a spreadsheet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prices ranged from 6.29 to 54.87 CNY, with a median of 12.0 CNY.&lt;/li&gt;
&lt;li&gt;8 of the 40 offers were paid placements (&lt;code&gt;isAd: true&lt;/code&gt;). Without them the median drops to about 9.95 CNY, so filter ads out before you average anything.&lt;/li&gt;
&lt;li&gt;35 of 40 suppliers were in Guangdong province: 17 in Dongguan and 16 in Shenzhen.&lt;/li&gt;
&lt;li&gt;27 suppliers were labelled 生产加工 (manufacturer) and 3 as 经销批发 (trading/wholesale). 10 had no type.&lt;/li&gt;
&lt;li&gt;The median supplier had been on 1688 for 3 years. 12 had been there for 5 years or more.&lt;/li&gt;
&lt;li&gt;5 offers showed 10,000+ units sold. The top one claimed 100,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tier data is where it gets interesting. One single-ear headset was listed at 12 CNY for 10 to 999 pieces, 4.5 CNY for 1,000 to 2,999 and 4.3 CNY from 3,000 up. The headline price in search was 12 CNY. If you only look at that number, you overestimate your landed cost by more than half at volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields you get
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it contains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;offerId&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1688 offer ID and link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;titleEn&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Chinese title and optional machine translation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Headline price, always CNY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priceTiers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List of &lt;code&gt;{minQuantity, maxQuantity, unit, price}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;minOrderQuantity&lt;/code&gt;, &lt;code&gt;unit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MOQ, filled when the offer shows tiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;salesCount&lt;/code&gt;, &lt;code&gt;salesText&lt;/code&gt;, &lt;code&gt;bookedCount&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Sales figures as shown on the card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repurchaseRate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Share of repeat buyers, e.g. &lt;code&gt;24%&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;supplierName&lt;/code&gt;, &lt;code&gt;supplierShopUrl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Company name and shop link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;supplierYearsOn1688&lt;/code&gt;, &lt;code&gt;supplierType&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Years on the platform, manufacturer or trader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;province&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Supplier location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;serviceScores&lt;/code&gt;, &lt;code&gt;serviceTags&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Overall, consultation, logistics, dispute, returns and goods scores, plus verification badges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;isAd&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;position&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Ranking context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Coverage in my run: &lt;code&gt;titleEn&lt;/code&gt; was filled on all 40 offers, &lt;code&gt;serviceScores&lt;/code&gt; on 38, &lt;code&gt;repurchaseRate&lt;/code&gt; on 28 and &lt;code&gt;minOrderQuantity&lt;/code&gt; on 18. MOQ is empty when 1688 does not show quantity tiers on the search card, which is a site limitation, so plan for gaps in that column.&lt;/p&gt;

&lt;p&gt;Supplier fields describe companies and their shops. There is no data about private individuals in the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input options
&lt;/h2&gt;

&lt;p&gt;There are only four settings, which keeps it simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;searchQueries&lt;/strong&gt;: a list of keywords. Chinese keywords return the most results (&lt;code&gt;手机壳&lt;/code&gt; for phone case, &lt;code&gt;充电宝&lt;/code&gt; for power bank). English works too, but the hit count is lower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;maxItemsPerQuery&lt;/strong&gt;: how many offers per keyword, up to about 2,000. That ceiling comes from 1688 search itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;translateTitles&lt;/strong&gt;: adds &lt;code&gt;titleEn&lt;/code&gt;. It uses a free translation service, so treat it as a rough guide. If the service is down, the field stays empty and the run still finishes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proxyFallback&lt;/strong&gt;: on by default. The actor tries without a proxy first and only switches to datacenter and then residential proxy if 1688 blocks it.&lt;/li&gt;
&lt;/ul&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%2F1qmk1a3r4ji3wl4vhtvz.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%2F1qmk1a3r4ji3wl4vhtvz.png" alt="1688 Scraper input form" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/flarewow/1688-scraper" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. You need a free Apify account.&lt;/li&gt;
&lt;li&gt;Type your keyword in &lt;strong&gt;Search keywords&lt;/strong&gt;. I used &lt;code&gt;蓝牙耳机&lt;/code&gt;. If you do not read Chinese, Google Translate is good enough to find the right term.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max offers per keyword&lt;/strong&gt; to something small for a first test, like 40.&lt;/li&gt;
&lt;li&gt;Tick &lt;strong&gt;Translate titles to English&lt;/strong&gt; if you want readable titles.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;. A 40 offer run finished in under a minute and a half for me.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Output&lt;/strong&gt; tab and export as CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Excel, sort by &lt;code&gt;price&lt;/code&gt; and hide the rows where &lt;code&gt;isAd&lt;/code&gt; is TRUE. That alone gives you a reasonable price range for a product category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with pandas
&lt;/h2&gt;

&lt;p&gt;Install the client and pandas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip install apify-client pandas openpyxl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script below runs the actor, drops ads, and works out what each offer costs at 1,000 units using the tier list.&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;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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="s2"&gt;"YOUR_APIFY_TOKEN"&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="s2"&gt;"flarewow/1688-scraper"&lt;/span&gt;&lt;span class="p"&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="s2"&gt;"searchQueries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"蓝牙耳机"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"maxItemsPerQuery"&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="s2"&gt;"translateTitles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&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="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&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="s2"&gt;"defaultDatasetId"&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;iterate_items&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="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;organic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"isAd"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organic&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value_counts&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;head&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;def&lt;/span&gt; &lt;span class="nf"&gt;price_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tiers&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="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tiers&lt;/span&gt; &lt;span class="ow"&gt;or&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="n"&gt;upper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&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="s2"&gt;"maxQuantity"&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;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"minQuantity"&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;qty&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"price"&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;None&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"price_at_1000"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"priceTiers"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;price_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"titleEn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"price_at_1000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"minOrderQuantity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"salesCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"supplierYearsOn1688"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;shortlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"price_at_1000"&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;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"price_at_1000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shortlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&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="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1688_bluetooth_earphones.xlsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes from running it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;price_at_1000&lt;/code&gt; is only filled for offers that publish tiers (18 of 40 in my run). For the rest, &lt;code&gt;price&lt;/code&gt; is all you have until you message the supplier.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serviceScores&lt;/code&gt; is a nested dict. &lt;code&gt;pd.json_normalize(items)&lt;/code&gt; flattens it into columns like &lt;code&gt;serviceScores.logistics&lt;/code&gt; if you want to filter on it.&lt;/li&gt;
&lt;li&gt;Prices exclude shipping inside China and international freight. Add those before you compare with your selling price.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;If you track a category, a weekly run per keyword is usually enough. In the Apify Console, open &lt;strong&gt;Schedules&lt;/strong&gt;, pick the actor and your saved input, and set a cron such as every Monday morning.&lt;/p&gt;

&lt;p&gt;From there you can push results onward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Sheets integration to keep a living price sheet.&lt;/li&gt;
&lt;li&gt;A webhook to n8n, Make or Zapier when a run finishes.&lt;/li&gt;
&lt;li&gt;The Apify MCP server if you want an AI agent to query offers for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each run creates a new dataset, so compare &lt;code&gt;offerId&lt;/code&gt; between runs to spot new listings and price changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The actor charges &lt;strong&gt;$2 per 1,000 offers&lt;/strong&gt; saved to the dataset. My 40 offer test cost $0.08. You only pay for results, and if no offers come back the run ends as failed with an explanation instead of an empty success. Set &lt;strong&gt;Max cost per run&lt;/strong&gt; in the run options if you want a hard cap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The actor reads publicly visible business listings and needs no login. Keep request volumes reasonable, respect 1688's terms and the law where you operate, and do not treat sales counts as audited figures. They are what the supplier card shows, and a 100,000 sold badge on an 8.99 CNY headset deserves a sample order before you believe it.&lt;/p&gt;

&lt;p&gt;If you want to try it, the &lt;a href="https://apify.com/flarewow/1688-scraper" rel="noopener noreferrer"&gt;1688 Scraper is here&lt;/a&gt;. Start with one keyword and 40 results, look at the tiers, then scale up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>ecommerce</category>
      <category>dropshipping</category>
    </item>
    <item>
      <title>How to turn EAN codes into Allegro product content with Python and pandas</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Mon, 09 Nov 2026 08:31:14 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-turn-ean-codes-into-allegro-product-content-with-python-and-pandas-5c5d</link>
      <guid>https://dev.to/norbertso/how-to-turn-ean-codes-into-allegro-product-content-with-python-and-pandas-5c5d</guid>
      <description>&lt;p&gt;You have a supplier file with a column of barcodes and a shop that needs titles, descriptions, specs and photos for each of them. Allegro.pl, the largest marketplace in Poland, already has that content for many products sold in the region. This tutorial shows how to pull it by EAN using the &lt;a href="https://apify.com/flarewow/allegro-description-photo-parameters" rel="noopener noreferrer"&gt;Allegro Product Data actor&lt;/a&gt; on Apify, either from the browser or from Python.&lt;/p&gt;

&lt;p&gt;I tested it with six barcodes on 9 November 2026. Everything below comes from that run, including the part where three of the six results were wrong, which is the most useful thing in this post if you plan to import the data anywhere.&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%2Fhkgv76125jfjvu4rxbks.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%2Fhkgv76125jfjvu4rxbks.png" alt="Allegro product content returned for six EAN codes" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What data you get
&lt;/h2&gt;

&lt;p&gt;One dataset row per EAN. These are the fields I used most, out of 55 in the output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example from my run&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5702017584072&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The code you sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;found&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only present on rows with no match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LEGO TECHNIC 42173 SZARY HIPERSAMOCHÓD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offer title, in Polish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;brand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LEGO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can be empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;186.65&lt;/code&gt;, &lt;code&gt;PLN&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Price of the chosen offer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;categoryPath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dziecko &amp;gt; Zabawki &amp;gt; Klocki &amp;gt; LEGO &amp;gt; Zestawy &amp;gt; Technic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full breadcrumb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parameters&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"Liczba elementów": "801", ...}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Object of spec name and value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;plain text&lt;/td&gt;
&lt;td&gt;1,212 characters for the LEGO set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;descriptionHtml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same content with markup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;images&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list of URLs&lt;/td&gt;
&lt;td&gt;Capped by &lt;code&gt;maxImages&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;descriptionImages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list of URLs&lt;/td&gt;
&lt;td&gt;13 for the LEGO set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;productRating&lt;/code&gt;, &lt;code&gt;productRatingsCount&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;4.97&lt;/code&gt;, &lt;code&gt;1693&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Product level, across offers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;manufacturerCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MTP63PX/A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filled on the iPhone row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The number of parameters varies a lot by category. The iPhone 15 row had 44, the LEGO set 15. Parameter names are in Polish (&lt;code&gt;Waga produktu z opakowaniem jednostkowym&lt;/code&gt; is packaged weight), so plan a mapping step if your catalog is in another language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main input options
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eans&lt;/code&gt;: the list of barcodes. This is the required field.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startUrls&lt;/code&gt;: Allegro links, if you already know the offer or product page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;marketplace&lt;/code&gt;: &lt;code&gt;pl&lt;/code&gt;, &lt;code&gt;cz&lt;/code&gt; or &lt;code&gt;sk&lt;/code&gt;. Applies to EAN lookups; URLs are detected automatically.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;productSelection&lt;/code&gt;: &lt;code&gt;relevance&lt;/code&gt; (default) or &lt;code&gt;popularity&lt;/code&gt;, for when one barcode has several offers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxImages&lt;/code&gt;: 1 to 50 photos per offer, default 10.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;descriptionFormat&lt;/code&gt;: &lt;code&gt;html&lt;/code&gt; (default), &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;both&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetchDescription&lt;/code&gt; and &lt;code&gt;fetchParameters&lt;/code&gt;: switch these off if you need only one of the two.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;downloadImages&lt;/code&gt;: stores the photo files in the run's key-value store and returns links in &lt;code&gt;imageFiles&lt;/code&gt;. Off by default, and charged per image.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/flarewow/allegro-description-photo-parameters" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Paste your barcodes into &lt;strong&gt;EAN / GTIN codes&lt;/strong&gt;, one per line.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Description format&lt;/strong&gt; to &lt;code&gt;HTML + text&lt;/code&gt; if you want both versions.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Start&lt;/strong&gt;. My six barcodes took 328 seconds and 21 page requests, so a batch of a few hundred is a job to leave running.&lt;/li&gt;
&lt;li&gt;Open the run's results and export as Excel, CSV or JSON. The &lt;code&gt;parameters&lt;/code&gt; object arrives flattened into columns in the spreadsheet formats, one per spec name.&lt;/li&gt;
&lt;/ol&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%2Fdhyzzg47d3hj4edcajzn.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%2Fdhyzzg47d3hj4edcajzn.png" alt="Input form of the actor" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python
&lt;/h2&gt;

&lt;p&gt;Install the two libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script below runs the actor and then applies the check that matters, which is comparing the returned brand against the brand you expected.&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&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;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expected&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;5702017584072&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;LEGO&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;195949036842&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;Apple&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;5449000000996&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;Coca-Cola&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;4006381333931&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;STABILO&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;5901234123457&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;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;3017620422003&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;Ferrero&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;flarewow/allegro-description-photo-parameters&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;eans&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;expected&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;descriptionFormat&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;both&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;maxImages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marketplace&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;pl&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="n"&gt;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;found&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="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_brand&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;hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ean&lt;/span&gt;&lt;span class="sh"&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="n"&gt;expected&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand_ok&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="n"&gt;hits&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&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;hits&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_ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;hits&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_ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;span class="n"&gt;columns&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;ean&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;title&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;brand&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;categoryPath&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;productRating&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;clean&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;columns&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;Kept &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;clean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &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;df&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;review&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 review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allegro_products.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my data it printed &lt;code&gt;Kept 2 of 6, 3 to review&lt;/code&gt;. The sixth row was the barcode with no match at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the six barcodes returned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;EAN&lt;/th&gt;
&lt;th&gt;Product I meant&lt;/th&gt;
&lt;th&gt;What came back&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5702017584072&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LEGO Technic 42173&lt;/td&gt;
&lt;td&gt;Correct. 186.65 PLN, 15 parameters, rating 4.97 from 1,693 ratings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;195949036842&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;iPhone 15 256 GB&lt;/td&gt;
&lt;td&gt;Correct. 3,667.10 PLN, 44 parameters, rating 4.79 from 107 ratings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5449000000996&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coca-Cola&lt;/td&gt;
&lt;td&gt;Wrong product: 5 L PET bottles for pickling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;4006381333931&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;STABILO point 88&lt;/td&gt;
&lt;td&gt;Mixed: STABILO title, but brand &lt;code&gt;ATS&lt;/code&gt; and a sliding door category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5901234123457&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GS1 sample barcode&lt;/td&gt;
&lt;td&gt;A fishing rod spare part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3017620422003&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nutella jar&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;found: false&lt;/code&gt;, with the note "No Allegro product with this EAN"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The likely reason is mundane. Sellers enter barcodes into their listings themselves, so a lookup can land on a listing that merely claims that EAN. Food and cheap stationery did badly in my small sample, while a branded toy and a phone came back clean. Six barcodes are too few to generalize from, so treat that as a hint and measure your own catalog.&lt;/p&gt;

&lt;p&gt;Checking that &lt;code&gt;parameters["EAN (GTIN)"]&lt;/code&gt; equals your input does not help. The STABILO row and the fishing part both passed that test. Brand comparison caught all three bad rows here. If your file has no brand column, compare &lt;code&gt;categoryPath&lt;/code&gt; against the category you sell in, or send anything suspicious to a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;Product content changes slowly, so a weekly or monthly refresh is plenty. In Apify Console, go to &lt;strong&gt;Schedules&lt;/strong&gt;, add the actor with your saved input and pick a cron expression. A weekly Monday run is &lt;code&gt;0 6 * * 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For moving the results elsewhere, the actor page lists Make, Zapier, n8n, Google Sheets, Slack, Airbyte and webhooks. A webhook on &lt;code&gt;ACTOR.RUN.SUCCEEDED&lt;/code&gt; pointing at your import script is enough for a simple PIM feed. You can also call it from an AI agent through the Apify MCP server, which suits the common next step of rewriting scraped descriptions in your own words.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing per 1,000 results
&lt;/h2&gt;

&lt;p&gt;Pay per event, current as of this run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event, per 1,000&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Starter&lt;/th&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;Business and up&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product content resolved&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;td&gt;$9.00&lt;/td&gt;
&lt;td&gt;$8.00&lt;/td&gt;
&lt;td&gt;$7.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra image URL, beyond the first 3&lt;/td&gt;
&lt;td&gt;$4.00&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image file downloaded, beyond the first 3&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;td&gt;$1.60&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EAN searched, no match&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.95&lt;/td&gt;
&lt;td&gt;$0.90&lt;/td&gt;
&lt;td&gt;$0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is also $0.00005 per actor start and $0.01 per 1,000 dataset rows. A resolved product includes the description, parameters and first 3 photos. My run logged 5 resolved products, so about $0.05 on the Free tier. Note that a wrong match counts as resolved, which is another reason to send clean, deduplicated barcodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The actor reads public product pages. Rows also carry &lt;code&gt;sellerLogin&lt;/code&gt;, &lt;code&gt;sellerUrl&lt;/code&gt; and review text in &lt;code&gt;latestReviews&lt;/code&gt;, and on Allegro a login can belong to a private individual. If you only need product content, drop those columns before storing anything, because under GDPR you need a legal basis to keep personal data. Descriptions and photos also belong to whoever wrote and shot them, so check the rights before republishing them in your shop, and rewrite the text rather than copying it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>ecommerce</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to scrape Google Play app reviews by star rating and app version with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Fri, 06 Nov 2026 08:26:35 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-google-play-app-reviews-by-star-rating-and-app-version-with-python-aj1</link>
      <guid>https://dev.to/norbertso/how-to-scrape-google-play-app-reviews-by-star-rating-and-app-version-with-python-aj1</guid>
      <description>&lt;p&gt;App reviews are free user research. People write them unprompted, they mention the app version, and on Google Play the developer can answer in public. The hard part is getting them out of the store in a shape you can sort and count.&lt;/p&gt;

&lt;p&gt;This walkthrough uses the &lt;a href="https://apify.com/liveclaude/app-reviews-scraper" rel="noopener noreferrer"&gt;App Store &amp;amp; Google Play Reviews Scraper&lt;/a&gt; on Apify. I ran it on Duolingo, US store, only 1 and 2 star reviews with at least 40 characters of text, capped at 25 reviews. Everything below comes from that one run.&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%2F42anlhqese47o50h6coi.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%2F42anlhqese47o50h6coi.png" alt="Dataset preview of negative Duolingo reviews from Google Play" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What came back from one small run
&lt;/h2&gt;

&lt;p&gt;The run took about 19 seconds. The status line said: 25 reviews, 2 app records, 159 filtered out. So the scraper read 184 recent reviews and kept the 25 that matched my rules. You only pay for the 25.&lt;/p&gt;

&lt;p&gt;A few things I noticed in those 25 reviews:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;13 were 1 star, 12 were 2 star. The sentiment label said "negative" for all 25.&lt;/li&gt;
&lt;li&gt;They were all posted on November 5, 2026, within roughly five hours. Duolingo gets a lot of reviews.&lt;/li&gt;
&lt;li&gt;11 reviews came from version 6.103.6 and 9 from 6.104.5. Three had no version at all.&lt;/li&gt;
&lt;li&gt;None had a developer reply yet. That makes sense for reviews a day old.&lt;/li&gt;
&lt;li&gt;7 of 25 mentioned streaks, 5 mentioned AI, 3 mentioned ads, 3 mentioned price. One person described a refund problem after upgrading to Duolingo Max.&lt;/li&gt;
&lt;li&gt;Median review length was 351 characters. These are not one-word rants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app records are useful context. Google Play listed Duolingo at 4.75 stars from 51,650,385 ratings, 500M+ installs, last updated 2026-11-03. The App Store record showed 4.72 stars from 5,565,332 ratings and version 7.147.1.&lt;/p&gt;

&lt;p&gt;That contrast is the reason to filter. A 4.75 average tells you nothing about what annoys people this week. Twenty-five recent low ratings, grouped by version, do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields you get
&lt;/h2&gt;

&lt;p&gt;Each review is one row. App details, if you turn them on, are separate rows with &lt;code&gt;recordType: "app"&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recordType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;review&lt;/code&gt; or &lt;code&gt;app&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;store&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;google_play&lt;/code&gt; or &lt;code&gt;app_store&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;appId&lt;/code&gt;, &lt;code&gt;appName&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Package name or Apple ID, plus the store title&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;country&lt;/code&gt;, &lt;code&gt;language&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Store country and review language&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rating&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 to 5 stars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sentiment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;negative&lt;/code&gt;, &lt;code&gt;neutral&lt;/code&gt; or &lt;code&gt;positive&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Review title (App Store) and body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the review was posted (ISO timestamp)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;appVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Version the reviewer had installed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;thumbsUp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Helpful" votes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;developerReply&lt;/code&gt;, &lt;code&gt;developerReplyDate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The public answer, Google Play only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Direct link to the review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;userName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reviewer display name. Treat as personal data (see the last section)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;App rows carry &lt;code&gt;developer&lt;/code&gt;, &lt;code&gt;rating&lt;/code&gt;, &lt;code&gt;ratingsCount&lt;/code&gt;, &lt;code&gt;installs&lt;/code&gt; (Google Play), &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;contentRating&lt;/code&gt;, &lt;code&gt;updated&lt;/code&gt;, &lt;code&gt;icon&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input options that matter
&lt;/h2&gt;

&lt;p&gt;You can paste store links, an app ID like &lt;code&gt;com.duolingo&lt;/code&gt;, or just a name like &lt;code&gt;Duolingo&lt;/code&gt;. A name is looked up in both stores. There is also &lt;code&gt;searchTerms&lt;/code&gt;, which takes a keyword such as &lt;code&gt;meditation&lt;/code&gt; and scrapes the top N apps for it (&lt;code&gt;maxAppsPerSearch&lt;/code&gt;, default 5).&lt;/p&gt;

&lt;p&gt;The rest is filtering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;countries&lt;/code&gt;: two-letter codes. Reviews are collected per country, so &lt;code&gt;["us", "gb", "de"]&lt;/code&gt; gives three separate sets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxReviewsPerApp&lt;/code&gt;: per app and per country, counted after filters. 0 means everything available.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sort&lt;/code&gt;: &lt;code&gt;newest&lt;/code&gt;, &lt;code&gt;most_relevant&lt;/code&gt; or &lt;code&gt;rating&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minRating&lt;/code&gt; / &lt;code&gt;maxRating&lt;/code&gt;: set &lt;code&gt;maxRating: 2&lt;/code&gt; for complaints only.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reviewsSince&lt;/code&gt;: a date, handy for weekly reports.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keywords&lt;/code&gt;: keep reviews that mention any of these words, like &lt;code&gt;crash&lt;/code&gt; or &lt;code&gt;login&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appVersion&lt;/code&gt;: version prefix such as &lt;code&gt;6.104&lt;/code&gt;. This is the one I use most for release checks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;developerReply&lt;/code&gt;: &lt;code&gt;with&lt;/code&gt;, &lt;code&gt;without&lt;/code&gt; or &lt;code&gt;any&lt;/code&gt;. &lt;code&gt;without&lt;/code&gt; gives you a support queue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minThumbsUp&lt;/code&gt; and &lt;code&gt;minTextLength&lt;/code&gt;: drop noise.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onlyNewReviews&lt;/code&gt; plus &lt;code&gt;monitorId&lt;/code&gt;: for scheduled runs, return only reviews you have not seen before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two limits from the README. Apple publishes only the latest 500 reviews per app and country, so add countries if you need more. Developer replies exist only on Google Play.&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%2Flpysuj56nqd3ptclnjul.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%2Flpysuj56nqd3ptclnjul.png" alt="Input form of the reviews scraper on Apify" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/app-reviews-scraper" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click Try for free.&lt;/li&gt;
&lt;li&gt;Under Apps, type &lt;code&gt;Duolingo&lt;/code&gt; or paste a Play Store link.&lt;/li&gt;
&lt;li&gt;Pick the US in Countries and set Max. reviews per app and country to 25.&lt;/li&gt;
&lt;li&gt;Set Max. rating to 2 and Min. review length to 40.&lt;/li&gt;
&lt;li&gt;Leave Include app details on if you want the rating and install numbers.&lt;/li&gt;
&lt;li&gt;Click Start. When it finishes, open the dataset and export to CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On a free Apify account this is enough to test a hypothesis in a couple of minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the actor and load the reviews into a DataFrame. Put your Apify token in an environment variable, not in the script.&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;os&lt;/span&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&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_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;apps&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;com.duolingo&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;countries&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;us&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxReviewsPerApp&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;sort&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;maxRating&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;minTextLength&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;includeAppDetails&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;liveclaude/app-reviews-scraper&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean&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;items&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;recordType&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;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;userName&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;userImage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&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;apps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;recordType&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;app&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;apps&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;appName&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;rating&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;ratingsCount&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;installs&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;updated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="c1"&gt;# Low ratings per app version
&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;reviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;appVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropna&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&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;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="c1"&gt;# Which topics come up (also matches plurals)
&lt;/span&gt;&lt;span class="n"&gt;topics&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;streak&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;ai&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;ads&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;subscription&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;refund&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s?\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regex&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="nf"&gt;sum&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;topics&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;counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duolingo_negative_reviews.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my run the version table showed 11 rows for 6.103.6 and 9 for 6.104.5, and the topic counter gave streak 7, ai 5, ads 3, price 3. Note that I drop &lt;code&gt;userName&lt;/code&gt; and &lt;code&gt;userImage&lt;/code&gt; right after loading. You rarely need them for analysis.&lt;/p&gt;

&lt;p&gt;If you want to compare competitors, put several apps in &lt;code&gt;apps&lt;/code&gt; and add &lt;code&gt;appName&lt;/code&gt; to the groupby. If you want to compare markets, add countries and group by &lt;code&gt;country&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;For ongoing monitoring, save the input as a task and add a schedule, for example daily at 07:00. Turn on &lt;code&gt;onlyNewReviews&lt;/code&gt; and give the monitor a name like &lt;code&gt;duolingo-negative&lt;/code&gt;. Each run then returns only new reviews, and the README says reviews already returned are not charged again.&lt;/p&gt;

&lt;p&gt;From there you can send the dataset to Google Sheets, post a Slack message through a webhook, or hook it into Make, Zapier or n8n. A setup I like: daily run, &lt;code&gt;maxRating: 2&lt;/code&gt;, &lt;code&gt;developerReply: "without"&lt;/code&gt;, results posted to a support channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The actor uses pay-per-event pricing, platform usage included. Current prices:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Starter&lt;/th&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000 reviews&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.18&lt;/td&gt;
&lt;td&gt;$0.165&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App details (per app and country)&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is also a start fee of $0.00005 per GB of memory. My run produced 25 reviews and 2 app records, so on the Free tier that is about $0.005 for reviews plus $0.004 for app details. Reviews dropped by filters are not charged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;Reviews are public, but the people who wrote them are private individuals. Their display names and avatars count as personal data under GDPR. Drop those columns unless you have a clear reason to keep them, and never publish them. Quote review text without names, as I did above. Keep request volumes reasonable and follow the stores' terms.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>android</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to scrape Workday career sites for jobs and pay ranges with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Wed, 04 Nov 2026 08:34:40 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-workday-career-sites-for-jobs-and-pay-ranges-with-python-cbm</link>
      <guid>https://dev.to/norbertso/how-to-scrape-workday-career-sites-for-jobs-and-pay-ranges-with-python-cbm</guid>
      <description>&lt;p&gt;Plenty of large employers run their careers page on Workday. NVIDIA, Boeing, Intel, Cisco and Pfizer all do, and the URLs end in &lt;code&gt;myworkdayjobs.com&lt;/code&gt;. The job lists look the same everywhere, which is handy: one scraper covers all of them.&lt;/p&gt;

&lt;p&gt;I used the &lt;a href="https://apify.com/flarewow/workday-jobs-scraper" rel="noopener noreferrer"&gt;Workday Jobs Scraper&lt;/a&gt; on Apify to pull "data engineer" openings from NVIDIA and Boeing. One run returned 40 postings with full descriptions and pay ranges in 15.7 seconds. Below is the setup, the code, and what the data looked like.&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%2Fd6w53qfmugx2zx3gmael.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%2Fd6w53qfmugx2zx3gmael.png" alt="Workday jobs dataset: NVIDIA and Boeing, data engineer" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What data you get
&lt;/h2&gt;

&lt;p&gt;Each row is one job posting. These are the fields I used the most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;th&gt;Example from my run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jobId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workday requisition ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JR2028539&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Job title&lt;/td&gt;
&lt;td&gt;Senior Staff Software Engineer - Data Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;company&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Workday tenant name&lt;/td&gt;
&lt;td&gt;Nvidia&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hiringEntity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Legal entity that hires&lt;/td&gt;
&lt;td&gt;Boeing Poland sp. z o.o. EFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;location&lt;/code&gt;, &lt;code&gt;allLocations&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Main location, every location of a multi-site job, country&lt;/td&gt;
&lt;td&gt;US, CA, Santa Clara&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remoteType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employer's own remote/hybrid/onsite label&lt;/td&gt;
&lt;td&gt;Onsite Job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;employmentType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full time or part time&lt;/td&gt;
&lt;td&gt;Full time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;postedText&lt;/code&gt;, &lt;code&gt;postedDate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;"Posted X days ago" and the exact date&lt;/td&gt;
&lt;td&gt;2026-10-30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;salaryText&lt;/code&gt;, &lt;code&gt;salaryMin&lt;/code&gt;, &lt;code&gt;salaryMax&lt;/code&gt;, &lt;code&gt;salaryCurrency&lt;/code&gt;, &lt;code&gt;salaryPeriod&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pay range parsed from the posting&lt;/td&gt;
&lt;td&gt;200000 / 322000 / USD / year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full job text&lt;/td&gt;
&lt;td&gt;about 5,900 characters on average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;url&lt;/code&gt;, &lt;code&gt;applyUrl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Job page and apply page&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;careerSiteUrl&lt;/code&gt;, &lt;code&gt;workdayTenant&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Which Workday site it came from&lt;/td&gt;
&lt;td&gt;boeing.wd1.myworkdayjobs.com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Postings are company data. There are no applicant profiles or personal data in the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What one small run returned
&lt;/h2&gt;

&lt;p&gt;Input: two companies, search text &lt;code&gt;data engineer&lt;/code&gt;, posted in the last 30 days, 20 jobs per company.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;40 jobs saved, 20 per company, all full time.&lt;/li&gt;
&lt;li&gt;Posting dates from 2026-10-14 to 2026-11-03.&lt;/li&gt;
&lt;li&gt;33 of 40 had a published pay range (NVIDIA 19 of 20, Boeing 14 of 20). 32 in USD, one in CAD from Boeing Vancouver.&lt;/li&gt;
&lt;li&gt;Median USD range: NVIDIA $168,000 to $264,500, Boeing $126,650 to $171,350.&lt;/li&gt;
&lt;li&gt;14 postings listed more than one location. One Boeing role listed 16 US sites.&lt;/li&gt;
&lt;li&gt;Countries: 34 US, 2 UK, and one each for India, Poland, Canada and Ethiopia.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The jobs without a pay range were mostly outside the US, which matches how pay-transparency laws work. One more detail I liked: a Boeing posting wrote its range as "$72.250 - $104,650" (note the dot), and the scraper still parsed &lt;code&gt;salaryMin&lt;/code&gt; as 72250.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main input options
&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%2Fb7k0slpmv9kz90cdv67f.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%2Fb7k0slpmv9kz90cdv67f.png" alt="Input form of the Workday Jobs Scraper" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Companies.&lt;/strong&gt; One per line. A Workday URL is the safest choice, for example &lt;code&gt;https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite&lt;/code&gt;. You can also type a name (&lt;code&gt;Boeing&lt;/code&gt;) or a website (&lt;code&gt;intel.com&lt;/code&gt;), and the actor tries to find the career site. I typed &lt;code&gt;Boeing&lt;/code&gt; and it resolved to &lt;code&gt;boeing.wd1.myworkdayjobs.com/EXTERNAL_CAREERS&lt;/code&gt; without help. Auto-detection does not work for every company. When it fails, the log says so and you paste the URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search text.&lt;/strong&gt; Sent to Workday's own search. Leave it empty to get every job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location contains.&lt;/strong&gt; Plain text match on the location, such as &lt;code&gt;Germany&lt;/code&gt;, &lt;code&gt;London&lt;/code&gt; or &lt;code&gt;TX&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Posted within last N days.&lt;/strong&gt; 0 means any date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max jobs per company.&lt;/strong&gt; Workday returns at most about 2,000 results per search, so split big employers with search text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include full description.&lt;/strong&gt; On by default. It opens each job to get the description, all locations, exact date and pay range. Turn it off if you only need titles and links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only new jobs.&lt;/strong&gt; Each run returns only postings that earlier runs with the same company and search text did not return.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/flarewow/workday-jobs-scraper" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Companies&lt;/strong&gt;, add the career site URLs or names you care about.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Search text&lt;/strong&gt; (I used &lt;code&gt;data engineer&lt;/code&gt;) and &lt;strong&gt;Posted within last N days&lt;/strong&gt; (30).&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max jobs per company&lt;/strong&gt; to something small for a first test, like 20.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;. My run finished in 16 seconds.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Output&lt;/strong&gt; tab and export to Excel, CSV, JSON or HTML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No proxy is needed. Workday's public job API answered fine without one, and the proxy option is off by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the actor and load the results into a DataFrame:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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;YOUR_APIFY_TOKEN&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_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;companies&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;https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite&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;Boeing&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;searchText&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;data engineer&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;postedWithinDays&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxJobsPerCompany&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeDescription&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;flarewow/workday-jobs-scraper&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;items&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="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="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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;jobs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Pay ranges in USD, per company
&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;salaryCurrency&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;USD&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;usd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&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;salaryMin&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;salaryMax&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# Share of postings with a published range
&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;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&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;salaryMin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;s&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="nf"&gt;notna&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

&lt;span class="c1"&gt;# Multi-location jobs
&lt;/span&gt;&lt;span class="n"&gt;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;n_locations&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;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;allLocations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;v&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;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&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="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&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;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_locations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_locations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;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;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workday_jobs.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my data the first groupby printed the medians above. The &lt;code&gt;description&lt;/code&gt; column is long, so I drop it before saving a CSV for a spreadsheet, and keep it in the DataFrame for keyword searches like &lt;code&gt;df[df.description.str.contains("Spark", case=False)]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;For monitoring, turn on &lt;strong&gt;Only new jobs&lt;/strong&gt; and create a Schedule in Apify Console, for example daily at 7:00. Each run then returns only postings that earlier runs have not seen, and you pay only for those.&lt;/p&gt;

&lt;p&gt;From there you can send results to Google Sheets, Slack, Airtable, n8n, Make or Zapier through Apify integrations, or point a webhook at your own endpoint. A setup I'd use for recruiting: one task per target company, daily schedule, new rows appended to a sheet that the team filters by location.&lt;/p&gt;

&lt;p&gt;If a run cannot scrape any company, it ends as Failed with a message instead of an empty success. A &lt;code&gt;RUN_SUMMARY&lt;/code&gt; record in the key-value store lists the status of each company, which makes failed schedules easy to spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The actor uses pay per result: &lt;strong&gt;$1.50 per 1,000 jobs&lt;/strong&gt;, plus a start fee of $0.00005 per run. Platform usage is included. You are charged only for jobs saved to the dataset, so a company that is not found costs nothing. My 40-job test cost about six cents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The actor reads public job postings that companies publish on their own Workday career sites. It does not log in and does not collect applicant data. Keep volumes reasonable, respect Workday's and the employers' terms, and if you republish postings, link back to the original job page.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>jobs</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to compare all sellers of one Allegro product by price and condition in Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Mon, 02 Nov 2026 08:36:02 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-compare-all-sellers-of-one-allegro-product-by-price-and-condition-in-python-5f2m</link>
      <guid>https://dev.to/norbertso/how-to-compare-all-sellers-of-one-allegro-product-by-price-and-condition-in-python-5f2m</guid>
      <description>&lt;p&gt;Allegro groups offers for the same item under one catalogue product page. That page is where sellers fight on price, and it is also where a quick glance misleads you: new units, ex-display stock, used phones and damaged ones all sit in one list. If you do repricing or keep an eye on distributors, you want that list as a table you can filter.&lt;/p&gt;

&lt;p&gt;This guide uses the &lt;a href="https://apify.com/liveclaude/allegro-price-comparison" rel="noopener noreferrer"&gt;Allegro Price Comparison&lt;/a&gt; actor on Apify. You paste a product or offer link and it returns every seller offer for that product. I ran it this morning on one product, the black iPhone 15 256 GB, and every number below comes from that run.&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%2F4gxxerw1spxoykzrd42e.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%2F4gxxerw1spxoykzrd42e.png" alt="Allegro Price Comparison dataset for iPhone 15 256 GB" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the run returned
&lt;/h2&gt;

&lt;p&gt;Input was a single product URL with &lt;code&gt;topNOffers&lt;/code&gt; set to 25. The log reported 57 offers on the product page, and the actor returned 22 offer rows in 33 seconds.&lt;/p&gt;

&lt;p&gt;A few things stood out once the data was in a table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prices ran from 1,359 PLN to 2,999.90 PLN, with a median of 2,119 PLN.&lt;/li&gt;
&lt;li&gt;Only one of the 22 offers was listed as new (&lt;code&gt;Stan: Nowy&lt;/code&gt;), at 2,999.90 PLN. Ten were used, eight were ex-display ("Powystawowy"), two were seller-refurbished and one was marked as damaged.&lt;/li&gt;
&lt;li&gt;The cheapest row (1,359 PLN) was the damaged unit. The cheapest used phone was 1,400 PLN.&lt;/li&gt;
&lt;li&gt;21 of 22 offers had Allegro Smart!, and only one carried the Lowest Price Guarantee badge.&lt;/li&gt;
&lt;li&gt;Two of the top three rows were promoted listings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the "lowest price" for this product, which the actor also reports as &lt;code&gt;lowestPriceForProduct: 1359&lt;/code&gt;, is a damaged phone. If you compare your own new stock against that number, your repricing rule will do something silly. Grouping by condition fixes it, and the Python section shows how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields you get
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;outputFormat: "offers"&lt;/code&gt; you get one row per seller offer. The most useful columns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;th&gt;Example from the run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;offerRank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Position in the product's offer list&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offer title as the seller wrote it&lt;/td&gt;
&lt;td&gt;Smartfon Apple iPhone 15 6 GB / 256 GB 5G czarny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Item price&lt;/td&gt;
&lt;td&gt;2999.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priceWithDelivery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Price plus cheapest delivery&lt;/td&gt;
&lt;td&gt;3010.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deliveryFrom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cheapest delivery cost&lt;/td&gt;
&lt;td&gt;10.49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deliveryTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delivery estimate shown on Allegro&lt;/td&gt;
&lt;td&gt;dostawa jutro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;smart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allegro Smart! eligible&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowestPriceGuarantee&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Gwarancja najniższej ceny" badge&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;promoted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sponsored placement&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;superSeller&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Super Seller badge&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sellerRating&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Positive feedback percentage&lt;/td&gt;
&lt;td&gt;95.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sellerIsCompany&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Business seller flag&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parameters&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offer parameters such as condition and warranty&lt;/td&gt;
&lt;td&gt;{"Stan": "Nowy", "Gwarancja": "12 miesięcy"}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;productRating&lt;/code&gt;, &lt;code&gt;productReviewsCount&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Catalogue product rating&lt;/td&gt;
&lt;td&gt;4.79, 106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowestPriceForProduct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lowest price on the product page&lt;/td&gt;
&lt;td&gt;1359&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;url&lt;/code&gt;, &lt;code&gt;offerId&lt;/code&gt;, &lt;code&gt;scrapedAt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Offer link, ID and timestamp&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are also seller identifier fields (&lt;code&gt;sellerLogin&lt;/code&gt;, &lt;code&gt;sellerId&lt;/code&gt;, &lt;code&gt;sellerUrl&lt;/code&gt;). In this run every seller was flagged as a company, but on a marketplace you can still hit private sellers, so the screenshots here leave those columns out and the Python example drops them before export.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;outputFormat: "products"&lt;/code&gt; you instead get one record per product with an &lt;code&gt;offers[]&lt;/code&gt; array plus &lt;code&gt;lowestPrice&lt;/code&gt;, &lt;code&gt;highestPrice&lt;/code&gt;, &lt;code&gt;avgPrice&lt;/code&gt; and &lt;code&gt;offerCount&lt;/code&gt;. That suits dashboards. For analysis I prefer the flat version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input options
&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%2Fta04wacto7tvyztn4ipj.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%2Fta04wacto7tvyztn4ipj.png" alt="Allegro Price Comparison input form" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;productUrls&lt;/strong&gt;: product links (&lt;code&gt;/produkt/...&lt;/code&gt;) or offer links (&lt;code&gt;/oferta/...&lt;/code&gt;), one per line. If you paste an offer link, the actor finds the catalogue product behind it. Allegro.pl, .cz and .sk all work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;topNOffers&lt;/strong&gt;: how many offers to return per product, 1 to 500, default 20.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sortBy&lt;/strong&gt;: &lt;code&gt;price-asc&lt;/code&gt;, &lt;code&gt;price-desc&lt;/code&gt; or &lt;code&gt;rating-desc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;smartFilter&lt;/strong&gt;: all offers, Smart! only, or no Smart!.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;includePromoted&lt;/strong&gt;: switch off to drop sponsored offers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;detectHiddenDiscounts&lt;/strong&gt;: for offers with the Lowest Price Guarantee badge, it reveals the price the seller actually set and the discount Allegro applies on top (&lt;code&gt;realSellerPrice&lt;/code&gt;, &lt;code&gt;hiddenDiscount&lt;/code&gt;). Charged per checked offer, so leave it off unless you need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onlyChanges&lt;/strong&gt; and &lt;strong&gt;monitorId&lt;/strong&gt;: monitoring mode. Each run returns only new offers and offers whose price moved since the last run, with &lt;code&gt;previousPrice&lt;/code&gt; and &lt;code&gt;priceChange&lt;/code&gt;. Unchanged offers are not charged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/allegro-price-comparison" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click Try for free.&lt;/li&gt;
&lt;li&gt;In the Input tab paste your product links. The iPhone 15 link is prefilled if you just want to look.&lt;/li&gt;
&lt;li&gt;Set "Top N offers per product" and pick "One row per offer" as output format.&lt;/li&gt;
&lt;li&gt;Click Start. My single-product run took about half a minute.&lt;/li&gt;
&lt;li&gt;Open the Output tab and export to Excel, CSV or JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Excel, add a filter on the parameters column and look at one condition at a time. That alone gives you a far more honest price spread than the product page does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with pandas
&lt;/h2&gt;

&lt;p&gt;Install the client and pandas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas openpyxl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the actor and summarise by condition:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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;YOUR_APIFY_TOKEN&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_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;productUrls&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;https://allegro.pl/produkt/smartfon-apple-iphone-15-6-gb-256-gb-5g-czarny-b7d39356-8090-4bcf-9247-b872c5f6935a&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;topNOffers&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;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;price-asc&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;outputFormat&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;offers&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;liveclaude/allegro-price-comparison&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Condition lives inside the parameters dict ("Stan" on Allegro.pl)
&lt;/span&gt;&lt;span class="n"&gt;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;condition&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;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;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;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;Stan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&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="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;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&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;min&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;median&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;max&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;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median&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="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Organic offers only, cheapest first, with delivery included
&lt;/span&gt;&lt;span class="n"&gt;organic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;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;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;promoted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priceWithDelivery&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;organic&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offerRank&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;priceWithDelivery&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;condition&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;smart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;head&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="c1"&gt;# Drop seller identifiers before you share the file
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;sellerLogin&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;sellerId&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;sellerUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iphone15_offers.xlsx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my run the summary came out like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;condition&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;th&gt;min&lt;/th&gt;
&lt;th&gt;median&lt;/th&gt;
&lt;th&gt;max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uszkodzony (damaged)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1359.00&lt;/td&gt;
&lt;td&gt;1359.00&lt;/td&gt;
&lt;td&gt;1359.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Używany (used)&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1400.00&lt;/td&gt;
&lt;td&gt;1949.00&lt;/td&gt;
&lt;td&gt;2219.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Powystawowy (ex-display)&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1990.00&lt;/td&gt;
&lt;td&gt;2154.00&lt;/td&gt;
&lt;td&gt;2189.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Odnowiony przez sprzedawcę (refurbished)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2156.98&lt;/td&gt;
&lt;td&gt;2201.00&lt;/td&gt;
&lt;td&gt;2245.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nowy (new)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2999.90&lt;/td&gt;
&lt;td&gt;2999.90&lt;/td&gt;
&lt;td&gt;2999.90&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap between the median used phone and the one new offer was about 1,050 PLN. If you sell new units, the useful benchmark is the "Nowy" row, and with only one new offer in the top 25 you would raise &lt;code&gt;topNOffers&lt;/code&gt; to see more of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;For competitor tracking, turn on &lt;code&gt;onlyChanges&lt;/code&gt;, give the watch list a &lt;code&gt;monitorId&lt;/code&gt; such as &lt;code&gt;competitors-iphone&lt;/code&gt;, and add a daily Schedule in Apify Console. Each run then returns only new offers and price changes. From there you can push results to Google Sheets, Slack, Make, Zapier or n8n, or pull them over the Apify API. The actor can also be called from AI agents through the Apify MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The actor is pay per event. Current prices per 1,000 events:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free plan&lt;/th&gt;
&lt;th&gt;Business plan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Seller offer scraped&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product compared&lt;/td&gt;
&lt;td&gt;$8.00&lt;/td&gt;
&lt;td&gt;$6.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Starter and Scale plans fall in between. My run had 22 offer events, so at Free plan prices the offers cost about $0.044. Hidden discount detection is billed separately per checked offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The actor reads publicly visible listing data. Keep request volumes reasonable, respect Allegro's terms, and treat any seller identifiers as potential personal data under GDPR: drop or pseudonymise them unless you have a clear reason to store them. Use the output for price analysis, not for contacting individual sellers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>pandas</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>How to pull open EU public tenders from TED into Python and pandas</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Fri, 30 Oct 2026 08:27:22 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-pull-open-eu-public-tenders-from-ted-into-python-and-pandas-3j98</link>
      <guid>https://dev.to/norbertso/how-to-pull-open-eu-public-tenders-from-ted-into-python-and-pandas-3j98</guid>
      <description>&lt;p&gt;Public tenders in Europe are published in one place: TED (Tenders Electronic Daily), the EU's official procurement journal. The data is open. The problem is volume and format. Notices arrive in 24 languages, values come in PLN, CZK or SEK, and the TED search UI is fine for browsing but slow if you want a daily shortlist in a spreadsheet.&lt;/p&gt;

&lt;p&gt;This guide shows how I pull open tenders from TED into a table with the &lt;a href="https://apify.com/flarewow/eu-tenders-ted-scraper" rel="noopener noreferrer"&gt;EU Tenders Scraper (TED)&lt;/a&gt; on Apify, first with no code, then with Python and pandas.&lt;/p&gt;

&lt;p&gt;For the examples I ran one real search this morning: keyword &lt;code&gt;cybersecurity&lt;/code&gt;, open tenders only, published in the last month, capped at 40 results. It returned 18 notices in 4.4 seconds. All numbers below come from that run.&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%2F313u6l6817i2vklsnvha.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%2F313u6l6817i2vklsnvha.png" alt="TED cybersecurity tenders in a table" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;Each row is one TED notice. The output has around 55 fields. These are the ones I use most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;th&gt;Example from my run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;noticeId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TED notice number&lt;/td&gt;
&lt;td&gt;&lt;code&gt;755875-2026&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Title in the original language&lt;/td&gt;
&lt;td&gt;Development, Implementation, Maintenance and Operation of financial IT Systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;buyerName&lt;/code&gt;, &lt;code&gt;buyerCountry&lt;/code&gt;, &lt;code&gt;buyerCity&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Contracting authority&lt;/td&gt;
&lt;td&gt;European Commission, DG BUDG, BE, Brussels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cpvMain&lt;/code&gt;, &lt;code&gt;cpvLabel&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Main CPV code and its label&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;72000000&lt;/code&gt;, IT services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;contractType&lt;/code&gt;, &lt;code&gt;procedureType&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Services / supplies / works, open / restricted etc.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;services&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;estimatedValueEur&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Estimated value converted to EUR&lt;/td&gt;
&lt;td&gt;700000000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;deadline&lt;/code&gt;, &lt;code&gt;daysToDeadline&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Submission deadline and days left&lt;/td&gt;
&lt;td&gt;2026-11-30, 32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;lotsCount&lt;/code&gt;, &lt;code&gt;lots&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Number of lots with title and value per lot&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;suitableForSme&lt;/code&gt;, &lt;code&gt;euFunded&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Flags set by the buyer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;documentsUrl&lt;/code&gt;, &lt;code&gt;submissionUrl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tender documents and e-submission links&lt;/td&gt;
&lt;td&gt;ec.europa.eu links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;isCorrection&lt;/code&gt;, &lt;code&gt;previousDeadline&lt;/code&gt;, &lt;code&gt;deadlineChanged&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Correction tracking&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;open / closed / awarded&lt;/td&gt;
&lt;td&gt;&lt;code&gt;open&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Award notices add winners (with country and company IDs), award value, number of bids and lowest/highest bid. I left awards out of this run to keep it short.&lt;/p&gt;

&lt;p&gt;A few things from the 18 notices that are hard to see in the TED UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They came from 9 countries. Belgium had 7, Italy 3, Poland 2, and Finland, Albania, Czechia, Greece, Romania and France one each.&lt;/li&gt;
&lt;li&gt;They were written in 8 languages. Five were in Dutch, so a keyword search in English alone still catches non-English notices here, but the README suggests adding the word in the buyer's language too.&lt;/li&gt;
&lt;li&gt;11 of 18 had a published value. The median was €459,036. The largest was a €700M European Commission framework for financial IT systems, split into 2 lots.&lt;/li&gt;
&lt;li&gt;Deadlines ranged from 1 to 47 days away, median 22. Three notices had a deadline that moved since the first version, and the actor flags that with &lt;code&gt;deadlineChanged&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;All 18 had both a documents link and an e-submission link.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Main input options
&lt;/h2&gt;

&lt;p&gt;You don't need all 30-odd settings. These cover most searches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;keywords&lt;/strong&gt;: full-text search in title, description and lots. &lt;code&gt;keywordMode&lt;/code&gt; switches between any and all. &lt;code&gt;excludeKeywords&lt;/code&gt; drops noise like &lt;code&gt;window cleaning&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;noticeStages&lt;/strong&gt;: &lt;code&gt;competition&lt;/code&gt; (open tenders), awards, or planning notices. Planning gives you a heads-up months before the tender.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;countries&lt;/strong&gt; and &lt;strong&gt;placeOfPerformance&lt;/strong&gt;: buyer country, or a NUTS code like &lt;code&gt;PL9&lt;/code&gt; for Masovia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cpvCodes&lt;/strong&gt;: CPV prefixes. &lt;code&gt;72&lt;/code&gt; is IT services, &lt;code&gt;45&lt;/code&gt; construction, &lt;code&gt;33&lt;/code&gt; medical equipment. This is language-independent, so it is more reliable than keywords for broad sectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;minValue / maxValue&lt;/strong&gt;: filter by estimated value. Notices without a value are excluded when you set these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onlyOpen&lt;/strong&gt; and &lt;strong&gt;deadlineWithinDays&lt;/strong&gt;: only tenders you can still bid on, or only those closing within N days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;publishedWithin&lt;/strong&gt; or &lt;strong&gt;publishedFrom / publishedTo&lt;/strong&gt;: the date window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;includeBuyerHistory&lt;/strong&gt;: for each tender, the buyer's past awards in the same sector, likely incumbent, top winners and average number of bids. This is charged separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aiEnrichment&lt;/strong&gt; with &lt;strong&gt;companyProfile&lt;/strong&gt;: translation into your language, a short summary, key requirements and a 0 to 100 fit score. Also charged separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onlyNew&lt;/strong&gt; with &lt;strong&gt;monitorId&lt;/strong&gt;: for scheduled runs, return only notices you haven't received before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;maxResults&lt;/strong&gt;: the cap. I used 40.&lt;/li&gt;
&lt;/ul&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%2Fuvdij3mzsrfz1w99cjuo.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%2Fuvdij3mzsrfz1w99cjuo.png" alt="Input schema of the EU Tenders Scraper" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/flarewow/eu-tenders-ted-scraper" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. You need an Apify account; the free plan is enough for a test.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Keywords&lt;/strong&gt;, type your search. I used &lt;code&gt;cybersecurity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under notice stages keep &lt;strong&gt;Tenders&lt;/strong&gt;. Switch on &lt;strong&gt;Only open tenders&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Published within&lt;/strong&gt; to a month and &lt;strong&gt;Max results&lt;/strong&gt; to 40.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;. My run finished in under 5 seconds.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Output&lt;/strong&gt; tab. Export to CSV, Excel or JSON, or send it straight to Google Sheets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the table is too wide, the export dialog lets you pick columns. I usually keep title, buyer, country, value, deadline and the two links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the actor and load the results:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&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_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;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;cybersecurity&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;noticeStages&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;competition&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;publishedWithin&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;month&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;onlyOpen&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;maxResults&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="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;flarewow/eu-tenders-ted-scraper&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean&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;items&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cols&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;noticeId&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;buyerName&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;buyerCountry&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;cpvLabel&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;estimatedValueEur&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;daysToDeadline&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;shortlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daysToDeadline&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;reset_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drop&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="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;shortlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&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="nf"&gt;to_string&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;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;buyerCountry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;value_counts&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;Median value (EUR):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;estimatedValueEur&lt;/span&gt;&lt;span class="sh"&gt;"&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;shortlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ted_cyber_open.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the token in an environment variable, not in the script. On my 18 rows &lt;code&gt;value_counts()&lt;/code&gt; printed BE 7, IT 3, PL 2 and six countries with 1, and the median came out at 459036.0. pandas skips the 7 empty values when it computes the median.&lt;/p&gt;

&lt;p&gt;One small thing: &lt;code&gt;estimatedValueEur&lt;/code&gt; is already converted with ECB reference rates, so you can sort Polish and Czech notices next to euro ones without doing the maths yourself. Use &lt;code&gt;currency&lt;/code&gt; and &lt;code&gt;estimatedValue&lt;/code&gt; if you need the original figure.&lt;/p&gt;

&lt;p&gt;If you bid on single lots, set &lt;code&gt;"splitLots": True&lt;/code&gt;. Each lot becomes its own row with its own value. Each row then counts as one notice for billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;For a daily alert, set &lt;code&gt;onlyNew&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; and give the search a &lt;code&gt;monitorId&lt;/code&gt; such as &lt;code&gt;cyber-eu&lt;/code&gt;. Then create a schedule in Apify Console (Schedules, daily at 7:00, for example). Each run returns only notices that weren't delivered before, and you aren't charged twice for the same one.&lt;/p&gt;

&lt;p&gt;From there you can push results to Google Sheets, Slack, Make, Zapier, n8n or any webhook. I send the CSV to a sheet and let a filter view do the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The actor uses pay-per-event pricing. At the time of writing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free plan&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Notice&lt;/td&gt;
&lt;td&gt;$6.00 per 1,000&lt;/td&gt;
&lt;td&gt;$4.50 per 1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buyer intelligence&lt;/td&gt;
&lt;td&gt;$0.020 per tender&lt;/td&gt;
&lt;td&gt;$0.015 per tender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI analysis&lt;/td&gt;
&lt;td&gt;$0.030 per notice&lt;/td&gt;
&lt;td&gt;$0.020 per notice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My 18 notices came to $0.108 at the free-plan rate. Tender to award linking and EUR conversion are free. Check the actor page for current prices before a big job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;TED is official EU open data, published for re-use, and the actor reads it through the official TED API. That said, notices can contain the name, email or phone of a contact person at the buyer. Treat those as personal data under GDPR. Use them to respond to the tender, don't build marketing lists from them, and leave them out of anything you publish. In the screenshot above I left the contact fields out.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>opendata</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to scrape OLX.pl apartment listings with price per m into Python and pandas</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Wed, 28 Oct 2026 08:25:15 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-olxpl-apartment-listings-with-price-per-m2-into-python-and-pandas-4ia3</link>
      <guid>https://dev.to/norbertso/how-to-scrape-olxpl-apartment-listings-with-price-per-m2-into-python-and-pandas-4ia3</guid>
      <description>&lt;p&gt;Polish property portals don't offer a public export button, and OLX.pl is one of the biggest sources of apartment listings in the country. If you want asking prices per square metre for a district, or a daily feed of fresh listings for an agency CRM, you need the data in a table first.&lt;/p&gt;

&lt;p&gt;This guide uses the &lt;a href="https://apify.com/flarewow/olx-nieruchomosci-pl" rel="noopener noreferrer"&gt;OLX Real Estate Scraper&lt;/a&gt; on Apify. I ran it while writing this post with a small search: 2-room apartments for sale in Kraków, capped at 50 listings. The run finished in 5.5 seconds. Every number below comes from that dataset.&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%2Fn3pgnq7fy2x7a9vgdn3b.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%2Fn3pgnq7fy2x7a9vgdn3b.png" alt="OLX real estate data from Kraków in a table" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get per listing
&lt;/h2&gt;

&lt;p&gt;One listing becomes one row. These are the fields I got back:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example from the run&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2-room flat ad title&lt;/td&gt;
&lt;td&gt;As written by the advertiser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;607034, PLN&lt;/td&gt;
&lt;td&gt;Asking price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pricePerM2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16147.0&lt;/td&gt;
&lt;td&gt;Calculated by the scraper from price and area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;areaM2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40.0&lt;/td&gt;
&lt;td&gt;Floor area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rooms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"2 pokoje"&lt;/td&gt;
&lt;td&gt;Polish label from OLX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;floor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"5"&lt;/td&gt;
&lt;td&gt;Can be null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;market&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pierwotny / Wtórny&lt;/td&gt;
&lt;td&gt;Primary (developer) or secondary market&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;buildingType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blok, Apartamentowiec, Kamienica&lt;/td&gt;
&lt;td&gt;Block, apartment building, tenement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;furnished&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tak / Nie&lt;/td&gt;
&lt;td&gt;Yes / No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isBusiness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;Agency or developer vs private owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sellerName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(empty in my run)&lt;/td&gt;
&lt;td&gt;Treat as personal data, see below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;city&lt;/code&gt;, &lt;code&gt;district&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Kraków, Podgórze, Małopolskie&lt;/td&gt;
&lt;td&gt;District was missing on 3 of 50 rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;lat&lt;/code&gt;, &lt;code&gt;lon&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;50.09, 19.93&lt;/td&gt;
&lt;td&gt;Map coordinates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createdTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ISO timestamp&lt;/td&gt;
&lt;td&gt;When the ad was posted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;photos&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Description comes as HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In monitoring mode two extra fields appear: &lt;code&gt;changeType&lt;/code&gt; (new listing or price change) and &lt;code&gt;previousPrice&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the 50 Kraków listings looked like
&lt;/h2&gt;

&lt;p&gt;It's a small sample from one morning, so don't read too much into it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Median asking price: 607,034 PLN. Cheapest 420,000, most expensive 1,210,023.&lt;/li&gt;
&lt;li&gt;Median price per m²: 16,147 PLN. The range was wide, from 11,598 to 27,900.&lt;/li&gt;
&lt;li&gt;Area: 30 to 69 m², median 40.&lt;/li&gt;
&lt;li&gt;22 listings were primary market and 28 secondary. Primary flats had a median of 16,650 PLN/m², secondary ones 15,040.&lt;/li&gt;
&lt;li&gt;43 of 50 ads came from businesses. Only 7 were private owners.&lt;/li&gt;
&lt;li&gt;Podgórze had the most listings (9), then Prądnik Biały (7).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That 43 to 7 split is something you only notice once the data is in a dataframe. If you build lead lists for an agency, the &lt;code&gt;ownerType&lt;/code&gt; filter matters a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main input options
&lt;/h2&gt;

&lt;p&gt;The input form is in Polish, because the actor was built for Polish agencies. The values you will touch most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;subcategory&lt;/code&gt;: &lt;code&gt;mieszkania&lt;/code&gt; (apartments), &lt;code&gt;domy&lt;/code&gt; (houses), &lt;code&gt;dzialki&lt;/code&gt; (plots), &lt;code&gt;biura-lokale&lt;/code&gt; (offices and retail), &lt;code&gt;garaze-parkingi&lt;/code&gt; (garages), &lt;code&gt;stancje-pokoje&lt;/code&gt; (rooms).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transaction&lt;/code&gt;: &lt;code&gt;sprzedaz&lt;/code&gt; (sale), &lt;code&gt;wynajem&lt;/code&gt; (rent) or &lt;code&gt;zamiana&lt;/code&gt; (swap).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;city&lt;/code&gt; plus &lt;code&gt;radiusKm&lt;/code&gt; (0, 5, 10, 15, 30, 50, 75 or 100). City Kraków with 30 km picks up the surrounding towns too, which helps for houses and plots.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;district&lt;/code&gt;: optional, narrows to one district.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;priceMin&lt;/code&gt;, &lt;code&gt;priceMax&lt;/code&gt;, &lt;code&gt;areaMin&lt;/code&gt;, &lt;code&gt;areaMax&lt;/code&gt; and for plots &lt;code&gt;plotAreaMin&lt;/code&gt;, &lt;code&gt;plotAreaMax&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rooms&lt;/code&gt;: list with &lt;code&gt;one&lt;/code&gt;, &lt;code&gt;two&lt;/code&gt;, &lt;code&gt;three&lt;/code&gt;, &lt;code&gt;four&lt;/code&gt; (four means 4 or more).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buildingType&lt;/code&gt;, &lt;code&gt;floor&lt;/code&gt;, &lt;code&gt;market&lt;/code&gt; (&lt;code&gt;primary&lt;/code&gt; / &lt;code&gt;secondary&lt;/code&gt;), &lt;code&gt;furniture&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ownerType&lt;/code&gt;: &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;business&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postedWithin&lt;/code&gt;: &lt;code&gt;24h&lt;/code&gt;, &lt;code&gt;7d&lt;/code&gt;, &lt;code&gt;30d&lt;/code&gt; or &lt;code&gt;all&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxItems&lt;/code&gt;: a cost cap per run. Default is 500.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;searchUrls&lt;/code&gt;: paste search links copied from OLX.pl. They override every filter above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/flarewow/olx-nieruchomosci-pl" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click Try for free. You need a free Apify account.&lt;/li&gt;
&lt;li&gt;Pick the category and transaction type. I used apartments for sale.&lt;/li&gt;
&lt;li&gt;Type the city and choose room count. Set Max items to something small for the first try, I used 50.&lt;/li&gt;
&lt;li&gt;Click Start. My run took a few seconds.&lt;/li&gt;
&lt;li&gt;Open the Output tab and export to CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&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%2Fvadpwmn7t1wsg6mclead.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%2Fvadpwmn7t1wsg6mclead.png" alt="The input form with filters for category, city and price" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you already have a search open on OLX with all the filters you like, copy its URL into the advanced &lt;code&gt;searchUrls&lt;/code&gt; field. Faster than rebuilding it field by field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the actor and load the results. Your API token is under Settings &amp;gt; API &amp;amp; Integrations in the Apify Console. Keep it in an environment variable.&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&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_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;subcategory&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;mieszkania&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;sprzedaz&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;city&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;Kraków&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;two&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;maxItems&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="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;flarewow/olx-nieruchomosci-pl&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;sellerName&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;description&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;photos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&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="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;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;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;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;median price/m2:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;pricePerM2&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market&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;pricePerM2&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;round&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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;district&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;value_counts&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;krakow_2_rooms.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my dataset this printed 50 listings, a median of 16,147 PLN/m² and the primary vs secondary split shown earlier. I drop &lt;code&gt;sellerName&lt;/code&gt; right after loading because I don't need it for price analysis and I'd rather not store it.&lt;/p&gt;

&lt;p&gt;A quick per-district view is one line more:&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subset&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;district&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;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;district&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;agg&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;median_m2&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;pricePerM2&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;median&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;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;listings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 50 rows most districts have 2 to 4 listings, so raise &lt;code&gt;maxItems&lt;/code&gt; before you draw conclusions per district.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and monitoring new listings
&lt;/h2&gt;

&lt;p&gt;The part agencies use most is monitoring. Set &lt;code&gt;monitorNew&lt;/code&gt; to true and the actor remembers which listings it has already seen for that search. The first run builds the baseline. After that, each run returns only listings that are new since the last one. With &lt;code&gt;detectPriceChanges&lt;/code&gt; on (the default) it also flags price drops and rises on listings it already knows, with the old price in &lt;code&gt;previousPrice&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make that useful, add a schedule in the Apify Console (Schedules tab): hourly for a busy city, daily for a small town. From there the results can go to Google Sheets, Slack, Make, Zapier, n8n or a webhook. The actor also works through the Apify MCP server, so you can call it from Claude, ChatGPT or Cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The actor is pay per event. Current prices per 1,000 scraped listings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Apify plan&lt;/th&gt;
&lt;th&gt;Per 1,000 listings&lt;/th&gt;
&lt;th&gt;Per 1,000 new listings (monitoring)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$8&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bronze&lt;/td&gt;
&lt;td&gt;$6&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silver&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gold and above&lt;/td&gt;
&lt;td&gt;$4&lt;/td&gt;
&lt;td&gt;$8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Price changes in monitoring mode cost the same as a scraped listing. There is also a $0.05 charge per run start, and the first 100 listings are free for a new user. So a 50-listing test like mine fits inside the free allowance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The scraper reads public listing data through the same JSON API the OLX app uses. Some ads come from private owners, and fields like &lt;code&gt;sellerName&lt;/code&gt; or the free-text &lt;code&gt;description&lt;/code&gt; can contain personal data. Under GDPR (RODO in Poland) you need a legal basis to store and process that. For price research, drop those columns right away, as in the code above. For lead generation, the &lt;code&gt;ownerType: business&lt;/code&gt; filter keeps you to agencies and developers. Keep &lt;code&gt;maxItems&lt;/code&gt; reasonable and don't hammer the site with schedules every few minutes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>realestate</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to track Allegro prices from every seller by EAN barcode with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Mon, 26 Oct 2026 08:30:45 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-track-allegro-prices-from-every-seller-by-ean-barcode-with-python-25k8</link>
      <guid>https://dev.to/norbertso/how-to-track-allegro-prices-from-every-seller-by-ean-barcode-with-python-25k8</guid>
      <description>&lt;p&gt;If you sell on Allegro or buy stock for resale in Poland, the question you ask every week is simple: who else sells this exact product, and at what price? Searching by product name gets messy fast. Searching by EAN barcode is cleaner, because one barcode should point to one product card on Allegro, and that card lists every seller.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to pull that seller list for a batch of EAN codes with the &lt;a href="https://apify.com/liveclaude/allegro-ean-price-tracker" rel="noopener noreferrer"&gt;Allegro EAN Price Tracker&lt;/a&gt; on Apify. First without code, then from Python with pandas. Every number below comes from one test run I did on 4 barcodes.&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%2Fbjj4lfd1yle3isgacyf4.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%2Fbjj4lfd1yle3isgacyf4.png" alt="Allegro EAN Price Tracker output table" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the test run returned
&lt;/h2&gt;

&lt;p&gt;Input: 4 EAN codes, Allegro.pl, up to 10 offers per product, cheapest first, one row per offer.&lt;/p&gt;

&lt;p&gt;The run took 241 seconds and returned 24 rows: 23 seller offers plus 1 row for a barcode Allegro did not know.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;EAN&lt;/th&gt;
&lt;th&gt;Product on Allegro&lt;/th&gt;
&lt;th&gt;Offers&lt;/th&gt;
&lt;th&gt;Lowest&lt;/th&gt;
&lt;th&gt;Highest&lt;/th&gt;
&lt;th&gt;Average&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;195949036842&lt;/td&gt;
&lt;td&gt;Apple iPhone 15 6 GB / 256 GB 5G, black&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1,400.00 PLN&lt;/td&gt;
&lt;td&gt;3,173.90 PLN&lt;/td&gt;
&lt;td&gt;2,040.49 PLN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5702017584072&lt;/td&gt;
&lt;td&gt;LEGO Technic 42173 Koenigsegg Jesko Absolut&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;175.67 PLN&lt;/td&gt;
&lt;td&gt;419.90 PLN&lt;/td&gt;
&lt;td&gt;265.19 PLN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5449000000996&lt;/td&gt;
&lt;td&gt;5 l PET bottle listing&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4.00 PLN&lt;/td&gt;
&lt;td&gt;4.00 PLN&lt;/td&gt;
&lt;td&gt;4.00 PLN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5900084231472&lt;/td&gt;
&lt;td&gt;not found&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things stood out. The iPhone spread is huge, more than 1,700 PLN between the cheapest and the most expensive offer for the same product card. Of those 10 iPhone offers, 9 had Allegro Smart! delivery, 6 came from Super Sellers and 3 were promoted placements. The most expensive one carried the Lowest Price Guarantee badge and a 12-day delivery time.&lt;/p&gt;

&lt;p&gt;The third barcode is a good warning. I used the example code from the actor's input description and it matched a listing for a 5-litre PET bottle. Always read &lt;code&gt;productName&lt;/code&gt; before you trust a row.&lt;/p&gt;

&lt;h2&gt;
  
  
  What data you get
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;outputFormat: "offers"&lt;/code&gt; you get one flat row per seller offer. These are the fields I found most useful:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The barcode you sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;productName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the Allegro product card the EAN matched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The seller's own offer title&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt; / &lt;code&gt;priceWithDelivery&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Item price and price including the cheapest delivery, in PLN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deliveryTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delivery text as Allegro shows it, e.g. "dostawa jutro"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;smart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offer has Allegro Smart! delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;superSeller&lt;/code&gt; / &lt;code&gt;sellerRating&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Super Seller badge and positive rating in percent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sellerIsCompany&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether the seller account is a business&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;promoted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sponsored placement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowestPriceGuarantee&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offer has the "Gwarancja najniższej ceny" badge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;productRating&lt;/code&gt; / &lt;code&gt;productReviewsCount&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Product card rating and review count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Link to the offer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Barcodes that match nothing come back as a row with &lt;code&gt;found: false&lt;/code&gt; and a &lt;code&gt;note&lt;/code&gt;. In my run that note read "No Allegro product with this EAN (Allegro only showed similar products)."&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;outputFormat: "products"&lt;/code&gt; (the default) you get one record per EAN instead, with &lt;code&gt;lowestPrice&lt;/code&gt;, &lt;code&gt;highestPrice&lt;/code&gt;, &lt;code&gt;avgPrice&lt;/code&gt;, &lt;code&gt;offerCount&lt;/code&gt; and the offers nested in an &lt;code&gt;offers[]&lt;/code&gt; array. Use that when you only care about the price range per product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/allegro-ean-price-tracker" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Paste your barcodes into &lt;strong&gt;EAN / GTIN codes&lt;/strong&gt;, one per line.&lt;/li&gt;
&lt;li&gt;Pick the marketplace. Allegro.pl is the default, Allegro.cz and Allegro.sk are also supported.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Top N offers per product&lt;/strong&gt;. The default is 20, the maximum is 500.&lt;/li&gt;
&lt;li&gt;Choose the output format and click &lt;strong&gt;Start&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;When the run finishes, export the dataset as Excel, CSV or JSON.&lt;/li&gt;
&lt;/ol&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%2Ffmgrr0n8ts4xai04c8yg.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%2Ffmgrr0n8ts4xai04c8yg.png" alt="Input form of the Allegro EAN Price Tracker" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main input options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;topNOffers&lt;/code&gt;: how many seller offers per product.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sortBy&lt;/code&gt;: &lt;code&gt;price-asc&lt;/code&gt;, &lt;code&gt;price-desc&lt;/code&gt; or &lt;code&gt;rating-desc&lt;/code&gt;. In my run the dataset rows did not come out in price order, so sort again on your side.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;smartFilter&lt;/code&gt;: &lt;code&gt;all&lt;/code&gt;, &lt;code&gt;smart-only&lt;/code&gt; or &lt;code&gt;exclude-smart&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includePromoted&lt;/code&gt;: set to &lt;code&gt;false&lt;/code&gt; to drop sponsored offers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;detectHiddenDiscounts&lt;/code&gt;: for offers with the Lowest Price Guarantee badge, it adds the price the seller actually set (&lt;code&gt;realSellerPrice&lt;/code&gt;) and how much Allegro covers. This one is billed per checked offer, so leave it off unless you need it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onlyChanges&lt;/code&gt; with &lt;code&gt;monitorId&lt;/code&gt;: monitoring mode, explained below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the actor and load the results into a DataFrame. You need your Apify API token from the Integrations page in the Apify Console. Keep it in an environment variable, not in the script.&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&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_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;eans&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;195949036842&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;5702017584072&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;5449000000996&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;5900084231472&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;marketplace&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;pl&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;topNOffers&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;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;price-asc&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;smartFilter&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;all&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;includePromoted&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;outputFormat&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;offers&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;liveclaude/allegro-ean-price-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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean&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;items&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Barcodes Allegro did not recognise
&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;found&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;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;found&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="bp"&gt;False&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;Not found:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;offers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;notna&lt;/span&gt;&lt;span class="p"&gt;()].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;offers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ean&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;productName&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;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;offers&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;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;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;lowest&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;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;min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;highest&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;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;max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;median&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;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;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;smart_share&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;smart&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;mean&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="nf"&gt;round&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allegro_ean_prices.csv&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;One cleanup step is worth adding. On the iPhone card, some offer titles mentioned ex-display units ("Powystawowy"), condition notes ("Stan: bardzo dobry") or even a different variant (one said 15 Plus, another 128GB). Sellers attach offers to product cards loosely, so filter titles before you compare prices:&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;noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;powystaw|stan:|używan|kondycja|plus|128gb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;offers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;offers&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;na&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;cheapest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priceWithDelivery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;head&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cheapest&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ean&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;priceWithDelivery&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;smart&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;superSeller&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adjust the pattern for your product category. For LEGO sets you will want different words than for phones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and price alerts
&lt;/h2&gt;

&lt;p&gt;For ongoing tracking, turn on &lt;code&gt;onlyChanges&lt;/code&gt; and give the watch list a &lt;code&gt;monitorId&lt;/code&gt;, for example &lt;code&gt;competitors-lego&lt;/code&gt;. Then add a schedule in Apify, say every morning at 7:00. Each run returns only new sellers and offers whose price changed since the previous run, with &lt;code&gt;previousPrice&lt;/code&gt;, &lt;code&gt;priceChange&lt;/code&gt; and &lt;code&gt;priceChangePercent&lt;/code&gt;. According to the actor's README, unchanged offers are not charged.&lt;/p&gt;

&lt;p&gt;From there you can push results to Google Sheets, Slack, email, Make, Zapier or n8n through Apify integrations, or poll the dataset from your own repricing script. Use a separate &lt;code&gt;monitorId&lt;/code&gt; per list so the change history stays clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing per 1,000 results
&lt;/h2&gt;

&lt;p&gt;The actor uses pay per event. Current prices on the Free plan, with the Business plan price in brackets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product resolved by EAN: $10 per 1,000 EANs ($7 on Business)&lt;/li&gt;
&lt;li&gt;Seller offer: $2 per 1,000 offers ($1.50 on Business)&lt;/li&gt;
&lt;li&gt;EAN not found: $1 per 1,000&lt;/li&gt;
&lt;li&gt;Hidden discount check: $10 per 1,000 checked offers, only if you enable it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So 1,000 barcodes with 10 offers each would come to roughly $10 for products plus $20 for 10,000 offers on the Free plan, before the small per-run start fee. Matched products are cached for 24 hours, which helps if you rerun the same list the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The actor reads publicly visible offer data. In my run all 23 offers came from business accounts, but Allegro also has private sellers, and a seller login can be a person's nickname. If you store or share results, drop or mask &lt;code&gt;sellerLogin&lt;/code&gt; and &lt;code&gt;sellerUrl&lt;/code&gt; unless you have a reason and a legal basis to keep them, and handle any personal data under GDPR. Keep run frequency sensible: once or twice a day is plenty for price monitoring.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>ecommerce</category>
      <category>pandas</category>
    </item>
    <item>
      <title>How to scrape Kleinanzeigen.de listings by city and radius with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Thu, 22 Oct 2026 07:31:09 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-kleinanzeigende-listings-by-city-and-radius-with-python-5b9b</link>
      <guid>https://dev.to/norbertso/how-to-scrape-kleinanzeigende-listings-by-city-and-radius-with-python-5b9b</guid>
      <description>&lt;p&gt;Kleinanzeigen.de, formerly eBay Kleinanzeigen, is Germany's biggest classifieds site. People sell bikes, cars, furniture and flats there. The site itself only shows 25 listings per page and stops after 50 pages. For a price check or a daily alert you want the data in a table. This post walks through the &lt;a href="https://apify.com/liveclaude/kleinanzeigen-scraper" rel="noopener noreferrer"&gt;Kleinanzeigen Scraper&lt;/a&gt; on Apify, first without code and then from Python.&lt;/p&gt;

&lt;p&gt;My test search: &lt;code&gt;e-bike&lt;/code&gt; within 20 km of Berlin, newest first, capped at 40 listings. Every number below is from that run on 22 October 2026.&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%2Ffrn1m238ihwm3u46jzhh.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%2Ffrn1m238ihwm3u46jzhh.png" alt="Kleinanzeigen Scraper results for e-bike listings around Berlin" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the run returned
&lt;/h2&gt;

&lt;p&gt;Kleinanzeigen reported 2,208 matching listings for that search. I only took the newest 40, which covered roughly 12 and a half hours of posts (the oldest timestamped one was from 18:54 UTC the evening before, the newest from 07:20 UTC). Expect a busy category in a big city to fill up fast.&lt;/p&gt;

&lt;p&gt;The price summary row gave me this for the 33 listings it analysed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Min&lt;/td&gt;
&lt;td&gt;2 EUR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25th percentile&lt;/td&gt;
&lt;td&gt;850 EUR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median&lt;/td&gt;
&lt;td&gt;1,899 EUR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average&lt;/td&gt;
&lt;td&gt;2,002.55 EUR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;75th percentile&lt;/td&gt;
&lt;td&gt;2,499 EUR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;6,599 EUR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negotiable (VB)&lt;/td&gt;
&lt;td&gt;12.12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price reduced&lt;/td&gt;
&lt;td&gt;3.03%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shipping offered&lt;/td&gt;
&lt;td&gt;72.73%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The top location was Lichtenberg with 16 listings, far ahead of every other district. Most of those came from shops, which brings me to the first lesson: 24 of the 40 listings were from PRO (commercial) sellers, and 4 were promoted TOP ads.&lt;/p&gt;

&lt;p&gt;The 2 EUR minimum was a luggage strap, which matched "e-bike" somewhere in its text. More on filtering that out below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields you get per listing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example from this run&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Giant Explore E+ 2 STA amber glow XL E-Bike&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;priceType&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1999, fixed&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;negotiable&lt;/code&gt; for VB listings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;originalPrice&lt;/code&gt;, &lt;code&gt;priceReduced&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Filled when the seller cut the price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;location&lt;/code&gt;, &lt;code&gt;postalCode&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Friedrichshain, 10243&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;distanceKm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Distance from your search location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postedAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2026-10-22T06:08:00Z&lt;/td&gt;
&lt;td&gt;Missing on some TOP ads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;shippingAvailable&lt;/code&gt;, &lt;code&gt;buyNow&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;"Versand möglich", "Direkt kaufen"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isTopAd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Promoted listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;sellerType&lt;/code&gt;, &lt;code&gt;isProSeller&lt;/code&gt;, &lt;code&gt;shopName&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;commercial, true, Radwelt Mitte RWM 1 GmbH&lt;/td&gt;
&lt;td&gt;Shop name only for PRO sellers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mileageKm&lt;/code&gt;, &lt;code&gt;firstRegistration&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Cars, already in search results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;livingSpaceM2&lt;/code&gt;, &lt;code&gt;rooms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Real estate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;imageUrl&lt;/code&gt;, &lt;code&gt;imageCount&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With &lt;strong&gt;Listing details&lt;/strong&gt; on, you also get the full description, all attributes (brand, model, frame size and so on), the equipment list, all photos and GPS coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/kleinanzeigen-scraper" rel="noopener noreferrer"&gt;Kleinanzeigen Scraper&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. A free Apify account is enough. You do not need a Kleinanzeigen login or your own proxies.&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;e-bike&lt;/code&gt; into &lt;strong&gt;Search terms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Location&lt;/strong&gt; to &lt;code&gt;Berlin&lt;/code&gt; and &lt;strong&gt;Radius&lt;/strong&gt; to 20 km. Postcodes like &lt;code&gt;80331&lt;/code&gt; work too.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max. listings per search&lt;/strong&gt; to 40.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;suche&lt;/code&gt; and &lt;code&gt;defekt&lt;/code&gt; to &lt;strong&gt;Exclude words&lt;/strong&gt;. That drops "looking for" posts and broken bikes, and filtered listings are free.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Price summary per search&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;. Then open the &lt;strong&gt;Listings&lt;/strong&gt; or &lt;strong&gt;Price summaries&lt;/strong&gt; view, or export CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&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%2Fjmm2io3cwwe42v5fqawj.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%2Fjmm2io3cwwe42v5fqawj.png" alt="Input form of the Kleinanzeigen Scraper on Apify" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Input options worth knowing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kleinanzeigen URLs&lt;/strong&gt;: set filters on kleinanzeigen.de in your browser and paste the link. Category-specific filters (mileage, first registration, rooms, living space, brand) stay in the URL and the scraper keeps them. Single listings, seller pages and PRO shops work as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categories&lt;/strong&gt;: limit keywords to one or more categories. Picking the e-bike category would have kept my luggage strap out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Title must include (any)&lt;/strong&gt;: keep only listings whose title has one of your words. Also a fix for the strap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Min. / Max. price&lt;/strong&gt;: a 100 EUR floor removes most accessories in one go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listing type&lt;/strong&gt;: offers, wanted ads, or both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seller type&lt;/strong&gt;: private or commercial. Useful here, since shops dominated the results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping&lt;/strong&gt; and &lt;strong&gt;Only "Direkt kaufen"&lt;/strong&gt;: shipping only, pickup only, or buy-now listings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Posted within (hours)&lt;/strong&gt;: for example &lt;code&gt;24&lt;/code&gt;. The search stops once it reaches older listings, so daily runs stay cheap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only price-reduced listings&lt;/strong&gt;: listings where the seller crossed out an older price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include TOP ads&lt;/strong&gt;: switch off for organic results only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get more than 1,250 results&lt;/strong&gt;: on by default. Big searches are split into price ranges to get past the 50 page limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sellers to track&lt;/strong&gt;: user IDs, seller links or PRO shop names, with their profile and active listings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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;YOUR_APIFY_TOKEN&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_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;searchQueries&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;e-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;location&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;Berlin&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;radiusKm&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;maxItemsPerSearch&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;excludeKeywords&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;suche&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;defekt&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;priceSummary&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;liveclaude/kleinanzeigen-scraper&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;recordType&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;listing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;recordType&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;price-summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# shops carry sellerType "commercial"; other listings have no shop attached
&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;seller&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;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;sellerType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no shop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bikes&lt;/span&gt; &lt;span class="o"&gt;=&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;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;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;gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&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;bikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seller&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="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&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;median&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;min&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;max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="n"&gt;bikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ebikes_berlin.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;radiusKm&lt;/code&gt; is a string. The input form offers fixed values (0, 1, 2, 3, 5, 10, 15, 20, 30, 50, 100, 150, 200).&lt;/p&gt;

&lt;p&gt;Output from my run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            count  median     min     max
seller
commercial     23  2499.0  1590.0  6599.0
no shop        12   815.0   249.0  2000.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That split tells you more than the overall median. Shops in this search started at 1,590 EUR and sat around 2,499 EUR. Listings without a shop had a median of 815 EUR. If you are hunting for a used e-bike, set &lt;strong&gt;Seller type&lt;/strong&gt; to private and the overall 1,899 EUR median stops being misleading.&lt;/p&gt;

&lt;p&gt;Two listings were below 100 EUR and the filter dropped them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and alerts
&lt;/h2&gt;

&lt;p&gt;The scraper has a monitoring mode built for alerts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save your input as a task.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Only new listings (monitoring)&lt;/strong&gt;. Later runs return only listings you have not seen, plus price drops.&lt;/li&gt;
&lt;li&gt;Keep &lt;strong&gt;Detect sold / reserved / deleted listings&lt;/strong&gt; on. Listings that vanish are reported as &lt;code&gt;sold&lt;/code&gt;, &lt;code&gt;reserved&lt;/code&gt; or &lt;code&gt;removed&lt;/code&gt;, with &lt;code&gt;daysOnMarket&lt;/code&gt;. Every record has &lt;code&gt;listingStatus&lt;/code&gt; so you can route them.&lt;/li&gt;
&lt;li&gt;Combine with &lt;strong&gt;Posted within (hours)&lt;/strong&gt; set to 2 or 24 and schedule hourly or daily.&lt;/li&gt;
&lt;li&gt;Give the task a &lt;strong&gt;Monitor name&lt;/strong&gt; like &lt;code&gt;ebikes-berlin&lt;/code&gt; if you run several monitors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For searches over 2,000 results, sold detection and older price drops only work after you narrow the search with a location, category or price range. My 2,208 result query would need a price band for that.&lt;/p&gt;

&lt;p&gt;Results can go to Google Sheets, Slack, Make, Zapier, n8n or any webhook, or you can call the scraper from an AI agent through the Apify MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing per 1,000 results
&lt;/h2&gt;

&lt;p&gt;Pay per event. Current prices per 1,000 events:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Starter&lt;/th&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Listing&lt;/td&gt;
&lt;td&gt;$1.30&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;td&gt;$1.05&lt;/td&gt;
&lt;td&gt;$0.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listing details (optional)&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;td&gt;$1.10&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seller / shop profile&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;td&gt;$1.60&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A price summary row costs about one cent and each run has a start fee of $0.00005. Filtered listings, already seen listings in monitoring mode and failed pages are free. At Free plan prices, my 40 listing run with one summary works out to roughly six cents. You can set a maximum cost per run and it stops there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;Many Kleinanzeigen sellers are private people. The scraper does not collect phone numbers or e-mail addresses of private sellers, and Kleinanzeigen hides them anyway. PRO shops publish business contact details on their shop pages, and only those end up in seller records. If you turn on seller profiles, you may get display names of private sellers too; leave them out of anything you publish or share, and keep in mind that GDPR covers storing them. Kleinanzeigen's terms apply to what you do with the data. Use small, targeted searches with filters and you will collect less data and pay less.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kleinanzeigen</category>
      <category>python</category>
      <category>webscraping</category>
      <category>germany</category>
    </item>
    <item>
      <title>How to compare eBay prices across countries with Python (UK vs Germany example)</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Thu, 22 Oct 2026 07:30:18 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-compare-ebay-prices-across-countries-with-python-uk-vs-germany-example-bli</link>
      <guid>https://dev.to/norbertso/how-to-compare-ebay-prices-across-countries-with-python-uk-vs-germany-example-bli</guid>
      <description>&lt;p&gt;Is a used Steam Deck OLED cheaper on ebay.de or on ebay.co.uk? I wanted a quick answer with real listings instead of scrolling two sites and converting currencies in my head. This post shows how I got it with the &lt;a href="https://apify.com/liveclaude/ebay-scraper" rel="noopener noreferrer"&gt;eBay Scraper&lt;/a&gt; on Apify: first with no code, then with a short Python script that loads everything into pandas.&lt;/p&gt;

&lt;p&gt;All numbers below come from one run on 22 October 2026: the query &lt;code&gt;steam deck oled&lt;/code&gt; on two sites, capped at 20 listings per site.&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%2Ffrg07f30dgl9npa1mld7.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%2Ffrg07f30dgl9npa1mld7.png" alt="eBay Scraper results table for steam deck oled on ebay.de and ebay.co.uk" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the run returned
&lt;/h2&gt;

&lt;p&gt;The run saved 24 listings: 18 from ebay.de and 6 from ebay.co.uk. The UK search came back thinner than the German one, so treat the UK side as a small sample. On top of the listings, the scraper added two price summary rows (one per site) and one site comparison row.&lt;/p&gt;

&lt;p&gt;The summary rows did the currency work for me:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Listings&lt;/th&gt;
&lt;th&gt;Min&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;Max&lt;/th&gt;
&lt;th&gt;Median in USD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ebay.de&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;441 EUR&lt;/td&gt;
&lt;td&gt;640 EUR&lt;/td&gt;
&lt;td&gt;919.95 EUR&lt;/td&gt;
&lt;td&gt;718.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ebay.co.uk&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;419.99 GBP&lt;/td&gt;
&lt;td&gt;550 GBP&lt;/td&gt;
&lt;td&gt;618.70 GBP&lt;/td&gt;
&lt;td&gt;737.09&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The comparison row marked &lt;code&gt;de&lt;/code&gt; as the cheaper site, with a median spread of 18.11 USD. That gap is small. Condition matters more than the country here, as the pandas section shows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fields you get per listing
&lt;/h2&gt;

&lt;p&gt;These are the columns I used most. Every listing record has them, though some are empty when eBay does not show the value.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example from this run&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Valve Steam Deck OLED 512 GB Handheld Spielkonsole&lt;/td&gt;
&lt;td&gt;As shown on eBay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;450, EUR&lt;/td&gt;
&lt;td&gt;Local currency of the site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priceUsd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;505.53&lt;/td&gt;
&lt;td&gt;Converted, handy for cross-site work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;shippingCost&lt;/code&gt;, &lt;code&gt;totalPrice&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;7.69&lt;/td&gt;
&lt;td&gt;Total is price plus shipping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;condition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gebraucht&lt;/td&gt;
&lt;td&gt;In the site's language&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;buyingFormat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;auction / buy-it-now&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bids&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Auctions only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;watchers&lt;/code&gt;, &lt;code&gt;soldCount&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;17, 591&lt;/td&gt;
&lt;td&gt;Only when eBay displays them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sellerType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;private / business&lt;/td&gt;
&lt;td&gt;Shown on sites like ebay.de&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;sellerFeedbackPercent&lt;/code&gt;, &lt;code&gt;sellerFeedbackCount&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;100, 1090&lt;/td&gt;
&lt;td&gt;Seller reputation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;marketplace&lt;/code&gt;, &lt;code&gt;site&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ebay.de, de&lt;/td&gt;
&lt;td&gt;Which eBay site it came from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;epid&lt;/code&gt;, &lt;code&gt;imageUrl&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Product ID, photo, link&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every record also has &lt;code&gt;recordType&lt;/code&gt;: &lt;code&gt;item&lt;/code&gt; for a listing, &lt;code&gt;price-summary&lt;/code&gt; for the per-site stats, &lt;code&gt;site-comparison&lt;/code&gt; for the cross-site row. That one field makes filtering easy later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/ebay-scraper" rel="noopener noreferrer"&gt;eBay Scraper page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. You need a free Apify account. No eBay account is involved.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Search queries&lt;/strong&gt;, type &lt;code&gt;steam deck oled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;eBay sites&lt;/strong&gt;, pick United Kingdom and Germany. There are 15 sites in total, and a switch to search all of them at once.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max listings per search&lt;/strong&gt; to 20 while you test.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;case&lt;/code&gt;, &lt;code&gt;skin&lt;/code&gt; and &lt;code&gt;hülle&lt;/code&gt; to &lt;strong&gt;Exclude words in title&lt;/strong&gt;. Accessory listings drop out and you are not charged for them.&lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;Price summary per search&lt;/strong&gt; on and turn on &lt;strong&gt;Compare prices between eBay sites&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;. My run finished in under a minute.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Price summaries&lt;/strong&gt; view for the numbers above, or export everything as CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&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%2Fiy2lbxog36pe8k9dgzj9.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%2Fiy2lbxog36pe8k9dgzj9.png" alt="Input form of the eBay Scraper on Apify" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Input options worth knowing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;eBay URLs&lt;/strong&gt;: set filters on eBay in your browser and paste the link. Search pages, categories (&lt;code&gt;/b/...&lt;/code&gt;), stores, seller pages and single listings (&lt;code&gt;/itm/...&lt;/code&gt;) all work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters in the form&lt;/strong&gt;: category ID, min and max price, condition (new, open box, refurbished, used, for parts), auction or Buy It Now, Best Offer only, free shipping only, item location, seller type, minimum seller feedback, sort order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Title must contain&lt;/strong&gt;: the opposite of exclude words. One UK result was titled "Valve Steam Deck 512GB Handheld Console - Black" with no "OLED" in it. Adding &lt;code&gt;oled&lt;/code&gt; here would have removed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listing details&lt;/strong&gt;: opens each listing and adds item specifics, brand, MPN, EAN/UPC, all photos and returns info. Costs extra, so only switch it on when you need those fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAN / UPC codes&lt;/strong&gt;: search by barcode instead of keywords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sellers&lt;/strong&gt;: give usernames to get a profile, active listings and recent buyer feedback with prices paid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only new listings&lt;/strong&gt;: monitoring mode, covered below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go past eBay's 10,000 result limit&lt;/strong&gt;: big searches get split into price ranges automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run this. Put your own API token from the Apify console in place of the placeholder.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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;YOUR_APIFY_TOKEN&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_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;searchQueries&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;steam deck oled&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;sites&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;uk&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;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;maxItemsPerSearch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;excludeKeywords&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;case&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;skin&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;hülle&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;priceSummary&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;compareSites&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;liveclaude/ebay-scraper&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;recordType&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;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;summaries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;recordType&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;price-summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;by_condition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marketplace&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;condition&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="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&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;median&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;by_condition&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;listings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marketplace&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;totalPrice&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# keep seller usernames out of files you share
&lt;/span&gt;&lt;span class="n"&gt;listings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;sellerName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&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="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steam_deck_ebay.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first print gave me this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                             count   median
marketplace condition
ebay.co.uk  Pre-owned            6  549.995
ebay.de     Gebraucht           11  599.900
            Neu                  4  734.475
            Neu (Sonstige)       3  630.000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So ebay.de had a mix of 11 used and 7 new or "new other" units, while all 6 UK listings were pre-owned. The German overall median of 640 EUR is pulled up by those new units at a median of 734.48 EUR. If you only care about used devices, the fair comparison is 599.90 EUR against 550 GBP.&lt;/p&gt;

&lt;p&gt;Shipping changes the picture a bit too. Median &lt;code&gt;totalPrice&lt;/code&gt; was 650.69 EUR on ebay.de and 562.47 GBP on ebay.co.uk. Two UK sellers charged 20 GBP for delivery, which is a lot on a 550 GBP item.&lt;/p&gt;

&lt;p&gt;Auctions are worth a separate look. Germany had 4 of them. Two had bids (15 bids at 441 EUR, 21 bids at 505 EUR) and two had none yet. Those prices will move until the auction ends, so do not mix them into a "typical price" without checking &lt;code&gt;timeLeft&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling and integrations
&lt;/h2&gt;

&lt;p&gt;A one-off run answers one question. For price tracking, schedule it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save your input as a task in the Apify console.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Only new listings (monitoring)&lt;/strong&gt;. Each run then returns only listings it has not seen, plus price drops.&lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;Track final prices of ended listings&lt;/strong&gt; on. When a tracked listing ends, the scraper opens its public page and records the final price, bids, end date and whether it sold. Each record carries &lt;code&gt;listingStatusChange&lt;/code&gt; with &lt;code&gt;new&lt;/code&gt;, &lt;code&gt;price-drop&lt;/code&gt;, &lt;code&gt;sold&lt;/code&gt;, &lt;code&gt;ended-unsold&lt;/code&gt; or &lt;code&gt;removed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a schedule, for example daily at 07:00.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This matters because since July 2026 eBay shows sold and completed searches only to signed-in users. The scraper does not log in, so monitoring is how you collect real sale prices over time.&lt;/p&gt;

&lt;p&gt;For delivery, connect the dataset to Google Sheets, Make, Zapier, n8n or a webhook. You can also call &lt;code&gt;liveclaude/ebay-scraper&lt;/code&gt; from an AI agent through the Apify MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing per 1,000 results
&lt;/h2&gt;

&lt;p&gt;The scraper uses pay per event pricing. Current prices per 1,000 events:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Starter&lt;/th&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Listing&lt;/td&gt;
&lt;td&gt;$3.20&lt;/td&gt;
&lt;td&gt;$2.90&lt;/td&gt;
&lt;td&gt;$2.70&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listing details (optional)&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$4.50&lt;/td&gt;
&lt;td&gt;$4.20&lt;/td&gt;
&lt;td&gt;$4.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description (optional)&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$4.50&lt;/td&gt;
&lt;td&gt;$4.20&lt;/td&gt;
&lt;td&gt;$4.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seller profile&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$4.50&lt;/td&gt;
&lt;td&gt;$4.20&lt;/td&gt;
&lt;td&gt;$4.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buyer feedback entry&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$2.20&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$1.80&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A price summary or site comparison row costs about one cent each. Listings removed by your filters, already seen listings in monitoring mode and failed pages are free. My test run cost well under 10 cents. You can set a maximum cost per run in the run options and the scraper stops there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The scraper collects publicly visible listing data and does not use an eBay account or cookies. Search results can include seller usernames, and on eBay many sellers are private people. I drop &lt;code&gt;sellerName&lt;/code&gt; before saving or sharing anything, as in the script above, and work with feedback counts and seller type instead. If you store data on individuals, GDPR applies to you, and eBay's terms apply to how you use the data. Keep request volumes sensible and only collect what you need for the question you are answering.&lt;/p&gt;

&lt;p&gt;For my question the answer was short: used units sat around 600 EUR in Germany and 550 GBP in the UK. At that gap, shipping and condition decide which listing is the better deal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ebay</category>
      <category>python</category>
      <category>webscraping</category>
      <category>pandas</category>
    </item>
    <item>
      <title>How to scrape Vinted listings and compare prices across countries with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:25:22 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-vinted-listings-and-compare-prices-across-countries-with-python-3493</link>
      <guid>https://dev.to/norbertso/how-to-scrape-vinted-listings-and-compare-prices-across-countries-with-python-3493</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This guide uses the &lt;a href="https://apify.com/liveclaude/vinted-scraper" rel="noopener noreferrer"&gt;Vinted Scraper&lt;/a&gt; on Apify. I ran it this morning with a small test: the query &lt;code&gt;levis 501&lt;/code&gt;, 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.&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%2Fwk5a3pnrp7e50px2mb6s.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%2Fwk5a3pnrp7e50px2mb6s.png" alt="Vinted listings for levis 501 exported to a table" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What data you get
&lt;/h2&gt;

&lt;p&gt;Each listing is one row. These are the fields I used most in the test run:&lt;/p&gt;

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

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the test run showed
&lt;/h2&gt;

&lt;p&gt;A few things stood out in the 50 rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Median total price (item plus buyer fee) was 26.95 EUR on both sites. The cheapest listing cost 8.05 EUR on both.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;44 listings came from private sellers and 6 from business accounts.&lt;/li&gt;
&lt;li&gt;41 of 50 had zero favourites. These were the newest listings, so that is expected.&lt;/li&gt;
&lt;li&gt;42 titles contained "501". The other 8 were other Levi's models (504, unlabeled jeans), because Vinted search is fuzzy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overlap is the practical lesson. If you want to compare real local prices, use the &lt;strong&gt;Seller countries&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/vinted-scraper" rel="noopener noreferrer"&gt;Vinted Scraper page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. You need a free Apify account.&lt;/li&gt;
&lt;li&gt;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 &lt;strong&gt;Vinted URLs&lt;/strong&gt;. Category, brand, single listing and seller wardrobe links work too.&lt;/li&gt;
&lt;li&gt;Or type keywords into &lt;strong&gt;Search queries&lt;/strong&gt;, one per line, and pick the &lt;strong&gt;Countries&lt;/strong&gt;. Each query runs in every selected country.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Max listings per search&lt;/strong&gt;. I used 25. &lt;code&gt;0&lt;/code&gt; means everything, and the actor splits the search into price ranges to get past Vinted's 960-result cap.&lt;/li&gt;
&lt;li&gt;Optional: tick &lt;strong&gt;Compare prices between countries&lt;/strong&gt; if you picked two or more countries.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt;, then export the dataset as CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&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%2Fe4hgwlew7jv1jccmv0mz.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%2Fe4hgwlew7jv1jccmv0mz.png" alt="Input form of the Vinted Scraper" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Main input options
&lt;/h3&gt;

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

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;

&lt;p&gt;Install the two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put your Apify API token in an environment variable called &lt;code&gt;APIFY_TOKEN&lt;/code&gt; (Console, Settings, API &amp;amp; Integrations). Then:&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;os&lt;/span&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&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_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;searchQueries&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;levis 501&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;countries&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;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;fr&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;maxItemsPerSearch&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;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_first&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;compareCountries&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;liveclaude/vinted-scraper&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;items&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="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="n"&gt;listings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&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;recordType&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;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;comparison&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&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;recordType&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;country-comparison&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# price summary per marketplace
&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;listings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marketplace&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;totalPriceEur&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;describe&lt;/span&gt;&lt;span class="p"&gt;()[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&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;min&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;50%&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;max&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="c1"&gt;# how many listings appear on both sites
&lt;/span&gt;&lt;span class="n"&gt;de_ids&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;listings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&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;country&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;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;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;fr_ids&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;listings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&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;country&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;fr&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;id&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;Shared 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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;de_ids&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;fr_ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# ten cheapest listings
&lt;/span&gt;&lt;span class="n"&gt;cols&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;marketplace&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;title&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;size&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;condition&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;totalPriceEur&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="nf"&gt;print&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="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalPriceEur&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;head&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="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;comparison&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;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;countries&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;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;marketplace&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;median&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;medianPriceEur&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;EUR&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vinted_levis_501.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Two small notes. The &lt;code&gt;condition&lt;/code&gt; 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 &lt;code&gt;recordType&lt;/code&gt; as shown, because the comparison row has a different shape from the listing rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling, monitoring and integrations
&lt;/h2&gt;

&lt;p&gt;The part I would use day to day is monitoring. Switch on &lt;strong&gt;Only new listings&lt;/strong&gt;, give the run a &lt;strong&gt;Monitor name&lt;/strong&gt;, 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 &lt;code&gt;listingStatus&lt;/code&gt; of &lt;code&gt;new&lt;/code&gt;, &lt;code&gt;price-drop&lt;/code&gt;, &lt;code&gt;sold&lt;/code&gt; or &lt;code&gt;removed&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;Already seen listings are not charged, so a monitor that finds 5 new items costs a fraction of a cent.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing per 1,000 results
&lt;/h2&gt;

&lt;p&gt;Pay per event, proxies included. Current prices from the actor page:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free plan&lt;/th&gt;
&lt;th&gt;Starter&lt;/th&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;Business&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Listing&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listing details (optional)&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seller profile (optional, once per seller)&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vinted</category>
      <category>python</category>
      <category>webscraping</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>How to scrape OLX.pl car listings by make, year and mileage with Python</title>
      <dc:creator>Norbert Sobala</dc:creator>
      <pubDate>Tue, 20 Oct 2026 07:29:19 +0000</pubDate>
      <link>https://dev.to/norbertso/how-to-scrape-olxpl-car-listings-by-make-year-and-mileage-with-python-46j2</link>
      <guid>https://dev.to/norbertso/how-to-scrape-olxpl-car-listings-by-make-year-and-mileage-with-python-46j2</guid>
      <description>&lt;p&gt;OLX.pl is where a large share of Poland's used cars get listed, by dealers and private owners alike. The site has good filters, but there is no export button, and comparing 200 listings by hand gets old fast. This guide uses the &lt;a href="https://apify.com/liveclaude/olx-motoryzacja-pl" rel="noopener noreferrer"&gt;OLX Car Scraper Poland&lt;/a&gt; on Apify to turn a filtered car search into a table you can open in Excel or load into pandas.&lt;/p&gt;

&lt;p&gt;My test search: Toyota Corolla, passenger cars, model year 2018 or newer, whole country, capped at 40 listings. Everything below comes from that dataset.&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%2F42d1di87qhiaxoh29a2o.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%2F42d1di87qhiaxoh29a2o.png" alt="OLX Toyota Corolla listings in a table" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get per listing
&lt;/h2&gt;

&lt;p&gt;The field names are in Polish because the actor targets the Polish market. Here is what they mean:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example from my run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Listing title and link&lt;/td&gt;
&lt;td&gt;Toyota Corolla 1.5 VVT-i 125KM Comfort&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;marka&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Make, model&lt;/td&gt;
&lt;td&gt;Toyota, Corolla&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rocznik&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Model year&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;przebiegKm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mileage in km&lt;/td&gt;
&lt;td&gt;180000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cena&lt;/code&gt;, &lt;code&gt;waluta&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Price, currency&lt;/td&gt;
&lt;td&gt;52500, PLN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;paliwo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fuel (Benzyna = petrol, CNG i Hybryda = hybrid)&lt;/td&gt;
&lt;td&gt;Benzyna&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;skrzynia&lt;/code&gt;, &lt;code&gt;naped&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Gearbox, drive&lt;/td&gt;
&lt;td&gt;Manualna, Na przednie koła&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pojemnoscCm3&lt;/code&gt;, &lt;code&gt;mocKm&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Engine size (cm³), power (hp)&lt;/td&gt;
&lt;td&gt;1490, 125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;typNadwozia&lt;/code&gt;, &lt;code&gt;kolor&lt;/code&gt;, &lt;code&gt;stan&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Body type, colour, damaged or not&lt;/td&gt;
&lt;td&gt;Sedan, Biały, Nieuszkodzony&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;krajPochodzenia&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Country of origin&lt;/td&gt;
&lt;td&gt;Polska&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;miasto&lt;/code&gt;, &lt;code&gt;wojewodztwo&lt;/code&gt;, &lt;code&gt;gpsLat&lt;/code&gt;, &lt;code&gt;gpsLon&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;City, region, coordinates&lt;/td&gt;
&lt;td&gt;Wieliczka, Małopolskie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprzedawcaTyp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Seller type (Prywatny = private, Komis / Firma = dealer)&lt;/td&gt;
&lt;td&gt;Komis / Firma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;dataDodania&lt;/code&gt;, &lt;code&gt;dataOdswiezenia&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Posted and refreshed timestamps&lt;/td&gt;
&lt;td&gt;2026-10-07T18:01:47+02:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;glowneZdjecie&lt;/code&gt;, &lt;code&gt;zdjecia&lt;/code&gt;, &lt;code&gt;opis&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Main photo, all photos, description (HTML)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is also &lt;code&gt;sprzedawcaNazwa&lt;/code&gt;, the seller's display name. For private sellers that is a person's name, so treat it carefully (more on that at the end).&lt;/p&gt;

&lt;h2&gt;
  
  
  What 40 Corollas looked like
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prices went from 36,900 PLN to 141,900 PLN, median 78,195 PLN. Median mileage was about 90,000 km.&lt;/li&gt;
&lt;li&gt;25 cars were hybrids, 11 petrol, 3 LPG and 1 diesel. All 25 hybrids were automatics, and every non-hybrid had a manual gearbox.&lt;/li&gt;
&lt;li&gt;34 listings came from dealers and 6 from private sellers.&lt;/li&gt;
&lt;li&gt;6 listings were brand new 2026 cars with 1 to 5 km on the clock, all from dealers. They pull the averages up, so filter them out if you want used-car prices.&lt;/li&gt;
&lt;li&gt;27 of the 40 cars were in the Mazowieckie region (Warsaw and around). I did not set a city, so that is just where the supply sits for this query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparing like with like is where a table beats scrolling. For model years 2018 to 2023 only, the 14 hybrids had a median price of 79,370 PLN against 58,450 PLN for the 10 petrol cars, at similar median mileage (about 120,000 vs 115,000 km). That is roughly a 21,000 PLN premium for the hybrid in this small sample. Forty rows won't settle a market question. It does show what you can answer in two minutes once the data is in a DataFrame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: no code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://apify.com/liveclaude/olx-motoryzacja-pl" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Pick a &lt;strong&gt;Kategoria&lt;/strong&gt; (category): passenger cars, motorcycles, trucks, vans, trailers or parts.&lt;/li&gt;
&lt;li&gt;Choose one or more &lt;strong&gt;Marki&lt;/strong&gt; (makes). Multi-select works, so BMW and Audi can go in one run.&lt;/li&gt;
&lt;li&gt;Type &lt;strong&gt;Modele&lt;/strong&gt; (models) if you want them, for example &lt;code&gt;Golf, Passat&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set filters: price, year, mileage, fuel, gearbox, city with a radius.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Maks. liczba ogłoszeń&lt;/strong&gt; (max listings). I used 40. Click &lt;strong&gt;Start&lt;/strong&gt; and export from the Storage tab as CSV, Excel or JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The form groups filters into sections, so you never have to build OLX URLs yourself:&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%2F5i3uecqwbsl3cxjvw24k.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%2F5i3uecqwbsl3cxjvw24k.png" alt="OLX Car Scraper input form" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Python with apify-client and pandas
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;apify-client pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grab your API token from Settings → API &amp;amp; Integrations in the Apify console and keep it in an environment variable.&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&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_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;kategoria&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;samochody-osobowe&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;marki&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;toyota&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;modele&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;Corolla&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;rocznikOd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2018&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxLiczbaOgloszen&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="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;liveclaude/olx-motoryzacja-pl&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;items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Leave out new cars and the seller name column
&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;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;przebiegKm&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;sprzedawcaNazwa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&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;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rocznik&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="mi"&gt;2023&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paliwo&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;agg&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cena&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;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="n"&gt;median_price&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;cena&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;median&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="n"&gt;median_km&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;przebiegKm&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;median&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="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cols&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;title&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;rocznik&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;przebiegKm&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;cena&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;paliwo&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;skrzynia&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;mocKm&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;miasto&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;sprzedawcaTyp&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;used&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cena&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;olx_corolla.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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;call()&lt;/code&gt; waits for the run to end. For runs with thousands of listings, &lt;code&gt;start()&lt;/code&gt; plus a webhook is cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input options worth knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Category and makes.&lt;/strong&gt; &lt;code&gt;kategoria&lt;/code&gt; takes &lt;code&gt;samochody-osobowe&lt;/code&gt;, &lt;code&gt;motocykle&lt;/code&gt;, &lt;code&gt;ciezarowe&lt;/code&gt;, &lt;code&gt;dostawcze&lt;/code&gt;, &lt;code&gt;przyczepy-naczepy&lt;/code&gt; or &lt;code&gt;czesci&lt;/code&gt;. &lt;code&gt;marki&lt;/code&gt; is a list of lowercase slugs like &lt;code&gt;bmw&lt;/code&gt; or &lt;code&gt;volkswagen&lt;/code&gt;, and &lt;code&gt;modele&lt;/code&gt; is free text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ranges.&lt;/strong&gt; &lt;code&gt;cenaOd&lt;/code&gt;/&lt;code&gt;cenaDo&lt;/code&gt; (price in PLN), &lt;code&gt;rocznikOd&lt;/code&gt;/&lt;code&gt;rocznikDo&lt;/code&gt; (year), &lt;code&gt;przebiegOd&lt;/code&gt;/&lt;code&gt;przebiegDo&lt;/code&gt; (km), &lt;code&gt;pojemnoscOd&lt;/code&gt;/&lt;code&gt;pojemnoscDo&lt;/code&gt; (cm³), &lt;code&gt;mocOd&lt;/code&gt;/&lt;code&gt;mocDo&lt;/code&gt; (hp).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specs.&lt;/strong&gt; &lt;code&gt;paliwa&lt;/code&gt; (fuel list), &lt;code&gt;skrzynia&lt;/code&gt; (&lt;code&gt;manual&lt;/code&gt; or &lt;code&gt;automatic&lt;/code&gt;), &lt;code&gt;naped&lt;/code&gt; (front, rear or one of the AWD variants), &lt;code&gt;typNadwozia&lt;/code&gt;, &lt;code&gt;kolor&lt;/code&gt;, and &lt;code&gt;stan&lt;/code&gt; to keep or drop damaged cars.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Location.&lt;/strong&gt; &lt;code&gt;miasto&lt;/code&gt; plus &lt;code&gt;promienKm&lt;/code&gt; (0 to 100 km) for a radius search around a city.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seller and freshness.&lt;/strong&gt; &lt;code&gt;typSprzedawcy&lt;/code&gt; filters to &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;business&lt;/code&gt;. &lt;code&gt;opublikowaneW&lt;/code&gt; limits to listings from the last 24 hours, 7 days or 30 days. &lt;code&gt;tylkoZeZdjeciem&lt;/code&gt; keeps only listings with photos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume.&lt;/strong&gt; &lt;code&gt;maxLiczbaOgloszen&lt;/code&gt; defaults to 500 per run. &lt;code&gt;uzyjProxy&lt;/code&gt; turns on Apify proxy rotation if you run large jobs. &lt;code&gt;advancedUrls&lt;/code&gt; accepts raw OLX search URLs when a filter is missing from the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling: alerts for new listings
&lt;/h2&gt;

&lt;p&gt;The most practical feature is &lt;code&gt;monitorNowe&lt;/code&gt;. With it on, each run returns only listings that were not there last time. &lt;code&gt;wykrywajZmianyCen&lt;/code&gt; (on by default) also flags price drops on cars you already saw. Here is an input a dealer could schedule every 30 minutes:&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;"kategoria"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"samochody-osobowe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"marki"&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="s2"&gt;"bmw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audi"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cenaDo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rocznikOd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"przebiegDo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;150000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"miasto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warszawa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"promienKm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"monitorNowe"&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;Create the schedule in the Apify console, then send results to Google Sheets, Slack, Make, Zapier or n8n, or to your own endpoint with a webhook. You can also call the actor from Claude, ChatGPT or Cursor through the &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pay per event. Per 1,000 events at the current price list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Bronze&lt;/th&gt;
&lt;th&gt;Silver&lt;/th&gt;
&lt;th&gt;Gold&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Listing scraped&lt;/td&gt;
&lt;td&gt;$6&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$4&lt;/td&gt;
&lt;td&gt;$3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New listing found (monitoring)&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;td&gt;$8&lt;/td&gt;
&lt;td&gt;$6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price change detected&lt;/td&gt;
&lt;td&gt;$6&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$4&lt;/td&gt;
&lt;td&gt;$3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each run also has a $0.05 start fee. New users get their first 100 listings free, which covers a test like mine twice over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;The actor reads public OLX listings. Private sellers are people, and their names and anything in a free-text description can be personal data under GDPR. Drop &lt;code&gt;sprzedawcaNazwa&lt;/code&gt; unless you have a lawful reason to keep it, do not build contact lists from private listings, and check that your use fits OLX.pl's terms. Keep run frequency sensible: every 30 minutes for a narrow search is plenty.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This tutorial was written with AI assistance and checked by the author. All numbers come from a real run of the scraper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>cars</category>
      <category>dataanalysis</category>
    </item>
  </channel>
</rss>
