<?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: Scrape-Smith</title>
    <description>The latest articles on DEV Community by Scrape-Smith (@scrape-smith).</description>
    <link>https://dev.to/scrape-smith</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%2F4200861%2Fb2669f66-0d2b-4f3a-a1e2-c51a265d9af2.jpg</url>
      <title>DEV Community: Scrape-Smith</title>
      <link>https://dev.to/scrape-smith</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scrape-smith"/>
    <language>en</language>
    <item>
      <title>How to Scrape Bluesky Posts, Hashtags and Followers with the Official AT Protocol API</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Fri, 23 Oct 2026 18:00:00 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-scrape-bluesky-posts-hashtags-and-followers-with-the-official-at-protocol-api-2pjo</link>
      <guid>https://dev.to/scrape-smith/how-to-scrape-bluesky-posts-hashtags-and-followers-with-the-official-at-protocol-api-2pjo</guid>
      <description>&lt;h1&gt;
  
  
  How to Scrape Bluesky Posts, Hashtags and Followers
&lt;/h1&gt;

&lt;p&gt;Bluesky has grown into a real place for tech, journalism, science and politics conversations, and unlike some other networks, it has an open, documented public API: the AT Protocol. That makes it one of the friendliest platforms for social listening, research and audience analysis.&lt;/p&gt;

&lt;p&gt;This tutorial covers what the public API offers, then uses the &lt;a href="https://apify.com/scrape-smith/bluesky-posts-profiles" rel="noopener noreferrer"&gt;Bluesky Scraper&lt;/a&gt; to export posts, profiles and followers to a spreadsheet without code, plus a short Python example.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can get from the public API
&lt;/h2&gt;

&lt;p&gt;Bluesky's AppView (&lt;code&gt;public.api.bsky.app&lt;/code&gt;) serves, without login:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Post search&lt;/strong&gt; by keyword, hashtag, phrase or author&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author feeds&lt;/strong&gt;: everything a user posted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profiles&lt;/strong&gt;: display name, bio, follower/following/post counts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Followers and follows&lt;/strong&gt; lists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User search&lt;/strong&gt; by name or bio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The actor wraps all of these, handles cursor pagination and flattens the nested records into rows with like, repost, reply and quote counts, hashtags, links and image URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Search a hashtag
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/bluesky-posts-profiles" rel="noopener noreferrer"&gt;actor&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. It's prefilled with &lt;code&gt;#buildinpublic&lt;/code&gt; and 20 items. Start it and you get rows like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;type&lt;/th&gt;
&lt;th&gt;authorHandle&lt;/th&gt;
&lt;th&gt;text&lt;/th&gt;
&lt;th&gt;likeCount&lt;/th&gt;
&lt;th&gt;createdAt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;post&lt;/td&gt;
&lt;td&gt;someone.bsky.social&lt;/td&gt;
&lt;td&gt;Build log, day 42: shipped...&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2026-10-22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Search supports Bluesky's syntax: &lt;code&gt;#tag&lt;/code&gt;, &lt;code&gt;"exact phrase"&lt;/code&gt;, &lt;code&gt;from:handle.bsky.social&lt;/code&gt;, and plain keywords. Use &lt;code&gt;sort=top&lt;/code&gt; for the most engaged posts or &lt;code&gt;latest&lt;/code&gt; for a chronological stream. &lt;code&gt;lang=en&lt;/code&gt; narrows by language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Profiles and followers
&lt;/h2&gt;

&lt;p&gt;Fill any of these fields (one handle per line):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authorFeeds&lt;/code&gt;: export a user's posts (&lt;code&gt;includeReplies&lt;/code&gt; to add replies).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;profiles&lt;/code&gt;: full profile rows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;followersOf&lt;/code&gt; / &lt;code&gt;followsOf&lt;/code&gt;: one profile row per follower or followed account.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;userSearch&lt;/code&gt;: find accounts by keyword, e.g. &lt;code&gt;data journalist&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;maxItems&lt;/code&gt; caps each query or handle, so a typo can't run up a bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Monitor a topic daily
&lt;/h2&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;datetime&lt;/span&gt;

&lt;span class="n"&gt;since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&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;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&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&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;scrape-smith/bluesky-posts-profiles&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;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="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;your brand&lt;/span&gt;&lt;span class="se"&gt;\"&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;#yourproduct&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;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;latest&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;onlyNewSince&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;since&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;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&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;type&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;post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&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="n"&gt;p&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;likeCount&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;reverse&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;likeCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authorHandle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p&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;That's a daily "top mentions" digest in about fifteen lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I need a Bluesky login?
&lt;/h2&gt;

&lt;p&gt;Usually not. Bluesky has at times restricted unauthenticated post search. If that happens, the actor can fall back to your handle plus an &lt;strong&gt;app password&lt;/strong&gt; (Settings &amp;gt; Privacy and security &amp;gt; App passwords). Never use your main password. Without one, a refused request returns a free error row explaining what to add, rather than failing silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Social listening and brand monitoring.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashtag and trend research&lt;/strong&gt; for content and community teams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Influencer discovery&lt;/strong&gt;: search users by bio, then export their follower counts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Academic research and NLP datasets.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archiving your own posts.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations and etiquette
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Search results come from Bluesky's index, which may not include every post ever made, and very old posts can be harder to reach through search than through author feeds.&lt;/li&gt;
&lt;li&gt;Respect deletions. If you store data, refresh it rather than keeping posts people deleted, and follow Bluesky's developer guidelines and applicable privacy law.&lt;/li&gt;
&lt;li&gt;Follower exports of very large accounts are large; use &lt;code&gt;maxItems&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Not affiliated with Bluesky.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;$0.0005 per post or profile row. The 20-post test is a cent; 1,000 posts is $0.50; 10,000 followers is $5. Error rows are free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Bluesky's open API is a gift for anyone who does social research. The &lt;a href="https://apify.com/scrape-smith/bluesky-posts-profiles" rel="noopener noreferrer"&gt;Bluesky Scraper&lt;/a&gt; turns it into CSVs and scheduled feeds with no code. Start with the prefilled hashtag and swap in your own topics.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bluesky</category>
      <category>socialmedia</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to Check US Trademark Status in Bulk with the USPTO TSDR API</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:42:02 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-check-us-trademark-status-in-bulk-with-the-uspto-tsdr-api-15ie</link>
      <guid>https://dev.to/scrape-smith/how-to-check-us-trademark-status-in-bulk-with-the-uspto-tsdr-api-15ie</guid>
      <description>&lt;h1&gt;
  
  
  How to Check US Trademark Status in Bulk with the USPTO TSDR API
&lt;/h1&gt;

&lt;p&gt;If you manage more than a handful of US trademarks, for clients or your own brands, you know the routine: open TSDR, paste a serial number, read the status, copy it into a spreadsheet, repeat. It's fine for five marks. It's a lost afternoon for two hundred.&lt;/p&gt;

&lt;p&gt;The USPTO has an official API for exactly this, called TSDR (Trademark Status &amp;amp; Document Retrieval). This tutorial explains how to get access, what it returns, and how to run bulk lookups with the &lt;a href="https://apify.com/scrape-smith/uspto-trademark-status" rel="noopener noreferrer"&gt;USPTO Trademark Status Lookup actor&lt;/a&gt; without writing any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get a free USPTO API key
&lt;/h2&gt;

&lt;p&gt;TSDR requires an API key. It is free:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://account.uspto.gov/api-manager" rel="noopener noreferrer"&gt;https://account.uspto.gov/api-manager&lt;/a&gt; and sign in with (or create) a USPTO.gov account.&lt;/li&gt;
&lt;li&gt;Request a TSDR API key.&lt;/li&gt;
&lt;li&gt;Copy the key somewhere safe.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key is personal and rate limited to 60 requests per minute. That's why this actor is "bring your own key": you stay in control of your own access and quota, and nothing is shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Collect serial numbers
&lt;/h2&gt;

&lt;p&gt;TSDR looks up marks by 8-digit &lt;strong&gt;serial number&lt;/strong&gt; (the application number), such as &lt;code&gt;78787878&lt;/code&gt;. If you only have a registration number, look it up once on tsdr.uspto.gov to find the serial. Most docketing exports already include serials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Run the lookup
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/uspto-trademark-status" rel="noopener noreferrer"&gt;actor&lt;/a&gt;, paste your serial numbers one per line, and paste your key into the &lt;strong&gt;API key&lt;/strong&gt; field (it's stored as a secret input). Click &lt;strong&gt;Start&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each successful row contains:&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 meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serialNumber&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;78787878&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;markText&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The word mark&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;e.g. "Registered", "Abandoned", "Published for opposition"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;statusDate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the status last changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;filingDate&lt;/code&gt;, &lt;code&gt;registrationDate&lt;/code&gt;, &lt;code&gt;registrationNumber&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Key dates and number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;owner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current owner name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;goodsServices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Goods &amp;amp; services descriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you run without a key (or with a wrong one), each row comes back as a &lt;strong&gt;free&lt;/strong&gt; error row saying &lt;code&gt;HTTP 401 - API key required/invalid&lt;/code&gt;. That's deliberate: you can test the tool safely before setting up the key, and you're never charged for failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Automate a weekly status report
&lt;/h2&gt;

&lt;p&gt;Schedule the actor weekly and export to Google Sheets, or from Python:&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="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;serials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portfolio.txt&lt;/span&gt;&lt;span class="sh"&gt;"&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;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;scrape-smith/uspto-trademark-status&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;serialNumbers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;serials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiKey&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;YOUR_USPTO_KEY&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&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="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&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;r&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;CHECK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serialNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serialNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusDate&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;Store last week's output and diff it: any row whose &lt;code&gt;status&lt;/code&gt; or &lt;code&gt;statusDate&lt;/code&gt; changed is something a human should look at (an office action, publication, registration, or abandonment).&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small IP practices&lt;/strong&gt; that want a status sheet without a docketing system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand owners&lt;/strong&gt; with a portfolio across product lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Due diligence&lt;/strong&gt; before acquiring a business or licensing a brand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive watch&lt;/strong&gt;: track competitors' pending applications as they move through examination.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You need your own USPTO key. There's no way around that, and that's a good thing.&lt;/li&gt;
&lt;li&gt;The USPTO limit is 60 requests per minute, so the actor paces itself at about one lookup per second. 1,000 marks take about 17 minutes.&lt;/li&gt;
&lt;li&gt;It looks up known serial numbers. It is not a clearance search by mark name.&lt;/li&gt;
&lt;li&gt;Status text comes straight from USPTO. It is not legal advice; deadlines should still be checked by an attorney.&lt;/li&gt;
&lt;li&gt;Not affiliated with the USPTO.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;$0.004 per successful lookup; errors are free. 100 marks is $0.40, 1,000 marks is $4. Apify platform usage is included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The TSDR API turns trademark status checks from a copy-paste chore into a scheduled report. If you'd rather not write the plumbing, the &lt;a href="https://apify.com/scrape-smith/uspto-trademark-status" rel="noopener noreferrer"&gt;USPTO Trademark Status Lookup&lt;/a&gt; does it with your own key in a couple of clicks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>legal</category>
      <category>api</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Export Steam Game Reviews (with Playtime) for Player Sentiment Analysis</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:38:40 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-export-steam-game-reviews-with-playtime-for-player-sentiment-analysis-4fej</link>
      <guid>https://dev.to/scrape-smith/how-to-export-steam-game-reviews-with-playtime-for-player-sentiment-analysis-4fej</guid>
      <description>&lt;h1&gt;
  
  
  How to Export Steam Game Reviews for Player Sentiment Analysis
&lt;/h1&gt;

&lt;p&gt;Steam reviews are unusually rich feedback. Every review is a thumbs up or down, it comes with how many hours the reviewer had played, and other players vote on whether it was helpful. For game developers, publishers and analysts, that's a sentiment dataset most industries would envy.&lt;/p&gt;

&lt;p&gt;Steam shows reviews nicely on the store page, but not in a form you can analyse. This tutorial uses Valve's public reviews endpoint through the &lt;a href="https://apify.com/scrape-smith/steam-reviews-scraper" rel="noopener noreferrer"&gt;Steam Reviews Scraper&lt;/a&gt; to export them, then looks at a couple of analyses worth doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The endpoint
&lt;/h2&gt;

&lt;p&gt;Valve documents a public endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://store.steampowered.com/appreviews/440?json=1&amp;amp;filter=recent&amp;amp;language=all&amp;amp;num_per_page=100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns reviews in pages with a &lt;code&gt;cursor&lt;/code&gt; for the next page, plus a summary like "Very Positive". The actor handles cursor pagination, throttles to roughly one request per second, and flattens each review into a row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Run the example
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/steam-reviews-scraper" rel="noopener noreferrer"&gt;actor&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. It's prefilled with app &lt;code&gt;440&lt;/code&gt; (Team Fortress 2) and 20 reviews:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;gameName&lt;/th&gt;
&lt;th&gt;recommended&lt;/th&gt;
&lt;th&gt;playtimeAtReviewMinutes&lt;/th&gt;
&lt;th&gt;votesUp&lt;/th&gt;
&lt;th&gt;language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Team Fortress 2&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;1236&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;spanish&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row includes review text, &lt;code&gt;recommended&lt;/code&gt;, &lt;code&gt;votesUp&lt;/code&gt;, &lt;code&gt;votesFunny&lt;/code&gt;, &lt;code&gt;weightedVoteScore&lt;/code&gt;, &lt;code&gt;playtimeForeverMinutes&lt;/code&gt;, &lt;code&gt;playtimeAtReviewMinutes&lt;/code&gt;, &lt;code&gt;playtimeLastTwoWeeksMinutes&lt;/code&gt;, &lt;code&gt;steamPurchase&lt;/code&gt;, &lt;code&gt;receivedForFree&lt;/code&gt;, &lt;code&gt;writtenDuringEarlyAccess&lt;/code&gt;, &lt;code&gt;primarilySteamDeck&lt;/code&gt;, author stats and any &lt;code&gt;developerResponse&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Pick your game and filters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;appIds&lt;/code&gt;: the number in the store URL (&lt;code&gt;store.steampowered.com/app/1091500/...&lt;/code&gt;) or the whole URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filter&lt;/code&gt;: &lt;code&gt;recent&lt;/code&gt; (newest first; the way to page through full history), &lt;code&gt;updated&lt;/code&gt;, or &lt;code&gt;all&lt;/code&gt; (most helpful first, optionally with &lt;code&gt;dayRange&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;language&lt;/code&gt;: &lt;code&gt;english&lt;/code&gt;, &lt;code&gt;german&lt;/code&gt;, &lt;code&gt;schinese&lt;/code&gt;... or &lt;code&gt;all&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reviewType&lt;/code&gt;: &lt;code&gt;positive&lt;/code&gt; or &lt;code&gt;negative&lt;/code&gt; only.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;purchaseType&lt;/code&gt;: Steam purchases vs. keys bought elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Two analyses worth doing
&lt;/h2&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&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;scrape-smith/steam-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="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;appIds&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;1091500&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;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter&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;recent&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;english&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;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;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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&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;createdAt&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;hours&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;playtimeAtReviewMinutes&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="mi"&gt;60&lt;/span&gt;

&lt;span class="c1"&gt;# 1) Sentiment over time: did the last patch help?
&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;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;recommended&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# 2) Do experienced players feel differently?
&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;bucket&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cut&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;hours&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="mi"&gt;0&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="mi"&gt;10&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&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;bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommended&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;mean&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first table shows the weekly share of positive reviews, the fastest way to see a patch landing well or badly. The second often reveals a pattern: players under two hours (refund window) and players over 200 hours can feel very differently about the same game. Both are useful, for different reasons.&lt;/p&gt;

&lt;p&gt;From there, feed negative reviews from heavy players into an LLM or topic model to find the issues your most committed players care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live-ops and patch monitoring&lt;/strong&gt; for studios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor and genre research&lt;/strong&gt; before greenlighting a project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publisher due diligence&lt;/strong&gt; on games they might sign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentiment and NLP datasets&lt;/strong&gt; with a built-in label (recommended or not).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily alerts&lt;/strong&gt; for new negative reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Some orderings (like &lt;code&gt;all&lt;/code&gt;) don't go back through the full history. Use &lt;code&gt;recent&lt;/code&gt; to page through everything.&lt;/li&gt;
&lt;li&gt;Huge games have hundreds of thousands of reviews; at one request per second, exporting all of them takes a while. Set &lt;code&gt;maxReviewsPerApp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Review text is user content. If you publish it, attribute and respect Steam's terms.&lt;/li&gt;
&lt;li&gt;Not affiliated with or endorsed by Valve.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;$0.0003 per review. The 20-review test is well under a cent; 10,000 reviews is $3. Error rows are free and you can cap spend per run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Steam gives you labelled, playtime-weighted feedback for every game on the platform. The &lt;a href="https://apify.com/scrape-smith/steam-reviews-scraper" rel="noopener noreferrer"&gt;Steam Reviews Scraper&lt;/a&gt; gets it into a DataFrame in a minute. Try the prefilled example, then point it at your game or the competitor you keep comparing yourself to.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>steam</category>
      <category>datascience</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Export iOS App Store Reviews by Country (for Sentiment Analysis and ASO)</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:35:09 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-export-ios-app-store-reviews-by-country-for-sentiment-analysis-and-aso-3nh6</link>
      <guid>https://dev.to/scrape-smith/how-to-export-ios-app-store-reviews-by-country-for-sentiment-analysis-and-aso-3nh6</guid>
      <description>&lt;h1&gt;
  
  
  How to Export iOS App Store Reviews by Country
&lt;/h1&gt;

&lt;p&gt;App reviews are the most honest product feedback you will ever get, and also the most annoying to collect. App Store Connect only shows reviews for your own apps, one storefront at a time, and third-party ASO suites are priced for agencies. If you just want a spreadsheet of recent reviews for your app or a competitor's, there is a simpler route: Apple's own public customer-reviews RSS feed.&lt;/p&gt;

&lt;p&gt;This tutorial walks through that feed, exports reviews with the &lt;a href="https://apify.com/scrape-smith/apple-app-store-reviews" rel="noopener noreferrer"&gt;Apple App Store Reviews Scraper&lt;/a&gt;, and ends with a quick sentiment pass in Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feed behind it
&lt;/h2&gt;

&lt;p&gt;Apple publishes recent reviews for every app at a URL like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://itunes.apple.com/us/rss/customerreviews/page=1/id=284882215/sortby=mostrecent/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;us&lt;/code&gt; is the storefront, &lt;code&gt;284882215&lt;/code&gt; is the app ID (Facebook here), and each page holds up to 50 reviews. There are up to 10 pages, so roughly the 500 most recent reviews per app per country.&lt;/p&gt;

&lt;p&gt;The JSON is awkward (every value is wrapped in a &lt;code&gt;label&lt;/code&gt; object, and the first entry is sometimes app metadata rather than a review), which is why a small wrapper helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Find the app ID
&lt;/h2&gt;

&lt;p&gt;Open the app on apps.apple.com. The ID is the number after &lt;code&gt;id&lt;/code&gt; in the URL: &lt;code&gt;apps.apple.com/us/app/facebook/id284882215&lt;/code&gt;. You can also paste the full URL into the actor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run it
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/apple-app-store-reviews" rel="noopener noreferrer"&gt;actor&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. The prefilled input is one app, the &lt;code&gt;us&lt;/code&gt; store and one page, so you get 50 real reviews in a few seconds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;rating&lt;/th&gt;
&lt;th&gt;title&lt;/th&gt;
&lt;th&gt;appVersion&lt;/th&gt;
&lt;th&gt;updated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Finding post&lt;/td&gt;
&lt;td&gt;584.0.0&lt;/td&gt;
&lt;td&gt;2026-10-20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;584.0.0&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row has &lt;code&gt;appId&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;reviewId&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;rating&lt;/code&gt; (1–5), &lt;code&gt;appVersion&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;updated&lt;/code&gt; and &lt;code&gt;voteCount&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add countries
&lt;/h2&gt;

&lt;p&gt;Reviews are per storefront. A German user's review only shows up in the &lt;code&gt;de&lt;/code&gt; store. Add the markets you care about, one per line: &lt;code&gt;us&lt;/code&gt;, &lt;code&gt;gb&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;jp&lt;/code&gt;, &lt;code&gt;br&lt;/code&gt;... Set &lt;code&gt;maxPages&lt;/code&gt; to 10 to get everything Apple exposes.&lt;/p&gt;

&lt;p&gt;Tip: the &lt;code&gt;appVersion&lt;/code&gt; column is gold. Group by version and you can see whether a release made ratings better or worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: A five-minute sentiment check
&lt;/h2&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&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;scrape-smith/apple-app-store-reviews&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;appIds&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;284882215&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;gb&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;ca&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;au&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;maxPages&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="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;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="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;appVersion&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="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;mean&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="nf"&gt;tail&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;low&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="n"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&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;low&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractall&lt;/span&gt;&lt;span class="p"&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;(crash\w*|login|ads?|slow|bug\w*)&lt;/span&gt;&lt;span class="sh"&gt;"&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;value_counts&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 shows average rating per recent version. The second counts common complaint words in 1–2 star reviews. Crude, but it gets you 80% of the insight before you reach for an LLM. If you do want an LLM, the reviews CSV is a perfect prompt input ("cluster these complaints into themes").&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Release monitoring&lt;/strong&gt;: schedule it daily and alert on new 1-star reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor research&lt;/strong&gt;: mine competitors' reviews for missing features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASO keyword research&lt;/strong&gt;: real users' words are the words people search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NLP datasets&lt;/strong&gt; for sentiment models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support triage&lt;/strong&gt;: route review complaints to the right team.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Apple's feed only exposes about 500 recent reviews per app per country. For an app with millions of reviews, this is a rolling window, not the full history. Schedule regular runs if you want to build history over time.&lt;/li&gt;
&lt;li&gt;Ratings without written reviews aren't included; Apple doesn't publish them in the feed.&lt;/li&gt;
&lt;li&gt;No developer replies; the feed doesn't contain them.&lt;/li&gt;
&lt;li&gt;This is App Store only, not Google Play.&lt;/li&gt;
&lt;li&gt;Not affiliated with Apple. It reads a public feed and doesn't log in.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;$0.0003 per review. The 50-review test costs 1.5 cents; 1,000 reviews is $0.30. Apify platform usage is included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;For most "what are users saying?" questions, Apple's RSS feed plus a spreadsheet is enough. Try the &lt;a href="https://apify.com/scrape-smith/apple-app-store-reviews" rel="noopener noreferrer"&gt;App Store Reviews Scraper&lt;/a&gt; with the prefilled app, then replace it with yours or a competitor's.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>datascience</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Scrape Job Postings from Workable, Personio, SmartRecruiters and More (One Clean Feed)</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:32:26 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-scrape-job-postings-from-workable-personio-smartrecruiters-and-more-one-clean-feed-25mh</link>
      <guid>https://dev.to/scrape-smith/how-to-scrape-job-postings-from-workable-personio-smartrecruiters-and-more-one-clean-feed-25mh</guid>
      <description>&lt;h1&gt;
  
  
  How to Scrape Job Postings from Workable, Personio, SmartRecruiters and More
&lt;/h1&gt;

&lt;p&gt;Building a niche job board, tracking which competitors are hiring, or finding companies that just opened a "Head of Data" role all start the same way: you need job postings from many company career pages, in one consistent format.&lt;/p&gt;

&lt;p&gt;The good news is that most applicant tracking systems (ATS) publish official public job feeds so their customers' jobs can be syndicated. The bad news is that every ATS does it differently: Personio speaks XML, Teamtailor RSS, Workable and Recruitee JSON with different field names, SmartRecruiters a paginated REST API, and BambooHR its own list format.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to pull all six into one normalized table with the &lt;a href="https://apify.com/scrape-smith/multi-ats-jobs-feed" rel="noopener noreferrer"&gt;ATS Jobs Scraper&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported ATS and how to identify them
&lt;/h2&gt;

&lt;p&gt;Look at a company's careers page URL:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ATS&lt;/th&gt;
&lt;th&gt;Careers URL looks like&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Workable&lt;/td&gt;
&lt;td&gt;apply.workable.com/&lt;strong&gt;huggingface&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workable:huggingface&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recruitee&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;bunq&lt;/strong&gt;.recruitee.com&lt;/td&gt;
&lt;td&gt;&lt;code&gt;recruitee:bunq&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personio&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;acme&lt;/strong&gt;.jobs.personio.de&lt;/td&gt;
&lt;td&gt;&lt;code&gt;personio:acme&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Teamtailor&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;polestar&lt;/strong&gt;.teamtailor.com&lt;/td&gt;
&lt;td&gt;&lt;code&gt;teamtailor:polestar&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SmartRecruiters&lt;/td&gt;
&lt;td&gt;jobs.smartrecruiters.com/&lt;strong&gt;Ubisoft2&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smartrecruiters:Ubisoft2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BambooHR&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;bitly&lt;/strong&gt;.bamboohr.com/careers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bamboohr:bitly&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can type &lt;code&gt;ats:slug&lt;/code&gt; or just paste the careers URL. Mixing ATS in one run is fine.&lt;/p&gt;

&lt;p&gt;Tip: if a company's careers page is on its own domain, click any job. The apply link usually reveals which ATS is behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Run the example
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/multi-ats-jobs-feed" rel="noopener noreferrer"&gt;actor&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. The prefill includes a few real companies and a limit of 20 jobs per company. Within seconds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ats&lt;/th&gt;
&lt;th&gt;company&lt;/th&gt;
&lt;th&gt;title&lt;/th&gt;
&lt;th&gt;location&lt;/th&gt;
&lt;th&gt;remote&lt;/th&gt;
&lt;th&gt;postedAt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;teamtailor&lt;/td&gt;
&lt;td&gt;Polestar&lt;/td&gt;
&lt;td&gt;Head of Customer Engagement Benelux&lt;/td&gt;
&lt;td&gt;Beesd, Netherlands&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;2026-10-15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;workable&lt;/td&gt;
&lt;td&gt;Hugging Face&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;Remote&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row has the same fields regardless of source: &lt;code&gt;ats, companySlug, company, id, title, location, city, country, remote, department, employmentType, experienceLevel, postedAt, url, applyUrl, description&lt;/code&gt;. If an ATS doesn't expose a field, it's &lt;code&gt;null&lt;/code&gt; rather than missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Filter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;keyword&lt;/code&gt;: case-insensitive title filter, e.g. &lt;code&gt;engineer&lt;/code&gt; or &lt;code&gt;sales&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onlyNewSince&lt;/code&gt;: skip jobs posted before a date. Combine with a daily schedule to get only new jobs each day.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includeDescription&lt;/code&gt;: on by default. Turn it off for faster, title-only runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Daily "new jobs" feed
&lt;/h2&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;datetime&lt;/span&gt;

&lt;span class="n"&gt;yesterday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&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;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&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&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;scrape-smith/multi-ats-jobs-feed&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;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="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;open&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.txt&lt;/span&gt;&lt;span class="sh"&gt;"&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;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;onlyNewSince&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyword&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&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;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;False&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;job&lt;/span&gt; &lt;span class="ow"&gt;in&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="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;job&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&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="si"&gt;}&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;Pipe that into Slack, email, or a static site generator and you have a niche job board backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Niche job boards&lt;/strong&gt; (remote, climate, Rust, EU startups) from hundreds of career pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive intelligence&lt;/strong&gt;: hiring is a leading indicator. A competitor posting ten sales roles in Germany is telling you something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sales prospecting&lt;/strong&gt;: companies hiring for a role often need tools for that role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruiting research&lt;/strong&gt; and labour-market analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Only these six ATS. Greenhouse, Lever, Ashby and Workday aren't covered by this actor.&lt;/li&gt;
&lt;li&gt;You need the company slug. The tool doesn't discover companies for you; you bring the list.&lt;/li&gt;
&lt;li&gt;Some ATS don't publish a posted date (so &lt;code&gt;onlyNewSince&lt;/code&gt; can't filter those), and some don't expose remote flags.&lt;/li&gt;
&lt;li&gt;If a company disables its public feed or the slug is wrong, you get a free error row for that company and the rest continue.&lt;/li&gt;
&lt;li&gt;These are official public feeds meant for syndication, but check each company's terms if you republish job content, and always link to the original apply URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;Pay per result: $0.005 per company feed fetched plus $0.001 per job. The test run costs about three cents. 1,000 jobs from 20 companies is $1.10. Platform usage is included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Six ATS, six formats, one table. If you're building anything on top of job data, try the &lt;a href="https://apify.com/scrape-smith/multi-ats-jobs-feed" rel="noopener noreferrer"&gt;ATS Jobs Scraper&lt;/a&gt; with the prefilled companies and then paste in your own list.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>jobs</category>
      <category>webscraping</category>
      <category>python</category>
      <category>startup</category>
    </item>
    <item>
      <title>How to Get the Full Wayback Machine History of Any Website (No Code or Python)</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:24:13 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-get-the-full-wayback-machine-history-of-any-website-no-code-or-python-p5d</link>
      <guid>https://dev.to/scrape-smith/how-to-get-the-full-wayback-machine-history-of-any-website-no-code-or-python-p5d</guid>
      <description>&lt;h1&gt;
  
  
  How to Get the Full Wayback Machine History of Any Website
&lt;/h1&gt;

&lt;p&gt;If you have ever tried to answer "when did this competitor change their pricing page?" or "what did this domain look like before I bought it?", you have probably clicked around web.archive.org one calendar dot at a time. It works, but it does not scale past a handful of pages, and you cannot put it in a spreadsheet.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to pull the complete capture history of a URL or domain into a table you can filter, chart and diff. We will use the Internet Archive's public CDX API through a small hosted tool, the &lt;a href="https://apify.com/scrape-smith/wayback-snapshots" rel="noopener noreferrer"&gt;Wayback Machine Scraper on Apify&lt;/a&gt;, and then look at the same thing from Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the CDX API actually gives you
&lt;/h2&gt;

&lt;p&gt;The Wayback Machine keeps an index of every capture it has made. Each entry has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original URL and capture timestamp&lt;/li&gt;
&lt;li&gt;the HTTP status code the archive saw (200, 301, 404...)&lt;/li&gt;
&lt;li&gt;the MIME type&lt;/li&gt;
&lt;li&gt;a content digest (a hash of the page body)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The digest is the interesting bit. If two consecutive captures have the same digest, the page did not change. If it differs, something on the page changed. That turns the archive from a photo album into a change log.&lt;/p&gt;

&lt;p&gt;The raw API is free and public, but it has quirks: timestamps like &lt;code&gt;20200109130705&lt;/code&gt;, pagination by line, and collapse rules that are easy to get wrong. The actor wraps all of that and returns clean rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Run it with the example input
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/wayback-snapshots" rel="noopener noreferrer"&gt;actor page&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. The input is prefilled with &lt;code&gt;apify.com&lt;/code&gt;, monthly collapse, from 2020, limit 20. Click &lt;strong&gt;Start&lt;/strong&gt;. In a few seconds you get rows like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;capturedAt&lt;/th&gt;
&lt;th&gt;statusCode&lt;/th&gt;
&lt;th&gt;contentChanged&lt;/th&gt;
&lt;th&gt;snapshotUrl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2020-01-09T13:07:05Z&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;&lt;a href="https://web.archive.org/web/20200109130705/https://apify.com/" rel="noopener noreferrer"&gt;https://web.archive.org/web/20200109130705/https://apify.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020-02-03T...&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;contentChanged&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt; for the first row (nothing to compare against) and &lt;code&gt;true&lt;/code&gt;/&lt;code&gt;false&lt;/code&gt; after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Choose the right collapse mode
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;collapse&lt;/code&gt; field decides how many rows you get back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;month&lt;/strong&gt;: at most one capture per month. Best for a quick overview of a site over years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;day&lt;/strong&gt;: one per day. Good for a period you care about, like a product launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;digest&lt;/strong&gt;: only captures where the content changed. This is the one to use for change tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;none&lt;/strong&gt;: every single capture. Popular sites can have tens of thousands, so set a &lt;code&gt;limit&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Pick a match type
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;exact&lt;/code&gt; looks at one URL, such as &lt;code&gt;example.com/pricing&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prefix&lt;/code&gt; covers everything under a path, such as &lt;code&gt;example.com/blog/&lt;/code&gt;. Handy for finding deleted blog posts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;domain&lt;/code&gt; covers the whole domain including subdomains.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A practical example: to see every blog URL a competitor ever published, use &lt;code&gt;matchType=prefix&lt;/code&gt;, &lt;code&gt;collapse=digest&lt;/code&gt; and a high limit. Then sort by URL and look for posts that return 404 today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Export or automate
&lt;/h2&gt;

&lt;p&gt;Every run produces a dataset you can download as CSV, Excel or JSON. You can also schedule the actor weekly and get only new captures by setting &lt;code&gt;from&lt;/code&gt; to last week's date.&lt;/p&gt;

&lt;p&gt;From Python with the official client:&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="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&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;scrape-smith/wayback-snapshots&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;urls&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;example.com/pricing&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;from&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;2022&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;collapse&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;digest&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;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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;row&lt;/span&gt; &lt;span class="ow"&gt;in&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&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;capturedAt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&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;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&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;snapshotUrl&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;That prints one line per version of the pricing page since 2022. Open the &lt;code&gt;snapshotUrl&lt;/code&gt; of two neighbours to see exactly what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases that come up a lot
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SEO audits.&lt;/strong&gt; When did a page start redirecting? When did the title change? Status-code history answers both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor monitoring.&lt;/strong&gt; Pricing, plans and positioning pages, version by version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expired-domain due diligence.&lt;/strong&gt; See whether a domain you are about to buy used to host spam.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content recovery.&lt;/strong&gt; Find old copies of pages you deleted by accident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research and journalism.&lt;/strong&gt; Show how an organisation's public statements evolved.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The archive only has what it crawled. Small sites may have a few captures a year.&lt;/li&gt;
&lt;li&gt;The tool returns snapshot metadata and links, not the page HTML. If you need the HTML, fetch the &lt;code&gt;snapshotUrl&lt;/code&gt; yourself (add &lt;code&gt;id_&lt;/code&gt; after the timestamp for the raw page).&lt;/li&gt;
&lt;li&gt;The digest changes on any byte change, so pages with rotating timestamps or ads will show "changed" more often than a human would say.&lt;/li&gt;
&lt;li&gt;This is not an official Internet Archive product. Please be kind to their servers; the actor makes a modest number of requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Pricing is pay-per-result: $0.0005 per snapshot plus $0.003 per run. A monthly history of one domain since 2020 is around 80 rows, so about four cents. 1,000 snapshots is $0.50. Apify's free plan credit easily covers experimenting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The Wayback Machine is one of the most useful free datasets on the web, and the CDX index makes it queryable. If you want to try it without writing any code, the &lt;a href="https://apify.com/scrape-smith/wayback-snapshots" rel="noopener noreferrer"&gt;Wayback Machine Scraper&lt;/a&gt; has a prefilled example you can run in one click. If you build something with it, I would love to hear what.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>seo</category>
      <category>python</category>
      <category>archive</category>
    </item>
    <item>
      <title>How to Export Any Shopify Store's Product Catalog and Prices to CSV</title>
      <dc:creator>Scrape-Smith</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:22:40 +0000</pubDate>
      <link>https://dev.to/scrape-smith/how-to-export-any-shopify-stores-product-catalog-and-prices-to-csv-1k1m</link>
      <guid>https://dev.to/scrape-smith/how-to-export-any-shopify-stores-product-catalog-and-prices-to-csv-1k1m</guid>
      <description>&lt;h1&gt;
  
  
  How to Export Any Shopify Store's Product Catalog and Prices to CSV
&lt;/h1&gt;

&lt;p&gt;Roughly a quarter of online stores run on Shopify, and almost all of them expose something many people don't know about: a public JSON product feed. If you do price monitoring, dropshipping research or competitor analysis, this feed is the cleanest data source you will find, and you don't need a headless browser or proxies to use it.&lt;/p&gt;

&lt;p&gt;In this tutorial we'll look at how the feed works, then export a full catalog with the &lt;a href="https://apify.com/scrape-smith/shopify-products-feed" rel="noopener noreferrer"&gt;Shopify Products Scraper on Apify&lt;/a&gt;, and finish with a small price-tracking workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The /products.json trick
&lt;/h2&gt;

&lt;p&gt;Open any Shopify store and add &lt;code&gt;/products.json&lt;/code&gt; to the domain, for example &lt;code&gt;https://www.allbirds.com/products.json&lt;/code&gt;. You will see a JSON list of products with titles, handles, vendors, product types, tags, images and every variant with its price, compare-at price and availability.&lt;/p&gt;

&lt;p&gt;Two catches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It is paginated (&lt;code&gt;?limit=250&amp;amp;page=2&lt;/code&gt; ...), so large stores need many requests.&lt;/li&gt;
&lt;li&gt;Prices and availability live inside variants, so you have to flatten them to get a useful table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is exactly the boring part a tool should do for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Check the store is on Shopify
&lt;/h2&gt;

&lt;p&gt;Visit &lt;code&gt;https://thestore.com/products.json&lt;/code&gt;. JSON means you're good. A 404 or an HTML page means the store isn't on Shopify or has disabled the feed (rare, but it happens).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run the scraper
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/scrape-smith/shopify-products-feed" rel="noopener noreferrer"&gt;Shopify Products Scraper&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;. The input is prefilled with &lt;code&gt;https://www.allbirds.com&lt;/code&gt; and a limit of 20 products, so the first run takes a few seconds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;title&lt;/th&gt;
&lt;th&gt;vendor&lt;/th&gt;
&lt;th&gt;minPrice&lt;/th&gt;
&lt;th&gt;onSale&lt;/th&gt;
&lt;th&gt;anyAvailable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Women's Allbirds Flip Flop - Dusty Pink&lt;/td&gt;
&lt;td&gt;Allbirds&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Men's Canvas Runner NZ - Deep Navy Stripes&lt;/td&gt;
&lt;td&gt;Allbirds&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each product row includes &lt;code&gt;minPrice&lt;/code&gt;, &lt;code&gt;maxPrice&lt;/code&gt;, &lt;code&gt;onSale&lt;/code&gt; (a compare-at price exists), &lt;code&gt;anyAvailable&lt;/code&gt;, &lt;code&gt;variantCount&lt;/code&gt;, tags, image URLs and timestamps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Products or variants?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;includeVariants&lt;/code&gt; switch changes the shape of the output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Off&lt;/strong&gt; (default): one row per product with min/max price. Best for catalog overviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On&lt;/strong&gt;: one row per variant (each size/colour) with its own SKU, price, compare-at price and availability. Best for price and stock monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Raise &lt;code&gt;maxProductsPerStore&lt;/code&gt; to export the full catalog. You can paste many stores at once, one per line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Build a simple price tracker
&lt;/h2&gt;

&lt;p&gt;Schedule the actor daily on Apify (Schedules &amp;gt; Create), then compare today's dataset with yesterday's. A minimal Python version:&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;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&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&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;scrape-smith/shopify-products-feed&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;storeUrls&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://www.allbirds.com&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;https://www.gymshark.com&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;maxProductsPerStore&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeVariants&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;rows&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;fname&lt;/span&gt; &lt;span class="o"&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;prices-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&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;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows saved to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Join two days on the variant ID and you have price drops, new products and sold-out items. Or skip code entirely: connect the dataset to Google Sheets with Apify's integration and use a formula.&lt;/p&gt;

&lt;h2&gt;
  
  
  What people use this for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Competitor price monitoring&lt;/strong&gt;, especially catching sales via compare-at prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dropshipping and product research&lt;/strong&gt; across dozens of stores in a niche.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalog backup or migration&lt;/strong&gt; when moving platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand and market research&lt;/strong&gt;: which product types and tags dominate a category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feeding AI agents&lt;/strong&gt;: the actor works through Apify's MCP server, so an assistant can pull a catalog on request.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The public feed shows availability (in stock / out of stock), not exact inventory counts.&lt;/li&gt;
&lt;li&gt;Some stores hide products from the feed or disable it; you can't get around that, and you shouldn't try.&lt;/li&gt;
&lt;li&gt;Prices are in the store's default currency and market.&lt;/li&gt;
&lt;li&gt;Respect each store's terms of service. This tool reads a public endpoint at a gentle rate; it does not log in or bypass anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;$0.0008 per row. The 20-product test is under two cents; 1,000 products is $0.80; a 10,000-variant monitoring run is $8. Apify platform usage is included, and you can cap spend per run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Shopify's product feed is one of the easiest structured data sources on the web, as long as something handles pagination and flattening. Try the &lt;a href="https://apify.com/scrape-smith/shopify-products-feed" rel="noopener noreferrer"&gt;Shopify Products Scraper&lt;/a&gt; with the prefilled example, then swap in the stores you actually care about.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scrape-Smith builds small, cheap data tools on public and official APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>webscraping</category>
      <category>python</category>
    </item>
  </channel>
</rss>
