<?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>How to scrape Threads posts without logging in (Python and JavaScript)</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Thu, 15 Oct 2026 11:54:20 +0000</pubDate>
      <link>https://dev.to/alom_d/how-to-scrape-threads-posts-without-logging-in-python-and-javascript-134h</link>
      <guid>https://dev.to/alom_d/how-to-scrape-threads-posts-without-logging-in-python-and-javascript-134h</guid>
      <description>&lt;p&gt;Disclosure first: I built the scraper this post uses (&lt;a href="https://apify.com/alom/threads-scraper" rel="noopener noreferrer"&gt;Threads Scraper on Apify&lt;/a&gt;),&lt;br&gt;
so take my enthusiasm with a grain of salt. The code below is real and I ran every snippet before posting it. The&lt;br&gt;
outputs are what I got on October 15.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why not just use the official API?
&lt;/h2&gt;

&lt;p&gt;Meta does have a Threads API, but it needs a Meta developer app and an access token, and it's built mostly around your&lt;br&gt;
own account. If you want "the last 50 posts of some public account" or "what people posted about espresso this week",&lt;br&gt;
you end up scraping the website. A logged-out visitor can see quite a lot on threads.com: profiles, posts, the replies&lt;br&gt;
under a post, a keyword search. That's what the scraper collects, without any account, so there's nothing to get banned.&lt;/p&gt;

&lt;p&gt;You need an Apify account (the free plan includes $5 of usage a month, which at $2.50 per 1,000 results is about 2,000&lt;br&gt;
posts) and your API token from Settings → API &amp;amp; Integrations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Python: an account's posts with view counts
&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;apify-client
&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;your_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;alom/threads-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;usernames&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;mosseri&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;maxPosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeViews&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="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="k"&gt;if&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;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="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;likeCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;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;viewCount&lt;/span&gt;&lt;span class="sh"&gt;"&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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Output (likes, views, text):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1018 320506 Big day for the Muse team with the introduction of Muse Wond
372 42105 As is often the case with features like this one, this post
216 31669 Once iOS27 reaches a majority of our users though I think we
156 14703 This is rad.
340 19718 Three DM settings worth knowing about. Let me know if there
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha if you copy older tutorials: &lt;code&gt;apify-client&lt;/code&gt; 3.x returns a &lt;code&gt;Run&lt;/code&gt; object, so it's &lt;code&gt;run.default_dataset_id&lt;/code&gt;, not&lt;br&gt;
&lt;code&gt;run["defaultDatasetId"]&lt;/code&gt;. The dictionary version throws &lt;code&gt;'Run' object is not subscriptable&lt;/code&gt;. I hit that myself while&lt;br&gt;
writing this.&lt;/p&gt;

&lt;p&gt;About &lt;code&gt;includeViews&lt;/code&gt;: Threads shows a view count ("320K views") on the post page, not in the feed, so the scraper has&lt;br&gt;
to open each post. That adds roughly 1.5 seconds per post, which is why it's off by default. For a post you pass by URL&lt;br&gt;
you get the view count anyway.&lt;/p&gt;

&lt;p&gt;The first row of the dataset is the profile itself (&lt;code&gt;type: "profile"&lt;/code&gt;) with followers, bio, bio links and verification.&lt;br&gt;
Set &lt;code&gt;"mode": "profile"&lt;/code&gt; if that's all you want.&lt;/p&gt;
&lt;h2&gt;
  
  
  Python: keyword search, newest first, with filters
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;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;alom/threads-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;searchQueries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;espresso&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;#coffee&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;searchSort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxPosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minLikes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;languages&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;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posts&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;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="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;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;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;username&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;likeCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;56 posts
2026-10-15T00:41:07.000Z riim__79 33 Espresso ☕️.
2026-10-14T22:55:40.000Z andi.vdb 10 Depresso in need of espresso
2026-10-14T17:25:44.000Z knowyourworthfl 295 Yeah a local coffee shop said they could not make me a doubl
2026-10-09T20:59:18.000Z j_x84 205 Espresso Alfredo❣️🪄.
2026-09-30T03:54:21.000Z alanchunggg 13 Espresso ☕️
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A few honest notes about search:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logged out, Threads shows one page of about 20 results per search. The scraper samples it several times and merges,
which usually gets 60 to 85 posts per keyword. It is not every post ever written about espresso.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;searchSort: "recent"&lt;/code&gt; sorts what it found newest first. It isn't Threads' real "Recent" tab, which needs a login.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;languages&lt;/code&gt; uses the language Threads reports when it has one, otherwise a guess from the text. A post like
"Espresso ☕️." is too short to tell, and those are kept rather than dropped. That's why one slipped in above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minLikes&lt;/code&gt; and &lt;code&gt;minReplies&lt;/code&gt; are applied before the results are saved, and you only pay for the rows you get.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  JavaScript: the replies under a post
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;apify-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApifyClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apify-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIFY_TOKEN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alom/threads-scraper&lt;/span&gt;&lt;span class="dl"&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="na"&gt;postUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.threads.com/@mosseri/post/DdUI1VfG_42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;maxReplies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reply&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; replies`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;likeCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That printed &lt;code&gt;100 replies&lt;/code&gt;, sorted the way Threads sorts them ("top"). Set &lt;code&gt;repliesSort: "recent"&lt;/code&gt; for newest first.&lt;br&gt;
Each reply has &lt;code&gt;parentPostId&lt;/code&gt;, so you can rebuild the reply chains, and &lt;code&gt;isLikedByAuthor&lt;/code&gt; tells you which replies the&lt;br&gt;
original poster liked. If replies are all you need, there's a separate&lt;br&gt;
&lt;a href="https://apify.com/alom/threads-replies-scraper" rel="noopener noreferrer"&gt;Threads Replies Scraper&lt;/a&gt; with a simpler input. Logged out, Threads&lt;br&gt;
shows roughly the first 1,000 replies of a post, so that's the ceiling.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using it from Claude, ChatGPT or Cursor (MCP)
&lt;/h2&gt;

&lt;p&gt;You don't have to write code at all if an AI assistant should do the asking. Apify runs an MCP server, and you can&lt;br&gt;
limit it to one Actor with the &lt;code&gt;tools&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp.apify.com/?tools=alom/threads-scraper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude (claude.ai or Desktop):&lt;/strong&gt; Settings → Connectors → Add custom connector, paste the URL, then sign in to Apify
when it asks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; &lt;code&gt;claude mcp add --transport http threads "https://mcp.apify.com/?tools=alom/threads-scraper"&lt;/code&gt;, then
run &lt;code&gt;/mcp&lt;/code&gt; to sign in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor and other clients with an &lt;code&gt;mcpServers&lt;/code&gt; config:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&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;"mcpServers"&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;"threads"&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;"url"&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://mcp.apify.com/?tools=alom/threads-scraper"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent gets a tool called &lt;code&gt;alom--threads-scraper&lt;/code&gt; with the same inputs as above. Then you just ask, for example:&lt;br&gt;
"Get the last 20 posts of @mosseri with view counts and tell me which topics got the most views." Two tips: keep&lt;br&gt;
&lt;code&gt;maxPosts&lt;/code&gt; small (10 to 30) so the answer fits the model's context, and use &lt;code&gt;minLikes&lt;/code&gt; and &lt;code&gt;languages&lt;/code&gt; so it isn't&lt;br&gt;
reading noise. Every call is a normal run on your Apify account, at the normal price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and limits
&lt;/h2&gt;

&lt;p&gt;$2.50 per 1,000 results on Apify's free plan, $1.50 on the bigger plans, plus half a cent per run. Proxies and compute&lt;br&gt;
are included in that. What it can't do logged out: follower and following lists, and post counts. A deleted post or an&lt;br&gt;
unknown username fails only that input, not the whole run.&lt;/p&gt;

&lt;p&gt;If something doesn't work for your case, open an issue on the Actor page. I read all of them.&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>webscraping</category>
      <category>threads</category>
    </item>
    <item>
      <title>I checked how many Threads accounts list an email in their bio. It's about 4%.</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:44:04 +0000</pubDate>
      <link>https://dev.to/alom_d/i-checked-how-many-threads-accounts-list-an-email-in-their-bio-its-about-4-4pgh</link>
      <guid>https://dev.to/alom_d/i-checked-how-many-threads-accounts-list-an-email-in-their-bio-its-about-4-4pgh</guid>
      <description>&lt;p&gt;I built a small tool that finds Threads accounts by keyword: type "coffee shop" or "marketing agency" and get a table&lt;br&gt;
of accounts with followers, bio and links. My plan was to call it an email finder, because that's what people ask&lt;br&gt;
for. Then I looked at the results and renamed it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The number
&lt;/h2&gt;

&lt;p&gt;Threads lets people write whatever they want in their bio, and some business accounts put a contact email there.&lt;br&gt;
"Some" turned out to be fewer than I expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"marketing" and "coffee shop" together returned 59 accounts. &lt;strong&gt;2&lt;/strong&gt; had an email in the bio and &lt;strong&gt;4&lt;/strong&gt; had a phone
number.&lt;/li&gt;
&lt;li&gt;Across 111 different accounts from several keywords, about &lt;strong&gt;4%&lt;/strong&gt; had an email.&lt;/li&gt;
&lt;li&gt;In a run that kept only accounts with an email, it opened 277 profiles to find &lt;strong&gt;5&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So between 2% and 4%, depending on the niche. Agencies and local businesses list one more often than personal&lt;br&gt;
accounts. If someone sells you "emails of every Threads account in your niche", that's not what's on the page.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is on the page, and is more useful
&lt;/h2&gt;

&lt;p&gt;For every account you do get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Followers and verification.&lt;/strong&gt; Enough to sort a list and decide who is worth a closer look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bio text.&lt;/strong&gt; People describe what they do in one line, which is the fastest filter there is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bio links.&lt;/strong&gt; Most business accounts link their website, a booking page or a link-in-bio page. That is where the
contact form and the real email usually are.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice the bio link is the lead, not the email. A list of 300 accounts sorted by followers, with their websites,&lt;br&gt;
is a morning of work done in a few minutes.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the search behaves
&lt;/h2&gt;

&lt;p&gt;A few things I learned about Threads' account search without logging in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It matches the keyword against usernames, display names and bios.&lt;/li&gt;
&lt;li&gt;It returns up to about 100 accounts per keyword. For a bigger list, use several specific keywords
("coffee shop berlin", "coffee roaster", "cafe owner") instead of one broad one.&lt;/li&gt;
&lt;li&gt;The follower count and bio links are not in the search result. Each account has to be opened once to get them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  A simple way to use it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write down 5 to 10 keywords your customers would use to describe themselves.&lt;/li&gt;
&lt;li&gt;Collect the accounts for all of them. Duplicates across keywords are removed.&lt;/li&gt;
&lt;li&gt;Sort by followers, read the bios, delete what doesn't fit.&lt;/li&gt;
&lt;li&gt;For the ones that fit, open the bio link and use the contact route they offer there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only contact people through details they chose to publish, and check the rules for cold email where they live. In&lt;br&gt;
the EU that matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;Disclosure: this is my &lt;a href="https://apify.com/alom/threads-lead-finder" rel="noopener noreferrer"&gt;Threads Account Finder&lt;/a&gt; on Apify. No login is&lt;br&gt;
involved, it reads what a logged-out visitor sees. It costs $2.50 per 1,000 accounts on Apify's free plan and $1.50&lt;br&gt;
on the bigger plans, plus $0.01 for each row that has an email, because finding one means opening about 50 profiles.&lt;br&gt;
On the free plan, 500 accounts with 20 emails come to about $1.45.&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;"coffee shop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"specialty coffee"&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a &lt;a href="https://apify.com/alom/threads-lead-finder/examples/find-threads-accounts-in-a-niche" rel="noopener noreferrer"&gt;ready-made example&lt;/a&gt;&lt;br&gt;
you can run as it is. If you also need the posts, replies or a keyword search, the&lt;br&gt;
&lt;a href="https://apify.com/alom/threads-scraper" rel="noopener noreferrer"&gt;Threads Scraper&lt;/a&gt; does those with the same input names.&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Google Hotels showed me two different prices 12 seconds apart</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:22:23 +0000</pubDate>
      <link>https://dev.to/alom_d/google-hotels-showed-me-two-different-prices-12-seconds-apart-4d95</link>
      <guid>https://dev.to/alom_d/google-hotels-showed-me-two-different-prices-12-seconds-apart-4d95</guid>
      <description>&lt;p&gt;I built a hotel price scraper on top of Google Hotels. It collects the price every booking site shows for a hotel&lt;br&gt;
(Booking.com, Expedia, Agoda, the hotel's own site and so on) for each night in a date range. The obvious use is&lt;br&gt;
monitoring: run it every day, alert when a price changes.&lt;/p&gt;

&lt;p&gt;Then my own monitoring test kept failing, and the reason is worth knowing if you work with hotel prices at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;The test is simple. Run the monitor twice in a row. The second run should return almost nothing, because prices&lt;br&gt;
don't change in a few seconds.&lt;/p&gt;

&lt;p&gt;The second run came back with 36 of 69 rows "changed". I assumed a bug in my comparison code. It wasn't. When I&lt;br&gt;
looked at the rows, every one of them really had a different price. Same hotel (a Novotel in Paris), same night,&lt;br&gt;
same booking site, 12 seconds apart: 347 the first time, 401 the second. Another site went from 270 to 315.&lt;/p&gt;

&lt;p&gt;Google Hotels doesn't show one price. It shows a price for &lt;em&gt;that request&lt;/em&gt;: which connection it came from, which&lt;br&gt;
session, possibly which test bucket. Ask again from somewhere else and you can get a noticeably different number.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this means in practice
&lt;/h2&gt;

&lt;p&gt;If you monitor prices, some of the "changes" you see are this variation, not a real price move. A few ways to deal&lt;br&gt;
with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compare against a threshold&lt;/strong&gt;, not equality. A 2% wobble is noise; a 15% jump on one site while the others stay
put is worth a look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look at several sites together.&lt;/strong&gt; If only one site moved, it's probably the request. If the hotel's own site and
two OTAs moved the same way, it's real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample more than once&lt;/strong&gt; before you act on a single reading, especially for rate parity complaints.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The first date that had no prices
&lt;/h2&gt;

&lt;p&gt;Another surprise: when you price one hotel over a range of nights, the very first night sometimes came back with no&lt;br&gt;
prices at all, while the next night had 35 booking sites. The first lookup goes through a different request than the&lt;br&gt;
rest of the range, and that request occasionally answers without its price section.&lt;/p&gt;

&lt;p&gt;The scraper used to report that night as "no offers", which is wrong and also invisible, because the run still said&lt;br&gt;
everything was fine. Now it asks the prices call once more before deciding a date really has no offers. Small thing,&lt;br&gt;
but a monitor that silently drops a night is worse than one that fails loudly.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;Disclosure: this is my &lt;a href="https://apify.com/alom/google-hotels-scraper" rel="noopener noreferrer"&gt;Google Hotels Scraper&lt;/a&gt; on Apify. It searches&lt;br&gt;
hotels in a city with prices for your dates, or tracks specific hotels across a date range with every booking site's&lt;br&gt;
price, room rates, free-cancellation deadlines and guest reviews. A price-calendar row costs $1.00 per 1,000 on&lt;br&gt;
Apify's free plan and $0.60 on the bigger plans.&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;"hotelPrices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hotels"&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;"Novotel Paris Les Halles"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checkInDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastCheckInDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+43"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"providers"&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;"booking.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;"expedia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"official"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also just published a &lt;a href="https://apify.com/alom/google-flights-scraper" rel="noopener noreferrer"&gt;Google Flights scraper&lt;/a&gt; built on the same&lt;br&gt;
Google Travel data, if you need flights next to hotels.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>travel</category>
      <category>data</category>
      <category>python</category>
    </item>
    <item>
      <title>Scraping YouTube comments without the API quota (and the bug where 300 comments meant one thread)</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:16:17 +0000</pubDate>
      <link>https://dev.to/alom_d/scraping-youtube-comments-without-the-api-quota-and-the-bug-where-300-comments-meant-one-thread-42l7</link>
      <guid>https://dev.to/alom_d/scraping-youtube-comments-without-the-api-quota-and-the-bug-where-300-comments-meant-one-thread-42l7</guid>
      <description>&lt;p&gt;The YouTube Data API gives you 10,000 quota units a day by default. Reading comments costs 1 unit per page of up to&lt;br&gt;
100 comments, which sounds generous until you need replies too, or more than a handful of videos, or a second&lt;br&gt;
project running on the same key. People who analyse comments at any scale hit that wall pretty quickly.&lt;/p&gt;

&lt;p&gt;I built a comments scraper that reads what youtube.com shows a logged-out visitor instead, so there is no key and no&lt;br&gt;
quota. A few things I learned on the way, in case you are building something similar.&lt;/p&gt;
&lt;h2&gt;
  
  
  "300 comments" should mean 300 comments
&lt;/h2&gt;

&lt;p&gt;My first version had one limit, &lt;code&gt;maxComments&lt;/code&gt;, and replies counted toward it. Seemed reasonable. Then a test asked&lt;br&gt;
for 300 comments with replies on a popular music video and got back &lt;strong&gt;1 comment and its 299 replies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Of course it did. Replies are delivered right after their parent, and the most liked comment on a big video has&lt;br&gt;
thousands of them. The limit was used up by one thread.&lt;/p&gt;

&lt;p&gt;Nobody who types "300" means that. The fix was two limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;maxComments&lt;/code&gt; counts top-level comments only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxRepliesPerComment&lt;/code&gt; caps the replies under each one (default 5)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second one matters for cost too. If replies were unlimited, 200 comments could turn into 4,000 rows and a bill&lt;br&gt;
nobody expected. With the default, 100 comments return at most 600 rows, and the input form says so.&lt;/p&gt;
&lt;h2&gt;
  
  
  Top comments vs newest
&lt;/h2&gt;

&lt;p&gt;YouTube has two orders, Top and Newest. Top is what you want for "what do people think". Newest is what you want&lt;br&gt;
for monitoring, for example comments on a launch video since yesterday. A date filter on top of Newest ("only the&lt;br&gt;
last 30 days") lets the scraper stop early instead of paging through years of comments.&lt;/p&gt;
&lt;h2&gt;
  
  
  The timeouts that weren't network problems
&lt;/h2&gt;

&lt;p&gt;This one took me a while. Runs with a short time limit kept ending as TIMED-OUT instead of stopping cleanly and&lt;br&gt;
saving what they had. The code had a deadline check that should stop work 25 seconds before the limit. It didn't&lt;br&gt;
fire in time.&lt;/p&gt;

&lt;p&gt;The cause: on Apify, 256 MB of memory also means about 1/16 of a CPU core. Parsing a lot of YouTube pages at once&lt;br&gt;
kept that tiny CPU so busy that the timer meant to stop the run fired late, sometimes by 20+ seconds. Raising the&lt;br&gt;
minimum to 512 MB (twice the CPU) fixed it completely, and the extra cost is still a fraction of a cent per thousand&lt;br&gt;
rows. If your scraper misses its own deadlines, check the CPU before you blame the network.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;Disclosure: this is the &lt;a href="https://apify.com/alom/youtube-comments-scraper" rel="noopener noreferrer"&gt;YouTube Comments Scraper&lt;/a&gt; I published on&lt;br&gt;
Apify. Paste video or Shorts URLs, pick Top or Newest, optionally replies and a date window. It costs $1.00 per&lt;br&gt;
1,000 comments on Apify's free plan and $0.45 on the bigger plans.&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;"startUrls"&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;"url"&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://www.youtube.com/watch?v=dQw4w9WgXcQ"&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;"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;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortCommentsBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TOP_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;"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;"maxRepliesPerComment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&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;There are also two siblings built on the same code: a &lt;a href="https://apify.com/alom/youtube-channel-scraper" rel="noopener noreferrer"&gt;channel scraper&lt;/a&gt;&lt;br&gt;
that exports every video of a channel with its stats, and a &lt;a href="https://apify.com/alom/youtube-shorts-scraper" rel="noopener noreferrer"&gt;Shorts scraper&lt;/a&gt;&lt;br&gt;
for Shorts by channel or keyword.&lt;/p&gt;

&lt;p&gt;If you'd rather stay on the official API, that's a perfectly good choice for small jobs. Just budget your quota for&lt;br&gt;
replies, they add up faster than you think.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>youtube</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Getting Google Flights prices into a spreadsheet</title>
      <dc:creator>Alom Dev</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:10:16 +0000</pubDate>
      <link>https://dev.to/alom_d/getting-google-flights-prices-into-a-spreadsheet-i2j</link>
      <guid>https://dev.to/alom_d/getting-google-flights-prices-into-a-spreadsheet-i2j</guid>
      <description>&lt;p&gt;Google Flights is the best flight search there is, and it has no public API. Google shut down the old QPX Express&lt;br&gt;
API years ago, so if you want flight prices in a spreadsheet or a script, you either pay for a travel data provider&lt;br&gt;
or you read what the Google Flights page shows.&lt;/p&gt;

&lt;p&gt;I built a scraper for the second option, using the same Google Travel data I already used for a hotel price tool.&lt;br&gt;
Here's what it does and a few things I ran into.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three things people actually want
&lt;/h2&gt;

&lt;p&gt;When I looked at what people ask for, it boiled down to three jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every flight for a route and date&lt;/strong&gt;, with price, airline, times, stops and layovers. Basically the results page
as a table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cheapest day to fly.&lt;/strong&gt; Google's date grid shows a price for each departure date. That's the most useful view
for anyone flexible on dates, and it's one request for about two months of prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerts.&lt;/strong&gt; Run it every day and only hear about flights whose price changed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Things I didn't expect
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;City names and multi-airport cities.&lt;/strong&gt; People type "New York", "NYC" or "JFK,EWR", not airport codes. Google's own&lt;br&gt;
search rejects some of those codes, so the scraper resolves names and city codes to airports first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prices were the same everywhere I tested.&lt;/strong&gt; I expected the country and IP to change fares, like hotel prices do.&lt;br&gt;
Across US, Ukrainian, German and Indian connections the prices came back identical; only the currency and the&lt;br&gt;
language of names changed. That may not hold for every airline, but it held in all my tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google refuses a fair share of requests.&lt;/strong&gt; Roughly a quarter to a third of connections get refused for flight&lt;br&gt;
searches. The fix that worked was boring: switch to a new connection right away, and after a few refusals fall back&lt;br&gt;
to the normal results page, which has the same data. In the end no route failed in my test runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layovers are where the detail is.&lt;/strong&gt; The interesting fields aren't the price, they're the layovers: which airport,&lt;br&gt;
how long, and whether you have to change airports. A 50-minute connection with an airport change is a very&lt;br&gt;
different trip from a 2-hour one in the same terminal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;Disclosure: this is my &lt;a href="https://apify.com/alom/google-flights-scraper" rel="noopener noreferrer"&gt;Google Flights Scraper&lt;/a&gt; on Apify. You can run&lt;br&gt;
it from the Apify website without code and download CSV or Excel, or call it from Python.&lt;/p&gt;

&lt;p&gt;Every flight from New York to London, cheapest first:&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;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JFK,EWR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LHR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"departureDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+45"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sortBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price"&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;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;Cheapest day to fly over the next two months, round trip with a 7-day stay:&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;"calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BCN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"departureDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"calendarDays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;Price alerts on a schedule:&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;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WAW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LIS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"departureDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+40"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"returnDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+47"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"onlyNewSinceLastRun"&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;"monitorName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waw-lis"&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 $0.50 per 1,000 flights on Apify's free plan ($0.25 on bigger plans) and $0.20 per 1,000 calendar dates. A&lt;br&gt;
whole route usually comes to a fraction of a cent.&lt;/p&gt;

&lt;p&gt;If you need hotels for the same trip, there's a &lt;a href="https://apify.com/alom/google-hotels-scraper" rel="noopener noreferrer"&gt;Google Hotels scraper&lt;/a&gt;&lt;br&gt;
built on the same data.&lt;/p&gt;

</description>
      <category>python</category>
      <category>travel</category>
      <category>webscraping</category>
      <category>data</category>
    </item>
    <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>
