<?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: Boris Radevic</title>
    <description>The latest articles on DEV Community by Boris Radevic (@radevicb).</description>
    <link>https://dev.to/radevicb</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%2F4091338%2Fd920cb70-2d5e-4197-8e41-41b36244d6bd.png</url>
      <title>DEV Community: Boris Radevic</title>
      <link>https://dev.to/radevicb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/radevicb"/>
    <language>en</language>
    <item>
      <title>I got tired of scraping app reviews twice, so I unified both stores</title>
      <dc:creator>Boris Radevic</dc:creator>
      <pubDate>Sun, 23 Aug 2026 22:49:53 +0000</pubDate>
      <link>https://dev.to/radevicb/i-got-tired-of-scraping-app-reviews-twice-so-i-unified-both-stores-4ccp</link>
      <guid>https://dev.to/radevicb/i-got-tired-of-scraping-app-reviews-twice-so-i-unified-both-stores-4ccp</guid>
      <description>&lt;p&gt;Every app review pipeline I've seen starts the same way: one scraper for Google&lt;br&gt;
Play, a different one for the App Store, and then a normalization layer to make&lt;br&gt;
&lt;code&gt;score&lt;/code&gt; and &lt;code&gt;im:rating&lt;/code&gt; pretend to be the same field. You write that glue code&lt;br&gt;
once, it breaks quietly, and you write it again.&lt;/p&gt;

&lt;p&gt;The raw sources, if you want to DIY it: Apple still serves a public RSS feed at&lt;br&gt;
&lt;code&gt;itunes.apple.com/{country}/rss/customerreviews/...&lt;/code&gt; — no auth needed, but it&lt;br&gt;
caps at roughly 500 recent reviews per app per country, and nobody tells you that&lt;br&gt;
until you hit it. For Play there are the open-source &lt;code&gt;google-play-scraper&lt;/code&gt;&lt;br&gt;
libraries, which work fine until Google shifts an internal endpoint and they&lt;br&gt;
don't.&lt;/p&gt;

&lt;p&gt;There's also the subscription route. Appfigures starts at $9.99/month and Appbot&lt;br&gt;
at $49/month, and they're good products — dashboards, alerts, sentiment, the&lt;br&gt;
whole suite. But if your actual need is "give me the reviews as data", paying for&lt;br&gt;
a dashboard you'll never open feels wrong.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://apify.com/radevicb/app-review-monitor" rel="noopener noreferrer"&gt;App Review Monitor&lt;/a&gt;&lt;br&gt;
on Apify. You pass apps from either store, it returns every review in one schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"310633997"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-20T14:03:11.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Latest update broke uploads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Since the last version ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"appVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"26.16.81"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reviewer_a1b2c3d4e5f6"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same ten fields whether it came from Play or the App Store. Pipe it into an LLM&lt;br&gt;
for a theme digest, a sheet for support triage, whatever.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;author&lt;/code&gt; field is a hash, not a name. On purpose. Review &lt;em&gt;text&lt;/em&gt; is what you&lt;br&gt;
analyze; the reviewer's display name is GDPR liability you almost never need, so&lt;br&gt;
anonymization is the default and including real names is an explicit opt-in.&lt;/p&gt;

&lt;p&gt;The other opinionated choice: failures are loud. Apps that couldn't be fetched&lt;br&gt;
get listed in the run summary, and if every app fails the run exits FAILED&lt;br&gt;
instead of handing you an empty dataset with a green checkmark. Monitoring that&lt;br&gt;
fails silently isn't monitoring.&lt;/p&gt;

&lt;p&gt;Disclosure: I build and maintain this with AI assistance on my own account, and&lt;br&gt;
it collects only publicly available review data.&lt;/p&gt;

</description>
      <category>appdev</category>
      <category>aso</category>
      <category>api</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Google Trends still has no API in 2026. Here's what I use instead</title>
      <dc:creator>Boris Radevic</dc:creator>
      <pubDate>Sun, 23 Aug 2026 22:49:47 +0000</pubDate>
      <link>https://dev.to/radevicb/google-trends-still-has-no-api-in-2026-heres-what-i-use-instead-1840</link>
      <guid>https://dev.to/radevicb/google-trends-still-has-no-api-in-2026-heres-what-i-use-instead-1840</guid>
      <description>&lt;p&gt;Google announced a Trends API alpha in July 2025. It's now August 2026 and access&lt;br&gt;
is still application-gated. I applied, waited, heard nothing, and eventually built&lt;br&gt;
my own thing.&lt;/p&gt;

&lt;p&gt;If you just need trends data in a pipeline or a spreadsheet, here's the honest&lt;br&gt;
lay of the land.&lt;/p&gt;

&lt;p&gt;The official alpha is worth applying for anyway — official beats scraped every&lt;br&gt;
time, and if your application goes through and the quotas fit, stop reading here.&lt;br&gt;
Mine didn't go through.&lt;/p&gt;

&lt;p&gt;The commercial SERP APIs (DataForSEO, SerpAPI) all have Trends endpoints. They&lt;br&gt;
work. They also start around $50-75/month, which is a strange amount of money to&lt;br&gt;
pay when you want to check thirty keywords once a week. If you're doing enterprise&lt;br&gt;
volume, fine, that's your answer.&lt;/p&gt;

&lt;p&gt;I wasn't doing enterprise volume. So I built&lt;br&gt;
&lt;a href="https://apify.com/radevicb/google-trends-explorer" rel="noopener noreferrer"&gt;Google Trends Explorer&lt;/a&gt; on&lt;br&gt;
Apify — you pass keywords, a region and a time range, and get one JSON item per&lt;br&gt;
keyword back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"keyword"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notion templates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"geo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worldwide"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeRange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"today 12-m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"latestValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"averageValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;68.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"peakDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"May 3 - 9, 2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"interestOverTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Aug 2025"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"relatedQueries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"top"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"templates for notion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rising"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notion marketplace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"change"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+250%"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;latestValue&lt;/code&gt; / &lt;code&gt;averageValue&lt;/code&gt; / &lt;code&gt;peakDate&lt;/code&gt; fields are there because half the&lt;br&gt;
time that's all you actually wanted — feeding a 54-point series to an LLM so it&lt;br&gt;
can tell you "interest peaked in May" is a waste of tokens.&lt;/p&gt;

&lt;p&gt;Some things you should know before trusting any Trends data, from any source:&lt;br&gt;
the values are Google's sampled, relative 0-100 index within whatever range and&lt;br&gt;
region you picked. Run the same query twice and small numbers can wiggle. It's a&lt;br&gt;
signal for "is this topic growing or dying", not a measurement.&lt;/p&gt;

&lt;p&gt;And Google rate-limits these endpoints hard. My actor rotates proxy sessions and&lt;br&gt;
backs off on 429s, but I made one design decision I'd defend anywhere: if every&lt;br&gt;
keyword fails, the run exits FAILED. Loudly. I've been burned by scrapers that&lt;br&gt;
return an empty array and call it success — you find out three weeks later that&lt;br&gt;
your "data pipeline" has been shipping nothing.&lt;/p&gt;

&lt;p&gt;Full disclosure: I build and maintain this with AI assistance, on my own account,&lt;br&gt;
and it only touches public aggregate data. If the official API ever goes GA with&lt;br&gt;
sane quotas, I'll be the first to switch.&lt;/p&gt;

</description>
      <category>googletrends</category>
      <category>api</category>
      <category>seo</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
