<?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: Meridian Labs Software</title>
    <description>The latest articles on DEV Community by Meridian Labs Software (@leewilliam200).</description>
    <link>https://dev.to/leewilliam200</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%2F4129512%2Ff7ab0c58-2d50-4e62-952d-7bed8544eab4.png</url>
      <title>DEV Community: Meridian Labs Software</title>
      <link>https://dev.to/leewilliam200</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leewilliam200"/>
    <language>en</language>
    <item>
      <title>Google Trends in Python after pytrends: bulk keywords without 429 errors</title>
      <dc:creator>Meridian Labs Software</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:43:59 +0000</pubDate>
      <link>https://dev.to/leewilliam200/google-trends-in-python-after-pytrends-bulk-keywords-without-429-errors-5jd</link>
      <guid>https://dev.to/leewilliam200/google-trends-in-python-after-pytrends-bulk-keywords-without-429-errors-5jd</guid>
      <description>&lt;p&gt;If you've used pytrends for anything bigger than a handful of keywords, you know how it goes. The first ten requests are fine, then &lt;code&gt;TooManyRequestsError: The request failed: Google returned a response with code 429&lt;/code&gt;, then you add &lt;code&gt;time.sleep(60)&lt;/code&gt;, then you start looking at proxy providers.&lt;/p&gt;

&lt;p&gt;And now there's a second problem. The pytrends repo on GitHub was archived in April 2025, and its last code push was in August 2024. It still installs, but nobody is fixing it when Google changes something.&lt;/p&gt;

&lt;p&gt;So what are the options in 2026?&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: keep pytrends and slow down
&lt;/h2&gt;

&lt;p&gt;This still works for small jobs. Keep one keyword per request, sleep between calls, and back off hard when you see a 429. For 20 keywords a week it's fine. For 500 keywords, or anything on a schedule, you'll spend more time babysitting it than using the data. You're also sending every request from your own IP, which is why the 429s show up so fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Google's official Trends API
&lt;/h2&gt;

&lt;p&gt;Google announced an official Trends API in July 2025. At the time of writing it's an alpha for a limited group of testers, and there's no public sign-up. Worth applying if you have a real use case, but you can't build on it today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: a hosted scraper you call like an API
&lt;/h2&gt;

&lt;p&gt;This is what I ended up building, so take this section with that in mind: I made the &lt;a href="https://apify.com/meridianlabs/google-trends-scraper" rel="noopener noreferrer"&gt;Google Trends Scraper&lt;/a&gt; on Apify for exactly this problem. You send a list of keywords, it handles the proxies and retries on its side, and you get one row per keyword back. You never deal with a 429.&lt;/p&gt;

&lt;p&gt;Here's the whole thing in Python. &lt;code&gt;pip install apify-client&lt;/code&gt;, then grab an API token from the Apify Console (Settings &amp;gt; API &amp;amp; Integrations).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;run&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;meridianlabs/google-trends-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;keywords&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;halloween costume&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;pickleball&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;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;past_5_years&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;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="n"&gt;default_dataset_id&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;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;|&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;summary&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;text&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 apify-client 3.x. On 2.x use &lt;code&gt;run["defaultDatasetId"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I ran exactly that today (30 September 2026). It took about 20 seconds for all four:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;halloween costume | Interest is rising (+391% vs the previous 13 weeks); up 3% year on year. Peak: October 2021. Seasonal: peaks every October. Fastest-rising related search: '2024 halloween costume ideas' (Breakout).
pickleball | Interest is falling (-29% vs the previous 13 weeks); down 2% year on year. Peak: April 2026. Fastest-rising related search: 'joola pickleball paddle' (Breakout).
air fryer | Interest is falling (-20% vs the previous 13 weeks); up 11% year on year. Peak: January 2022. Fastest-rising related search: 'shop air fryer deals' (Breakout).
standing desk | Interest is falling (-66% vs the previous 13 weeks); up 29% year on year. Peak: April 2026. Fastest-rising related search: 'vernal standing desk' (Breakout).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Google Trends samples its data, so a second run a few minutes later gave standing desk -65% and +31%. Expect the odd point of drift between runs; the direction doesn't change.)&lt;/p&gt;

&lt;p&gt;The raw data is still there. Each row has the full &lt;code&gt;interestOverTime&lt;/code&gt; series, &lt;code&gt;interestByRegion&lt;/code&gt; and &lt;code&gt;relatedQueries&lt;/code&gt; (top and rising), same as pytrends. The summary line is the part I added, because with 500 keywords I don't want 500 charts. I want to know which ones are going up right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving pytrends code over
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pytrends&lt;/th&gt;
&lt;th&gt;This&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interest_over_time()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;row["interestOverTime"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interest_by_region()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;row["interestByRegion"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;related_queries()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;row["relatedQueries"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timeframe='today 5-y'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"timeRange": "past_5_years"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;geo='US'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"geo": "US"&lt;/code&gt; (same codes, including &lt;code&gt;US-CA&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;build_payload(kw_list=[...5 terms])&lt;/code&gt; to compare&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"compareKeywords": true&lt;/code&gt;, in groups of up to 5 (Google's limit), as many groups as you like&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every data type comes back in one lookup, so there's no separate request per method like with pytrends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering 500 keywords down to the ones that matter
&lt;/h2&gt;

&lt;p&gt;The summary is also split into plain fields, so you can filter without parsing text: &lt;code&gt;momentum&lt;/code&gt; (rising, falling or stable), &lt;code&gt;recentChangePct&lt;/code&gt;, &lt;code&gt;yearOnYearChangePct&lt;/code&gt;, &lt;code&gt;seasonal&lt;/code&gt;, &lt;code&gt;seasonalPeakMonth&lt;/code&gt;, &lt;code&gt;peakDate&lt;/code&gt; and &lt;code&gt;topRisingQuery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I put a small script on GitHub that reads &lt;code&gt;keywords.txt&lt;/code&gt;, writes everything to a CSV and prints the rising and seasonal ones: &lt;a href="https://github.com/leewilliam200/pytrends-alternative" rel="noopener noreferrer"&gt;pytrends-alternative&lt;/a&gt;. The core of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rows&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;keyword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&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;i&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="n"&gt;default_dataset_id&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;rising&lt;/span&gt; &lt;span class="o"&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;keyword&lt;/span&gt;&lt;span class="sh"&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;rows&lt;/span&gt; &lt;span class="k"&gt;if&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;momentum&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;rising&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;seasonal&lt;/span&gt; &lt;span class="o"&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;keyword&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;seasonalPeakMonth&lt;/span&gt;&lt;span class="sh"&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;rows&lt;/span&gt; &lt;span class="k"&gt;if&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;seasonal&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;On my four test keywords that prints &lt;code&gt;Rising now: halloween costume&lt;/code&gt; and &lt;code&gt;Seasonal: halloween costume (October)&lt;/code&gt;, which is what you'd hope for at the end of September.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs, and what it can't do
&lt;/h2&gt;

&lt;p&gt;It's $0.005 per keyword lookup, so 1,000 keywords is $5, and failed lookups aren't charged. Apify's free plan includes $5 of usage a month, which covers about 1,000 lookups, so you can try it on a real list before paying anything.&lt;/p&gt;

&lt;p&gt;Things it won't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Related topics.&lt;/strong&gt; Google doesn't return these to automated lookups, so only related searches (queries) come back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;City-level regions.&lt;/strong&gt; Countries, states and US metro areas work. Cities don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absolute search volume.&lt;/strong&gt; Google Trends is a 0 to 100 index, not search counts. If you need monthly volumes, you need a Keyword Planner-type source instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand-new or tiny terms&lt;/strong&gt; can come back as &lt;code&gt;no_data&lt;/code&gt;. That's Google having nothing, not an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're still on pytrends and it's working for your volume, there's no reason to switch. If you're fighting 429s or building something that runs on a schedule, this is the setup I'd use.&lt;/p&gt;

</description>
      <category>python</category>
      <category>seo</category>
      <category>datascience</category>
      <category>api</category>
    </item>
    <item>
      <title>pytrends is dead. Here's a maintained drop-in replacement</title>
      <dc:creator>Meridian Labs Software</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:23:50 +0000</pubDate>
      <link>https://dev.to/leewilliam200/pytrends-is-dead-heres-a-maintained-drop-in-replacement-37ih</link>
      <guid>https://dev.to/leewilliam200/pytrends-is-dead-heres-a-maintained-drop-in-replacement-37ih</guid>
      <description>&lt;h2&gt;
  
  
  pytrends stopped getting updates in April 2025
&lt;/h2&gt;

&lt;p&gt;If you've used pytrends to pull Google Trends data, you've probably noticed it's gotten flakier over the last year. That's because &lt;a href="https://github.com/GeneralMills/pytrends" rel="noopener noreferrer"&gt;the repo was archived by its owner in April 2025&lt;/a&gt; — no more fixes, ever.&lt;/p&gt;

&lt;p&gt;The core problem it never solved well: pytrends fires every request straight from your own IP with no cookie handling, so as soon as you run more than a handful of lookups you get hit with Google's &lt;code&gt;429 Too Many Requests&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;google-trends-api-python&lt;/code&gt; is a drop-in replacement that keeps pytrends' exact interface (&lt;code&gt;TrendReq&lt;/code&gt;, &lt;code&gt;build_payload&lt;/code&gt;, &lt;code&gt;interest_over_time()&lt;/code&gt;, all the same DataFrame shapes) but routes requests through a hosted backend instead of your machine. Proxies and retries run server-side, so there's nothing to handle yourself.&lt;/p&gt;

&lt;p&gt;Swap two lines:&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;google_trends_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TrendReq&lt;/span&gt;  &lt;span class="c1"&gt;# was: from pytrends.request import TrendReq
&lt;/span&gt;
&lt;span class="n"&gt;pytrends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrendReq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en-US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apify_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;pytrends&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_payload&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="n"&gt;timeframe&lt;/span&gt;&lt;span class="o"&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;geo&lt;/span&gt;&lt;span class="o"&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="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytrends&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interest_over_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else — &lt;code&gt;interest_by_region()&lt;/code&gt;, &lt;code&gt;related_queries()&lt;/code&gt;, timeframe strings, &lt;code&gt;gprop&lt;/code&gt; — works the same as pytrends. There's a full mapping table in the README for anything that changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: bulk mode, and a summary() pytrends never had
&lt;/h2&gt;

&lt;p&gt;For more than a handful of keywords there's a second class that batches hundreds of lookups in one call:&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;google_trends_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GoogleTrends&lt;/span&gt;

&lt;span class="n"&gt;gt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleTrends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_cost_usd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&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;halloween costume&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;pool float&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;ski jacket&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;sunscreen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;geo&lt;/span&gt;&lt;span class="o"&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="n"&gt;timeframe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;today 5-y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;()[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;momentum&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;yearOnYearChangePct&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;seasonal&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;seasonalPeakMonth&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;&lt;code&gt;summary()&lt;/code&gt; gives you momentum, year-on-year change, seasonality and peak month per keyword, computed server-side — nothing pytrends had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bit: it's not free
&lt;/h2&gt;

&lt;p&gt;This isn't a "free API" trick. It calls a hosted Actor on Apify ($0.005 per keyword lookup), and Apify's free plan gives you $5/month, so roughly 1,000 lookups before you'd pay anything. Figured that's worth saying upfront rather than people finding out after installing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install: &lt;code&gt;pip install "google-trends-api-python[pandas] @ git+https://github.com/leewilliam200/google-trends-api-python"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Source, MIT licensed: &lt;a href="https://github.com/leewilliam200/google-trends-api-python" rel="noopener noreferrer"&gt;https://github.com/leewilliam200/google-trends-api-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hosted backend: &lt;a href="https://apify.com/meridianlabs/google-trends-scraper" rel="noopener noreferrer"&gt;https://apify.com/meridianlabs/google-trends-scraper&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hit a case the pytrends mapping doesn't cover, open an issue — happy to take PRs too.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>webscraping</category>
      <category>api</category>
    </item>
    <item>
      <title>I built a plain-text "art director" so my AI images stop changing when I edit them</title>
      <dc:creator>Meridian Labs Software</dc:creator>
      <pubDate>Thu, 17 Sep 2026 09:13:37 +0000</pubDate>
      <link>https://dev.to/leewilliam200/i-built-a-plain-text-art-director-so-my-ai-images-stop-changing-when-i-edit-them-7o8</link>
      <guid>https://dev.to/leewilliam200/i-built-a-plain-text-art-director-so-my-ai-images-stop-changing-when-i-edit-them-7o8</guid>
      <description>&lt;p&gt;If you make images with AI, you know the specific pain: the first generation is easy. Editing it is where everything falls apart.&lt;/p&gt;

&lt;p&gt;I make YouTube thumbnails and playlist covers. I'd get a result I liked, ask to change one small thing — "make the jacket blue" — and get back a completely different picture. New face. New background. New lighting. The one thing I asked for, plus ten I didn't.&lt;/p&gt;

&lt;p&gt;After the hundredth time, I realized the problem isn't the model — it's the prompt. When you say "change the jacket," the model has no idea what you want to &lt;em&gt;keep&lt;/em&gt;, so it re-rolls the whole scene. The fix is to spell out, every time, what stays locked and what changes, in language the model actually follows.&lt;/p&gt;

&lt;p&gt;That's tedious by hand, so I turned it into a plain-text bundle you paste into ChatGPT or Claude. You describe what you want; it writes the &lt;em&gt;direction&lt;/em&gt; — one ready-to-paste prompt that says exactly what to preserve and what to change. No code, no API, nothing to install.&lt;/p&gt;

&lt;p&gt;Clearest example — same street photo, one instruction ("add falling snow"):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvbsk6v790ec8rt8i0hsr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvbsk6v790ec8rt8i0hsr.jpg" alt="snow before and after" width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The snow appears; the houses, the road, the parked vans stay exactly as they were. Most tools hand you a brand-new street.&lt;/p&gt;

&lt;p&gt;It can also diagnose a render that came out wrong — what's off, why, what to change, and what to keep:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhib600hcsniv5w13foh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhib600hcsniv5w13foh.gif" alt="diagnosis panel" width="719" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works (no magic):&lt;/strong&gt; it's plain-text reasoning, not a model or an integration. It works out how much detail a request needs, separates what's immutable from what you're changing, and phrases the result for the renderer (ChatGPT / GPT Image by default). Because ChatGPT re-renders the whole frame instead of inpainting, the trick is to restate the locked parts explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest limits:&lt;/strong&gt; it's tuned and tested on ChatGPT / GPT Image; the Midjourney, Gemini, Ideogram and FLUX profiles are starting points, not verified. And no prompt makes a whole-frame renderer do true local edits — it's a strong mitigation, not a guarantee.&lt;/p&gt;

&lt;p&gt;The free version is on GitHub and takes about a minute to try. There's a $19 one-time Pro bundle with the full preservation system, the edit-state ledger, precise single-element edits and deep diagnosis — but the free one shows the whole idea.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free on GitHub:&lt;/strong&gt; &lt;a href="https://github.com/leewilliam200/visual-director-lite" rel="noopener noreferrer"&gt;https://github.com/leewilliam200/visual-director-lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would genuinely love feedback — especially where the "keep everything else the same" logic breaks for you. What's the one thing you always want an AI editor to preserve that it never does?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>chatgpt</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
