<?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: Alom Dev</title>
    <description>The latest articles on DEV Community by Alom Dev (@alom_d).</description>
    <link>https://dev.to/alom_d</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%2F4163866%2Fdec1c77b-1775-4dd8-8a83-f591493de5c9.jpg</url>
      <title>DEV Community: Alom Dev</title>
      <link>https://dev.to/alom_d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alom_d"/>
    <language>en</language>
    <item>
      <title>Getting a competitor's Google ad copy out of the Ads Transparency Center</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:52:55 +0000</pubDate>
      <link>https://dev.to/alom_d/getting-a-competitors-google-ad-copy-out-of-the-ads-transparency-center-2f38</link>
      <guid>https://dev.to/alom_d/getting-a-competitors-google-ad-copy-out-of-the-ads-transparency-center-2f38</guid>
      <description>&lt;p&gt;Google's &lt;a href="https://adstransparency.google.com" rel="noopener noreferrer"&gt;Ads Transparency Center&lt;/a&gt; is one of the most useful free things for&lt;br&gt;
anyone doing PPC. Type in a competitor's domain and you see every ad they've run: Search, YouTube, Shopping, Display,&lt;br&gt;
with dates and regions.&lt;/p&gt;

&lt;p&gt;The annoying part is that you can look but you can't really &lt;em&gt;use&lt;/em&gt; it. There's no export, and if you try to copy the&lt;br&gt;
text of a Search ad, you can't, because it isn't text. Each ad preview is rendered as an image.&lt;/p&gt;

&lt;p&gt;So if you want something like "all the headlines HubSpot ran in Germany last quarter" in a spreadsheet, you're&lt;br&gt;
screenshotting, or you're scraping.&lt;/p&gt;

&lt;p&gt;I went the scraping route. These are my notes, plus the tool I ended up with.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's in there
&lt;/h2&gt;

&lt;p&gt;For each ad you can get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the advertiser (the legal entity that paid, which is often not the brand name you'd expect)&lt;/li&gt;
&lt;li&gt;first and last shown dates, so how long it's been running&lt;/li&gt;
&lt;li&gt;which countries it ran in&lt;/li&gt;
&lt;li&gt;format (text, image, video) and platform (Search, YouTube, Shopping, Maps, Play)&lt;/li&gt;
&lt;li&gt;how many variations of the creative exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Long-running ads are the interesting ones. If a competitor has been paying for the same ad for 200+ days, it's&lt;br&gt;
probably converting.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hard parts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The search goes through advertisers, not ads.&lt;/strong&gt; You don't search ads directly. A domain or brand name resolves to&lt;br&gt;
one or more advertiser IDs first, and big brands often have several (regional entities, agencies). Miss one and you&lt;br&gt;
miss half their ads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The text is in images.&lt;/strong&gt; For Search ads the preview is an image of the ad. To get headline, description, display&lt;br&gt;
URL and sitelinks you need OCR. Shopping ads are easier, the product title is in the data. YouTube ads give you the&lt;br&gt;
video ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Languages.&lt;/strong&gt; I assumed the region filter would tell me the language. It doesn't. Filtering an ad set to Germany I&lt;br&gt;
still got ads in Turkish, because advertisers target people, not just places. So the language has to be detected from&lt;br&gt;
the image itself. In my tests across 11 languages (Japanese, Korean, Chinese, Arabic, Russian, Ukrainian, German,&lt;br&gt;
French, Spanish, Portuguese, Italian) the OCR read 54 of 56 real ads. The two misses were a Japanese ad with a big&lt;br&gt;
photo over the text and an odd Maps format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limits.&lt;/strong&gt; Google starts refusing an IP after roughly 20 to 40 requests in a row. You need rotating proxies,&lt;br&gt;
and the rotation logic matters more than the scraping code.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;Disclosure: I turned this into a &lt;a href="https://apify.com/alom/google-ads-transparency-scraper" rel="noopener noreferrer"&gt;Google Ads Transparency scraper on Apify&lt;/a&gt;.&lt;br&gt;
It does the advertiser lookup, the OCR and the proxy rotation, and gives you a table.&lt;/p&gt;

&lt;p&gt;Every ad a domain runs, with the actual ad copy:&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;"searchQueries"&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;"hubspot.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;"maxResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&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="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;"DE"&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;Some other things it can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"resultType": "advertisers"&lt;/code&gt; gives one row per advertiser with their estimated ad count and format mix, which is a
quick way to size up a market before pulling every ad&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"includeLandingPage": true&lt;/code&gt; follows each ad to its landing page and grabs the title, H1 and main call to action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"politicalAds": true&lt;/code&gt; adds spend ranges, impressions and targeting for political ads where Google publishes them&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"deltaMode": true&lt;/code&gt; on a schedule only returns ads it hasn't seen before, so you get a feed of a competitor's new ads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Price is $2 per 1,000 ads on Apify's free plan, $1 on the bigger plans. Ad copy is an extra $2 to $3 per 1,000&lt;br&gt;
because OCR is the expensive part. Landing pages are extra too, and only charged when the page actually returned&lt;br&gt;
something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ways to use it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pull a competitor's Search ads, sort by days running, and read the top 20 headlines. Those are the ones working.&lt;/li&gt;
&lt;li&gt;Run the same domain monthly with &lt;code&gt;deltaMode&lt;/code&gt; and see what they're testing.&lt;/li&gt;
&lt;li&gt;Compare headlines across countries. Companies often test messaging in one market before rolling it out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you'd rather build it yourself, the short version: resolve advertisers first, OCR the Search previews, detect the&lt;br&gt;
language from the image, and spend most of your effort on proxy rotation.&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>ppc</category>
      <category>webscraping</category>
      <category>python</category>
    </item>
    <item>
      <title>Scraping Bilibili comments: the 3-comment limit was my own fault</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:31:28 +0000</pubDate>
      <link>https://dev.to/alom_d/scraping-bilibili-comments-the-3-comment-limit-was-my-own-fault-358j</link>
      <guid>https://dev.to/alom_d/scraping-bilibili-comments-the-3-comment-limit-was-my-own-fault-358j</guid>
      <description>&lt;p&gt;If you've tried to pull comments from Bilibili (B站) without logging in, you've probably hit the same wall I did:&lt;br&gt;
every video gives you 3 comments. Not 20, not "the first page". Three.&lt;/p&gt;

&lt;p&gt;I believed this for a while. Guests get the top 3, log in for the rest. It sounded plausible, Chinese platforms are&lt;br&gt;
strict about logged-out access, so I wrote it into my README as a known limitation and moved on.&lt;/p&gt;

&lt;p&gt;Then, while working on something else, a request went out without my usual cookies and came back with 20 comments.&lt;/p&gt;
&lt;h2&gt;
  
  
  What was actually going on
&lt;/h2&gt;

&lt;p&gt;To look like a normal browser, I was sending the visitor cookies Bilibili hands out on the first page load (&lt;code&gt;buvid3&lt;/code&gt;&lt;br&gt;
and friends). Most scraping guides tell you to do this, and for search and video details it's fine.&lt;/p&gt;

&lt;p&gt;For the comment API, those cookies mark you as a &lt;em&gt;guest with a session&lt;/em&gt;, and guests with a session get a preview: 3&lt;br&gt;
top comments, then a prompt to log in. A request with &lt;strong&gt;no cookies at all&lt;/strong&gt; is treated like an anonymous API call and&lt;br&gt;
gets normal pages of 20.&lt;/p&gt;

&lt;p&gt;I tested it properly to make sure it wasn't a fluke. Without cookies the paging just kept going: about 1,200 unique&lt;br&gt;
comments across the videos I tried, no login anywhere.&lt;/p&gt;

&lt;p&gt;The fix was literally to stop sending something. That stung a bit.&lt;/p&gt;
&lt;h2&gt;
  
  
  The other things that tripped me up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WBI signing.&lt;/strong&gt; Most Bilibili endpoints want a &lt;code&gt;w_rid&lt;/code&gt; signature built from two keys in the &lt;code&gt;nav&lt;/code&gt; response, mixed&lt;br&gt;
with a fixed shuffle table and a timestamp. Skip it and you get error codes instead of data. The keys rotate, so don't&lt;br&gt;
hardcode them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 1,000-result search cap.&lt;/strong&gt; Search stops at about 50 pages no matter what. If you need more videos for a keyword,&lt;br&gt;
sort by publish date and walk backwards in date windows. Each window gets its own 1,000. Slow, but it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Danmaku (弹幕) pools are capped.&lt;/strong&gt; The bullet comments you see flying over the video are a separate XML or protobuf&lt;br&gt;
feed. For popular videos Bilibili only keeps a slice of them. In one test the video had 128,575 danmaku and the pool&lt;br&gt;
held 3,600. If you're doing "danmaku per minute" analysis, keep that in mind, the counts are a sample, not the whole&lt;br&gt;
thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reply threads.&lt;/strong&gt; The main comment list shows only a few replies under each comment. To get the full thread you page&lt;br&gt;
a second endpoint per root comment. Easy to miss, and it's where the interesting arguments live.&lt;/p&gt;
&lt;h2&gt;
  
  
  If you just want the data
&lt;/h2&gt;

&lt;p&gt;Disclosure: after all of this I packaged it as a &lt;a href="https://apify.com/alom/bilibili-scraper" rel="noopener noreferrer"&gt;Bilibili scraper on Apify&lt;/a&gt;,&lt;br&gt;
so if you don't want to maintain the signing and paging yourself, this does it. No login, no cookies, and the field&lt;br&gt;
names match the most common Bilibili scraper, so switching is easy.&lt;/p&gt;

&lt;p&gt;Comments with full reply threads for a few videos:&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;"video_comments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"videoUrls"&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;"https://www.bilibili.com/video/BV1LraH6qEr5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"av170001"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeReplies"&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;"maxComments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortComments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hot"&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;Search plus comments under each result:&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;"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;"searchQuery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"maxResults"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeComments"&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;"maxComments"&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="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;&lt;code&gt;b23.tv&lt;/code&gt; short links work as input too. Pricing is $4 per 1,000 rows on Apify's free plan and goes down to $2 on the&lt;br&gt;
bigger plans. A danmaku profile (the per-minute reaction summary for a video) is an optional extra at $0.02 per video.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather build it yourself
&lt;/h2&gt;

&lt;p&gt;Totally doable. The short version of everything above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Don't send visitor cookies to the comment API.&lt;/li&gt;
&lt;li&gt;Sign requests with WBI and refresh the keys from &lt;code&gt;nav&lt;/code&gt; regularly.&lt;/li&gt;
&lt;li&gt;Use date windows to get past the search cap.&lt;/li&gt;
&lt;li&gt;Page reply threads separately.&lt;/li&gt;
&lt;li&gt;Treat danmaku counts as a sample on popular videos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's most of the pain. Took me longer than I'd like to admit, mostly because of step 1.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>bilibili</category>
      <category>china</category>
    </item>
    <item>
      <title>Threads search shows logged-out visitors about 20 posts. Here's how I get 60 to 85.</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:31:18 +0000</pubDate>
      <link>https://dev.to/alom_d/threads-search-shows-logged-out-visitors-about-20-posts-heres-how-i-get-60-to-85-2g6l</link>
      <guid>https://dev.to/alom_d/threads-search-shows-logged-out-visitors-about-20-posts-heres-how-i-get-60-to-85-2g6l</guid>
      <description>&lt;p&gt;If you open a Threads search without being logged in, you get one page of results, roughly 20 posts, and that's it.&lt;br&gt;
No "load more", no cursor, nothing. Every no-login Threads scraper I looked at returns about that many per keyword, and&lt;br&gt;
most of them say so in their README.&lt;/p&gt;

&lt;p&gt;I build scrapers, and I wanted more than 20, so I spent some time poking at it. A few notes, in case you're doing the&lt;br&gt;
same.&lt;/p&gt;
&lt;h2&gt;
  
  
  Search: the 20 posts aren't always the same 20
&lt;/h2&gt;

&lt;p&gt;What I noticed first: if you load the same search twice from two different IPs, you don't get identical results.&lt;br&gt;
There's overlap, but the ranking shifts. And there are actually three different pages that answer a keyword query:&lt;br&gt;
the normal search, the tag search (&lt;code&gt;serp_type=tags&lt;/code&gt;), and the &lt;code&gt;/tag/&amp;lt;name&amp;gt;&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;So instead of trying to paginate (you can't, logged out), you sample. Load each of the three surfaces from fresh IPs,&lt;br&gt;
merge by post ID, and stop when three loads in a row bring nothing new. In my tests that lands on &lt;strong&gt;60 to 85 unique&lt;br&gt;
posts per keyword&lt;/strong&gt;. It's not unlimited, and I'd be lying if I said it was, but it's 3 to 4 times what one page gives&lt;br&gt;
you.&lt;/p&gt;

&lt;p&gt;If you need it sorted newest first, sort what you collected. That's not the same as Threads' real "Recent" tab, which&lt;br&gt;
is only for logged-in users, and it's worth being honest about that difference if you sell data to someone.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Replies and Reposts tabs work logged out
&lt;/h2&gt;

&lt;p&gt;My notes said these needed a login. They don't. The profile page loads them through GraphQL queries with a flag that&lt;br&gt;
the logged-out page just doesn't send by default. Send it and you get the account's replies (with the post they were&lt;br&gt;
replying to) and their reposts (with who reposted and when). That's handy for influencer research: what someone says in&lt;br&gt;
replies is often more revealing than their posts.&lt;/p&gt;
&lt;h2&gt;
  
  
  The "user not found" false alarm
&lt;/h2&gt;

&lt;p&gt;This one cost me a while. Under load, some real accounts came back as "not found". Threads answers an unknown username&lt;br&gt;
by redirecting to the login page. It also redirects a perfectly fine username to the login page when your IP is being&lt;br&gt;
throttled. Same response, different meaning.&lt;/p&gt;

&lt;p&gt;The fix I ended up with: when a profile redirects to login, load a profile that definitely exists from the same IP. If&lt;br&gt;
that one is walled too, the IP is the problem, so rotate and retry. Only if the control profile loads fine is the&lt;br&gt;
username really unknown.&lt;/p&gt;

&lt;p&gt;Funny side note: the accounts that kept "failing" in my test list (bbcnews, vogue, samsung, theverge) turned out to&lt;br&gt;
genuinely not exist on Threads under those handles. The scraper was right and my test data was wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  E-mails in bios
&lt;/h2&gt;

&lt;p&gt;A lot of business accounts put a contact e-mail in their bio. If you search accounts by keyword and open each profile,&lt;br&gt;
you can pull those out, plus phone numbers and bio links. It only works for people who chose to publish one, which is&lt;br&gt;
also why it's fine to use, but it's surprisingly effective for finding contacts in a niche.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;Disclosure: I packaged all of this as a &lt;a href="https://apify.com/alom/threads-scraper" rel="noopener noreferrer"&gt;Threads scraper on Apify&lt;/a&gt;. Profiles,&lt;br&gt;
posts, replies, the Replies and Reposts tabs, keyword and hashtag search, account search with bio contacts, and a&lt;br&gt;
monitoring mode that only returns new posts on a schedule. No login. It's $2.50 per 1,000 results on Apify's free plan&lt;br&gt;
and $1.50 on the bigger plans.&lt;/p&gt;

&lt;p&gt;Example, hashtag plus keyword search:&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;"searchQueries"&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;"#buildinpublic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indie hacker"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPosts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requireKeywordMatch"&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;If you already use another Threads scraper, the main input fields (&lt;code&gt;usernames&lt;/code&gt;, &lt;code&gt;searchQueries&lt;/code&gt;, &lt;code&gt;maxPosts&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;postedAfter&lt;/code&gt;) are named the same, so trying it is just swapping the Actor ID.&lt;/p&gt;

&lt;p&gt;There's also an optional mode where you paste the cookie of your own logged-in account. Then search uses the real&lt;br&gt;
Recent tab and pages through it like the app does (300 recent posts for "news" in my test, versus 155 without). It's at&lt;br&gt;
your own risk, use a spare account if you try it.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>threads</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>Google Trends in Python after pytrends died</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:10:35 +0000</pubDate>
      <link>https://dev.to/alom_d/google-trends-in-python-after-pytrends-died-1e16</link>
      <guid>https://dev.to/alom_d/google-trends-in-python-after-pytrends-died-1e16</guid>
      <description>&lt;p&gt;If you search for this error you'll find a lot of unhappy people:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pytrends.exceptions.TooManyRequestsError: The request failed: Google returned a response with code 429
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The usual advice is to add sleeps, then retries, then proxies. None of it really fixes it, because the repo was&lt;br&gt;
archived in April 2025 and nobody is coming to fix it. Google changed how the Trends site loads its data, and the&lt;br&gt;
library's pattern (lots of quick requests from one IP) gets rate limited almost right away now.&lt;/p&gt;

&lt;p&gt;I needed Trends data for a project anyway, so I went down the rabbit hole. Here's what I found, in case you're in the&lt;br&gt;
same spot.&lt;/p&gt;

&lt;p&gt;Quick disclosure before anything else: I ended up building a hosted scraper for this and I use it below. I'll try to&lt;br&gt;
be fair about the other options, because for some people they're the better choice.&lt;/p&gt;
&lt;h2&gt;
  
  
  The options, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Google's official API.&lt;/strong&gt; Google announced an official Trends API in 2025. It's in alpha and you have to apply.&lt;br&gt;
If you get access, use it. Last time I checked it was still invite-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write it yourself.&lt;/strong&gt; The site talks to internal JSON endpoints: &lt;code&gt;/trends/api/explore&lt;/code&gt; gives you widget tokens,&lt;br&gt;
then you call &lt;code&gt;/trends/api/widgetdata/...&lt;/code&gt; with each token. I did this, and it works, but expect to deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first request getting a 429 on purpose before Google sets the &lt;code&gt;NID&lt;/code&gt; cookie&lt;/li&gt;
&lt;li&gt;tokens that only work for the exact parameters they were issued for&lt;/li&gt;
&lt;li&gt;IP blocks. From a laptop you hit them in minutes. Datacenter IPs get blocked in ranges, so you need rotating
proxies and some fallback logic&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;)]}'&lt;/code&gt; prefix on every response, and parameters that change without notice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It took me a few days to get it stable, and it still needs fixing every couple of months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use something hosted.&lt;/strong&gt; That's what the rest of this post does. I use my &lt;a href="https://apify.com/alom/google-trends-scraper" rel="noopener noreferrer"&gt;Google Trends Scraper on Apify&lt;/a&gt;&lt;br&gt;
from Python with the official client. It's $1.50 per 1,000 search terms on the free plan and goes down to $0.50 on&lt;br&gt;
the bigger plans. Proxies are included, and Apify's free plan gives you some monthly credit, which is enough to play with.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"apify-client&amp;gt;=3"&lt;/span&gt; pandas
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;APIFY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...   &lt;span class="c"&gt;# Apify Console &amp;gt; Settings &amp;gt; API &amp;amp; Integrations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This helper runs the scraper and loads the flat CSV it saves (one row per data point, which is what I usually want&lt;br&gt;
for pandas):&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;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;google_trends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run the Google Trends scraper and return one row per data point.&lt;/span&gt;&lt;span class="sh"&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;alom/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="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&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;key_value_store&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_key_value_store_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESULTS_FLAT.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&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="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;return&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;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Interest over time
&lt;/h2&gt;

&lt;p&gt;This is the one most people used pytrends for:&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;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;google_trends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchTerms&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;web scraping&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;timeRange&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;timeline&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;section&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeline&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;date&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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;timeline&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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;timeline&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time ranges are the same strings the website uses: &lt;code&gt;now 1-H&lt;/code&gt;, &lt;code&gt;now 7-d&lt;/code&gt;, &lt;code&gt;today 3-m&lt;/code&gt;, &lt;code&gt;today 5-y&lt;/code&gt;, &lt;code&gt;all&lt;/code&gt;. For exact&lt;br&gt;
dates use &lt;code&gt;customTimeRange="2024-01-01 2024-12-31"&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Comparing terms
&lt;/h2&gt;

&lt;p&gt;Trends values are 0 to 100 &lt;em&gt;inside one request&lt;/em&gt;, so two separate requests aren't comparable. Put the terms in one&lt;br&gt;
comparison instead:&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;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;google_trends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchTerms&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;tea, coffee, matcha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isMultiple&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;GB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeRange&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 3-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;wide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeline&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;pivot_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;term&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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;wide&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google caps this at 5 terms. If you need more, there's a &lt;code&gt;normalizeAcrossQueries&lt;/code&gt; option that chains several 5-term&lt;br&gt;
comparisons through one shared anchor term and rescales them onto one scale. Pick your most searched term as the&lt;br&gt;
anchor. With a tiny anchor the rounding gets ugly, and the output flags that with &lt;code&gt;lowPrecision&lt;/code&gt;. It costs a bit&lt;br&gt;
extra per comparison because it really is several requests under the hood.&lt;/p&gt;
&lt;h2&gt;
  
  
  States, cities and US metro areas
&lt;/h2&gt;

&lt;p&gt;pytrends gave you one resolution per call. Here you get states, cities and metro areas (DMA) in the same result:&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;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;google_trends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchTerms&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;coffee&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;metros&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&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;code&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;value&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;metros&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;section&lt;/code&gt; column is one of &lt;code&gt;timeline&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;subregion&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;metro&lt;/code&gt;, &lt;code&gt;relatedQueryTop&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;relatedQueryRising&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Related queries
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;google_trends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchTerms&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;web scraping&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;rising&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;section&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;relatedQueryRising&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;label&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;formattedValue&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;rising&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One thing that confused me: rising queries are sometimes weird. For "coffee" I once got "pet care tips". I assumed it&lt;br&gt;
was a parsing bug and spent an evening on it. It wasn't. The Trends website shows the exact same list. Google's data&lt;br&gt;
is just like that sometimes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trending Now
&lt;/h2&gt;

&lt;p&gt;The newer page at trends.google.com/trending never had a pytrends equivalent. This gets the list per country (or US&lt;br&gt;
state) with search volume:&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;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;google_trends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trendingNow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trendingGeos&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;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="n"&gt;trendingHours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;24&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;trendingSort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxTrendsPerGeo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchVolume&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;increasePercentage&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;categories&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schedule it hourly with &lt;code&gt;onlyNewSinceLastRun=True&lt;/code&gt; and each run only returns trends you haven't seen yet. It's&lt;br&gt;
basically a cheap alert feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather have JSON
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&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;alom/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;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;web scraping&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&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;item&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interestOverTime_timelineData&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;points&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;h2&gt;
  
  
  One limitation
&lt;/h2&gt;

&lt;p&gt;Related &lt;em&gt;topics&lt;/em&gt; come back empty. Google only shows them to signed-in users now, so pytrends and every logged-out&lt;br&gt;
tool lose them too. Related &lt;em&gt;queries&lt;/em&gt; still work fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  So which one?
&lt;/h2&gt;

&lt;p&gt;If you need a couple of charts, just download the CSV from the Trends website, honestly. If you get into the official&lt;br&gt;
API alpha, use that. If you have something scheduled or bulk, or it sits inside a pipeline, paying someone else to&lt;br&gt;
deal with the 429s is cheaper than doing it yourself, at least it was for me.&lt;/p&gt;

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