<?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: Rafi-siddiq</title>
    <description>The latest articles on DEV Community by Rafi-siddiq (@rafisiddiq).</description>
    <link>https://dev.to/rafisiddiq</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%2F4219347%2F1df0d8b6-f686-4e9b-a90b-d80f1b87b1ab.png</url>
      <title>DEV Community: Rafi-siddiq</title>
      <link>https://dev.to/rafisiddiq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rafisiddiq"/>
    <language>en</language>
    <item>
      <title>A pytrends alternative that handles Google Trends rate limits</title>
      <dc:creator>Rafi-siddiq</dc:creator>
      <pubDate>Sat, 31 Oct 2026 17:32:09 +0000</pubDate>
      <link>https://dev.to/rafisiddiq/a-pytrends-alternative-that-handles-google-trends-rate-limits-537p</link>
      <guid>https://dev.to/rafisiddiq/a-pytrends-alternative-that-handles-google-trends-rate-limits-537p</guid>
      <description>&lt;p&gt;For years, the default way to get Google Trends data in code was &lt;a href="https://github.com/GeneralMills/pytrends" rel="noopener noreferrer"&gt;pytrends&lt;/a&gt;. Today it's a frustrating choice: the repository is archived, it no longer gets fixes, and many people now see &lt;code&gt;429 Too Many Requests&lt;/code&gt; on the very first call.&lt;/p&gt;

&lt;p&gt;This post covers why that happens, what your options are, and how to get Trends data without managing it yourself. &lt;em&gt;Disclosure: the hosted option at the end is a tool I built.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Google Trends returns 429s
&lt;/h2&gt;

&lt;p&gt;Google Trends has no public API that anyone can use (more on the official one below). Libraries like pytrends call the same internal endpoints the Trends website uses. Google limits how often those endpoints can be called, and it doesn't publish the limits. When you go over, you get a 429 and have to wait.&lt;/p&gt;

&lt;p&gt;The limits are stricter for traffic from cloud servers than from home connections, which is why a script that works on your laptop often fails on a server or in a notebook environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your options
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Keep a library and slow it down.&lt;/strong&gt; Space requests out by several seconds, retry with exponential backoff (wait 2 s, then 4 s, then 8 s…), and cache results so you don't ask twice. This works for small, occasional jobs. It gets painful when you need hundreds of keywords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Apply for the official Google Trends API.&lt;/strong&gt; Google opened an &lt;a href="https://developers.google.com/search/apis/trends" rel="noopener noreferrer"&gt;alpha of an official Trends API&lt;/a&gt; in 2025. It returns consistently scaled data, which the website doesn't. Access is by application, it's limited to approved testers, and there's no general release date yet. If Trends data matters to your product, apply now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use a hosted scraper.&lt;/strong&gt; Someone else deals with retries, pacing, and changes to the site, and you call an API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a hosted Google Trends scraper
&lt;/h2&gt;

&lt;p&gt;I built the &lt;a href="https://apify.com/rsiddiq95/google-trends-scraper" rel="noopener noreferrer"&gt;Google Trends Scraper&lt;/a&gt; on Apify for my own keyword research. For each search term it returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interest over time:&lt;/strong&gt; the 0–100 timeline for any period, from the past hour to 2004 onward, or exact dates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interest by region:&lt;/strong&gt; countries, states, cities, or US metro areas (DMA).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related searches:&lt;/strong&gt; top and rising queries, with a flag for "Breakout" (growth over 5,000%).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparisons:&lt;/strong&gt; up to 5 terms on the same scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trending Now:&lt;/strong&gt; today's trending searches per country, with traffic estimates and news links.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It paces itself, backs off when Google rate-limits it, and only charges for terms that return data: failed lookups and terms with no data are free.&lt;/p&gt;

&lt;h3&gt;
  
  
  From the web UI
&lt;/h3&gt;

&lt;p&gt;Enter your terms, a location such as &lt;code&gt;US&lt;/code&gt; or &lt;code&gt;US-CA&lt;/code&gt;, and a time range, then click &lt;strong&gt;Start&lt;/strong&gt;. You can download the results as JSON, CSV or Excel.&lt;/p&gt;

&lt;h3&gt;
  
  
  From code
&lt;/h3&gt;

&lt;p&gt;Apify can run it and return the results in one HTTP call. Get your API token from &lt;strong&gt;Settings → API &amp;amp; Integrations&lt;/strong&gt; in Apify Console, and keep it out of your code and repositories (use an environment variable).&lt;/p&gt;

&lt;p&gt;With curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/rsiddiq95~google-trends-scraper/run-sync-get-dataset-items"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$APIFY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"searchTerms": ["air fryer", "standing desk"], "geo": "US", "timeRange": "today 12-m"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 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;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&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://api.apify.com/v2/acts/rsiddiq95~google-trends-scraper/run-sync-get-dataset-items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&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;searchTerms&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;air fryer&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;standing desk&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;geo&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;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;timeRange&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;today 12-m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;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;resultType&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;keyword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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;searchTerm&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;averageInterest&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;Each keyword comes back as one record:&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;"resultType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyword"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hasData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"searchTerm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"air fryer"&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;"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;"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;"averageInterest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65&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;"2025-10-26T00:00:00.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;"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;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isPartial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;"interestByRegion"&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;"geoCode"&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-WY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"geoName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wyoming"&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;"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;"air fryer chicken"&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;"best air fryer 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;"formattedValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Breakout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isBreakout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trendsUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://trends.google.com/trends/explore?q=air+fryer&amp;amp;date=today+12-m&amp;amp;geo=US"&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;It costs $2.50 per 1,000 keywords and $1 per 1,000 trending searches, with no platform fees on top. Apify's free plan includes monthly credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to know about Trends data (whatever tool you use)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Values are relative.&lt;/strong&gt; 100 is the peak within your query, not a search volume. If you want two keywords on the same scale, compare them in one query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results can shift slightly between runs,&lt;/strong&gt; because Google re-scales each request. Use exact date ranges for reports you want to reproduce.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related topics aren't available&lt;/strong&gt; from my scraper, because Google doesn't provide them to automated tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classic Explore is going away.&lt;/strong&gt; Google shows a notice on the Trends site that the "classic Explore" page is expiring soon, with no date announced. The new version requires signing in. Every unofficial Trends tool reads the classic version, so expect disruption when it shuts down. That's another reason to apply for the official API if you depend on this data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have questions or need a feature, open an issue on the Actor page.&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>seo</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>How to track competitors' Google ads in a Google Sheet automatically</title>
      <dc:creator>Rafi-siddiq</dc:creator>
      <pubDate>Sat, 31 Oct 2026 17:31:44 +0000</pubDate>
      <link>https://dev.to/rafisiddiq/how-to-track-competitors-google-ads-in-a-google-sheet-automatically-1nnj</link>
      <guid>https://dev.to/rafisiddiq/how-to-track-competitors-google-ads-in-a-google-sheet-automatically-1nnj</guid>
      <description>&lt;p&gt;If you run paid search, you've probably opened the &lt;a href="https://adstransparency.google.com/" rel="noopener noreferrer"&gt;Google Ads Transparency Center&lt;/a&gt; to see what a competitor is running. It's useful, but it shows one ad at a time, and there's no export. If you want to notice when a competitor changes their messaging, you have to remember to check.&lt;/p&gt;

&lt;p&gt;This tutorial sets up a small pipeline that does it for you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every morning, collect the latest Google ads for a list of competitors, including the ad copy.&lt;/li&gt;
&lt;li&gt;Write them to a Google Sheet, updating ads you already have instead of duplicating them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It takes about 15 minutes and runs on &lt;a href="https://apify.com" rel="noopener noreferrer"&gt;Apify&lt;/a&gt;. &lt;em&gt;Disclosure: I built both Actors used here.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll end up with
&lt;/h2&gt;

&lt;p&gt;A sheet with one row per ad, kept up to date:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;advertiserName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nike, Inc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TEXT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;adText&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nike, nike.com, Nike® Official Site, Shop exclusive new styles…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;firstShownAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2023-11-17T00:26:00Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lastShownAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2026-10-30T00:29:17Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;daysShown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;911&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;landingDomain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nike.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;adUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;link to the ad in the Transparency Center&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because &lt;code&gt;lastShownAt&lt;/code&gt; updates on every run, sorting by it shows which ads are still live, and new rows at the bottom are ads that appeared since yesterday.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Collect the ads
&lt;/h2&gt;

&lt;p&gt;Open the &lt;a href="https://apify.com/rsiddiq95/google-ads-transparency-scraper" rel="noopener noreferrer"&gt;Google Ads Transparency Scraper&lt;/a&gt; and click &lt;strong&gt;Try for free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can find a competitor's ads three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;code&gt;competitor.com&lt;/code&gt;. This also finds resellers and agencies running ads that point to the same site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verified advertiser name:&lt;/strong&gt; for example &lt;code&gt;Nike, Inc.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advertiser ID or URL:&lt;/strong&gt; paste a link from the Transparency Center.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an input that tracks two competitors' US search ads:&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;"advertiserDomains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"competitor-one.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"competitor-two.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&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;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SEARCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxAdsPerTarget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extractAdCopy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ads come back newest first, so 50 per competitor is plenty for a daily check: anything new shows up at the top. For the very first run you might raise it to a few hundred to get the full history, then lower it again.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Start&lt;/strong&gt;. 100 ads take about a minute.&lt;/p&gt;

&lt;p&gt;A note on ad copy: Google stores a text preview for many ads, but some only as images. When there's no text preview, &lt;code&gt;adText&lt;/code&gt; is empty and you still get the image URL.&lt;/p&gt;

&lt;p&gt;When the run finishes, click &lt;strong&gt;Save as a new task&lt;/strong&gt; and name it something like &lt;code&gt;competitor-ads-daily&lt;/code&gt;. A task is just a saved input you can schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create a Google service account (one time)
&lt;/h2&gt;

&lt;p&gt;To write to your sheet, the sync Actor uses a Google service account: a separate login that can only open sheets you share with it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;, create a project.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services → Library&lt;/strong&gt; and enable &lt;strong&gt;Google Sheets API&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services → Credentials → Create credentials → Service account&lt;/strong&gt;, give it a name, and click &lt;strong&gt;Done&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the service account, go to &lt;strong&gt;Keys → Add key → Create new key → JSON&lt;/strong&gt;. A key file downloads.&lt;/li&gt;
&lt;li&gt;Create a Google Sheet, click &lt;strong&gt;Share&lt;/strong&gt;, and add the service account's email (the &lt;code&gt;client_email&lt;/code&gt; in the key file) as an &lt;strong&gt;Editor&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 3: Send the ads to the sheet automatically
&lt;/h2&gt;

&lt;p&gt;Open your &lt;code&gt;competitor-ads-daily&lt;/code&gt; task, go to &lt;strong&gt;Integrations → Add integration → Apify Actor&lt;/strong&gt;, and pick &lt;a href="https://apify.com/rsiddiq95/google-sheets-sync" rel="noopener noreferrer"&gt;Google Sheets Sync&lt;/a&gt;. Use this input:&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"upsert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"spreadsheet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.google.com/spreadsheets/d/your-sheet-id/edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sheetName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Competitor ads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"datasetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{resource.defaultDatasetId}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"keyColumns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"creativeId"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"omitFields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"previewScriptUrl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resultType"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serviceAccountKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paste the contents of your key file here"&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;What each part does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;upsert&lt;/code&gt; with &lt;code&gt;creativeId&lt;/code&gt; as the key&lt;/strong&gt; means an ad already in the sheet gets its row updated (new &lt;code&gt;lastShownAt&lt;/code&gt;, new &lt;code&gt;daysShown&lt;/code&gt;) instead of being added again. New ads are appended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;{{resource.defaultDatasetId}}&lt;/code&gt;&lt;/strong&gt; is filled in by Apify with the results of each run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Columns you add yourself,&lt;/strong&gt; such as a "Notes" column, are left alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Values are written as plain text,&lt;/strong&gt; so ad text that happens to start with &lt;code&gt;=&lt;/code&gt; can't run as a formula in your sheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is stored encrypted in Apify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Schedule it
&lt;/h2&gt;

&lt;p&gt;Go to &lt;strong&gt;Schedules → Create new&lt;/strong&gt;, set it to run daily (for example at 7 AM), and add the &lt;code&gt;competitor-ads-daily&lt;/code&gt; task. That's it: the scraper runs, and when it finishes, the sync writes the results to your sheet.&lt;/p&gt;

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

&lt;p&gt;Both Actors charge per result, and you don't pay Apify platform fees on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ads: $3 per 1,000 ads. Two competitors at 50 ads each, every day, is about 3,000 ads a month, or about $9.&lt;/li&gt;
&lt;li&gt;Sheets sync: $0.05 per 1,000 rows, so about 15 cents a month for the same data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checking weekly instead of daily cuts that to about $2 a month.&lt;/p&gt;

&lt;p&gt;Lookups that find nothing aren't charged, and Apify's free plan includes monthly credit to try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas to extend it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spot new messaging:&lt;/strong&gt; if &lt;code&gt;firstShownAt&lt;/code&gt; is in column F, add a column with &lt;code&gt;=IF(DATEVALUE(LEFT(F2,10))&amp;gt;TODAY()-7,"NEW","")&lt;/code&gt; to flag ads first shown in the last week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track YouTube creative:&lt;/strong&gt; change &lt;code&gt;platform&lt;/code&gt; to &lt;code&gt;YOUTUBE&lt;/code&gt; and &lt;code&gt;adFormat&lt;/code&gt; to &lt;code&gt;VIDEO&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch your own brand:&lt;/strong&gt; search your domain to see who else runs ads pointing to your site.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and something's missing, open an issue on the Actor page. I read every one.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>marketing</category>
      <category>googleads</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
