<?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: Hugues Ishema</title>
    <description>The latest articles on DEV Community by Hugues Ishema (@ishemah).</description>
    <link>https://dev.to/ishemah</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%2F4156904%2F0782e442-9e42-455b-9aa3-312db7483cbb.jpg</url>
      <title>DEV Community: Hugues Ishema</title>
      <link>https://dev.to/ishemah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ishemah"/>
    <language>en</language>
    <item>
      <title>Google's Trends API is still invite-only. I tested what the alternatives return</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Tue, 13 Oct 2026 07:39:09 +0000</pubDate>
      <link>https://dev.to/ishemah/googles-trends-api-is-still-invite-only-i-tested-what-the-alternatives-return-5be4</link>
      <guid>https://dev.to/ishemah/googles-trends-api-is-still-invite-only-i-tested-what-the-alternatives-return-5be4</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Google announced an official Trends API in July 2025 "only to a very limited number of testers". Fifteen months later the page is still titled "Alpha" and still ends in an application form. pytrends, the library most tutorials use, was archived in April 2025, and its last maintainer left a note saying Google gives bots "slightly altered data". I tested that claim on 13 October 2026: five keywords, one hour, three ways to ask Google. Interest over time and interest by region came back the same from all three, give or take 3 points. Related queries are unstable for everyone, including the website. Related topics are the one thing Google withholds from anything it tags as a scraper, and it tags sessions openly, in its own JSON.&lt;/p&gt;

&lt;p&gt;What you can't get from any route below: absolute search volumes. Google Trends only ever gives relative interest on a 0 to 100 scale, API or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status table (checked 13 October 2026)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;State today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Official Google Trends API&lt;/td&gt;
&lt;td&gt;Alpha. "As we can't open the API for everyone yet, we're prioritizing developers that know what they want to do", with an application form (&lt;a href="https://developers.google.com/search/apis/trends" rel="noopener noreferrer"&gt;API page&lt;/a&gt;). Announced 24 July 2025 (&lt;a href="https://developers.google.com/search/blog/2025/07/trends-api" rel="noopener noreferrer"&gt;blog&lt;/a&gt;). An April 2026 &lt;a href="https://news.ycombinator.com/item?id=47901379" rel="noopener noreferrer"&gt;Ask HN&lt;/a&gt; asking who got in has one reply, from someone who didn't&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytrends (Python)&lt;/td&gt;
&lt;td&gt;Archived on GitHub 17 April 2025. Last release 4.9.2, April 2023. Still 908,810 PyPI downloads in the last 30 days (pypistats). 63 issues and PRs in the repo mention HTTP 429&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trendspy (Python)&lt;/td&gt;
&lt;td&gt;The fork the pytrends maintainer pointed to. Last commit 25 December 2024, v0.1.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;google-trends-api (npm)&lt;/td&gt;
&lt;td&gt;Last push November 2022. 569,469 downloads in the last 30 days, 54 open issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trends.google.com&lt;/td&gt;
&lt;td&gt;Works. Compare a handful of terms, download a CSV by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scrapers (self-built or hosted)&lt;/td&gt;
&lt;td&gt;Work, with the caveats measured below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pytrends note is worth reading in full (&lt;a href="https://github.com/GeneralMills/pytrends/issues/636" rel="noopener noreferrer"&gt;issue #636&lt;/a&gt;, 8 February 2025). The maintainer's claim is that when Google detects a bot it returns "slightly altered data" compared with the website. That's a strong claim, and nobody I could find had checked it. So I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;Same five keywords (python, javascript, rust, typescript, golang), United States, past 12 months, web search. All pulled between 07:19 and 07:31 UTC on 13 October 2026, three ways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Who asks Google&lt;/th&gt;
&lt;th&gt;Where from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A. Website&lt;/td&gt;
&lt;td&gt;trends.google.com in a normal browser. I read the JSON the page itself loads&lt;/td&gt;
&lt;td&gt;My IP (Rwanda)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B. Plain fetch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fetch()&lt;/code&gt; typed into the console of that same tab. Same cookies, same IP, same minute&lt;/td&gt;
&lt;td&gt;My IP (Rwanda)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C. Scraper&lt;/td&gt;
&lt;td&gt;My Apify Actor (residential proxy, no cookies)&lt;/td&gt;
&lt;td&gt;Apify's servers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The raw responses, the Actor's output and the overlap script are in the &lt;a href="https://github.com/ISHEMAH/travel-price-studies" rel="noopener noreferrer"&gt;public repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Google labels every session, and tells you
&lt;/h2&gt;

&lt;p&gt;The first request on a Trends page is &lt;code&gt;POST /trends/api/explore&lt;/code&gt;. Its response contains the request object for every widget, and inside each one is this:&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="nl"&gt;"userConfig"&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="nl"&gt;"userType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USER_TYPE_LEGIT_USER"&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;That is what the page got (A). The &lt;code&gt;fetch()&lt;/code&gt; from the console of the same tab (B) got, every time:&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="nl"&gt;"userConfig"&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="nl"&gt;"userType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USER_TYPE_SCRAPER"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same browser, same cookies, same IP, seconds apart. The widget tokens are signed against this label, so you can't edit it. Whatever the website's JavaScript sends that my &lt;code&gt;fetch()&lt;/code&gt; didn't, Google uses it to sort requests into two bins before any data is returned. The question is what differs between the bins.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Interest over time: the same, within 3 points
&lt;/h2&gt;

&lt;p&gt;A and B returned identical numbers. I compared python on all 53 weeks and the other four terms on 48 weeks, and found no difference. The scraper label alone does not change the time series.&lt;/p&gt;

&lt;p&gt;The Actor (C) got a slightly different sample. Google's help pages say Trends data is a sample, and the Actor asked from a different IP ten minutes earlier. Per keyword, 53 weekly points on the shared 0 to 100 scale:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keyword&lt;/th&gt;
&lt;th&gt;Points identical&lt;/th&gt;
&lt;th&gt;Mean abs. difference&lt;/th&gt;
&lt;th&gt;Max difference&lt;/th&gt;
&lt;th&gt;Correlation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;python&lt;/td&gt;
&lt;td&gt;16 of 53&lt;/td&gt;
&lt;td&gt;1.08&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.997&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;javascript&lt;/td&gt;
&lt;td&gt;42 of 53&lt;/td&gt;
&lt;td&gt;0.21&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.994&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rust&lt;/td&gt;
&lt;td&gt;25 of 53&lt;/td&gt;
&lt;td&gt;0.66&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.985&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typescript&lt;/td&gt;
&lt;td&gt;48 of 53&lt;/td&gt;
&lt;td&gt;0.09&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.987&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;golang&lt;/td&gt;
&lt;td&gt;52 of 53&lt;/td&gt;
&lt;td&gt;0.02&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.995&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Actor's share-of-search ranking (python 57%, rust 28%, javascript 9%, typescript 3%, golang 2%) is the same ranking the website chart shows. For a trend line, a seasonality check or a "did interest double" question, 3 points on a 0 to 100 scale is noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Interest by region: the same, within 1 point
&lt;/h2&gt;

&lt;p&gt;Website (A) versus Actor (C) for python, US states, top of the list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Website&lt;/th&gt;
&lt;th&gt;Actor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wyoming&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kansas&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;District of Columbia&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Massachusetts&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maryland&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Washington&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;California&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virginia&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All 51 regions are within 1 point. (Yes, Wyoming. Google's regional score is a share of all searches in that state, not a count.)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Related queries: unstable for everyone
&lt;/h2&gt;

&lt;p&gt;This is where "altered data" would hide, so I pulled the top 25 related queries for python five times: the website twice (two page loads, ten minutes apart), the plain fetch twice, and the Actor once. Overlap of the 25:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pair&lt;/th&gt;
&lt;th&gt;Shared queries (of 25)&lt;/th&gt;
&lt;th&gt;Shared in the top 10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Website load 1 vs website load 2&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website load 1 vs plain fetch&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website load 2 vs plain fetch&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website load 1 vs Actor&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website load 2 vs Actor&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The website disagrees with itself as much as it disagrees with the scrapers. Two loads of the same page, ten minutes apart, shared 15 of 25 queries. "python list", "python documentation", "python news" and "python class" were in load 1 and gone in load 2. Rising queries were worse: 6 of 25 shared between the two website loads.&lt;/p&gt;

&lt;p&gt;The head of the list is stable everywhere: "what is python" at 100, "python code" at 69 to 72, "ball python" at 40 or 41 in every pull but one. Even the wording moves: the website said "install python" on one load and "python install" on the next.&lt;/p&gt;

&lt;p&gt;Conclusion: take the top handful of related queries from any source. Don't build anything on the tail, and don't diff two pulls and call the difference a trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Related topics: withheld from scrapers
&lt;/h2&gt;

&lt;p&gt;Related topics (Google's entity list, with Freebase-style ids like &lt;code&gt;/m/05z1_&lt;/code&gt;) are the one hard difference.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website (A), python, past 12 months: 20 top topics and 13 rising. Python the programming language at 100, then Data 7, Python the snake 7, Documentation 6, Computer file 5, API 5, and so on.&lt;/li&gt;
&lt;li&gt;Plain fetch (B), same tab, seconds later: &lt;code&gt;rankedList&lt;/code&gt; present, both lists empty.&lt;/li&gt;
&lt;li&gt;Actor (C): &lt;code&gt;{"top": [], "rising": []}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The topics widget appears only on single-keyword pages, by the way. On a compare page the website itself shows related queries only.&lt;/p&gt;

&lt;p&gt;So the maintainer was right that scrapers get something different, and the something is this: no related topics. On the numbers people actually chart, I found no alteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you need Trends data in code
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You need&lt;/th&gt;
&lt;th&gt;Where to get it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interest over time, interest by region, share of search between terms&lt;/td&gt;
&lt;td&gt;Any route. The website, a scraper, or the official API if you're in the alpha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Related queries&lt;/td&gt;
&lt;td&gt;Any route, head of the list only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Related topics&lt;/td&gt;
&lt;td&gt;The website, or the official API if it ever ships them. Not a scraper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absolute volumes&lt;/td&gt;
&lt;td&gt;Nowhere in Trends&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Hosted version
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/kuezi/google-trends-scraper" rel="noopener noreferrer"&gt;Google Trends Scraper&lt;/a&gt; on Apify is source C above. One record per keyword: the time series with a trend direction and recent change, the region table, related queries with breakouts flagged, and a share-of-search ranking when you compare terms. It also chains batches so you can compare more than five keywords on one scale. It can't return related topics, for the reason in section 5, and it says so in the README.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built this. It costs $4 per 1,000 keyword reports and $0.50 per 1,000 Trending Now items. Keywords Google has no data for aren't charged. Apify's free plan includes $5 of credit a month.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;apify-client&lt;/code&gt; 3.x (&lt;code&gt;pip install apify-client&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

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

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

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kuezi/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;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;geo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeRange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;today 12-m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeInterestOverTime&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeInterestByRegion&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeRelatedQueries&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeRelatedTopics&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="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;keyword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;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;status&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;avg&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;average&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;latest&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;latestValue&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;direction&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  top region:&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;topRegion&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  top queries:&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;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&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;q&lt;/span&gt; &lt;span class="ow"&gt;in&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;relatedQueries&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;top&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  rising:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&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;q&lt;/span&gt; &lt;span class="ow"&gt;in&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;relatedQueries&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;rising&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;3&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  related topics:&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;relatedTopics&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;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output from the real run on 13 October 2026 (25 seconds, $0.004):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python ok avg 62.5 latest 37 falling
  top region: Wyoming
  top queries: ['what is python', 'python code', 'ball python', 'install python', 'python online']
  rising: ['walmart near me', 'python swallowing deer india', 'singapore reticulated python sighting']
  related topics: {'top': [], 'rising': []}
Done: 1/1 keywords.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The empty topics are the honest output, not a bug. A &lt;code&gt;TIMELINE_WIDE.csv&lt;/code&gt; with one column per keyword lands in the run's key-value store for Sheets or pandas.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't give you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search volumes.&lt;/strong&gt; 100 means the peak for that term, place and period. Nothing more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related topics.&lt;/strong&gt; See section 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A guarantee the sample matches the website to the point.&lt;/strong&gt; Expect a 1 to 3 point spread on the time series.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've compared Trends output across sources in a different way, or you're in the API alpha and can say what it returns, post it in the comments and I'll add it to the tables.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance. Every quote, number and output above was checked or run on 13 October 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>datascience</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>There's no Google Hotels API, and the free fallbacks closed</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:06:39 +0000</pubDate>
      <link>https://dev.to/ishemah/theres-no-google-hotels-api-and-the-free-fallbacks-closed-1c4e</link>
      <guid>https://dev.to/ishemah/theres-no-google-hotels-api-and-the-free-fallbacks-closed-1c4e</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Google has no public API for the prices you see on Google Hotels. In 2026 the two fallbacks most tutorials pointed to also closed for small developers: Amadeus Self-Service (including Hotel Search) shut on 17 July, and Booking.com's Demand API is for contracted partners only. If you need hotel prices in code without a partnership, what's left is reading the public results page, yourself or through a paid scraper. Code and a real run are below.&lt;/p&gt;

&lt;p&gt;What this post can't fix: &lt;strong&gt;booking.&lt;/strong&gt; Everything here is price data. If you need to sell rooms, you need a supplier contract (Expedia Rapid, Booking.com, a bedbank), and no scraper replaces that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status table (checked 12 October 2026)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Can a solo developer get hotel prices from it today?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Travel Partner API&lt;/td&gt;
&lt;td&gt;No. It's for Hotel Center accounts: your own feed, reports and price accuracy. No method returns other providers' prices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amadeus Self-Service Hotel Search / Hotel List&lt;/td&gt;
&lt;td&gt;No. The portal "has been decommissioned on July 17th", per the Amadeus for Developers homepage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Booking.com Demand API&lt;/td&gt;
&lt;td&gt;Only as a "Managed Affiliate Partner" with Partner Centre access given by your account manager after a contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expedia Rapid API&lt;/td&gt;
&lt;td&gt;Only as an approved Expedia partner. The key stays in development mode until a site review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Hotels results page&lt;/td&gt;
&lt;td&gt;Yes. It's public. You parse it or pay someone who does&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Google: the API exists, but not for you
&lt;/h2&gt;

&lt;p&gt;Search "Google Hotels API" and you land on the &lt;a href="https://developers.google.com/hotels/hotel-prices/api-reference" rel="noopener noreferrer"&gt;Travel Partner API&lt;/a&gt;. It's real, and it's for hotels and booking sites that &lt;em&gt;send&lt;/em&gt; prices to Google. You authenticate with OAuth2 against your own Hotel Center account and read your own price views and reports. There is no endpoint that returns what Booking.com or Expedia charge for a hotel you don't manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amadeus: the free door closed on 17 July
&lt;/h2&gt;

&lt;p&gt;For years the standard answer was Amadeus Self-Service: free test keys, then &lt;code&gt;shopping.hotel_offers_search&lt;/code&gt; and &lt;code&gt;reference_data.locations.hotels.by_city&lt;/code&gt; in the Python SDK.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://developers.amadeus.com/" rel="noopener noreferrer"&gt;Amadeus for Developers homepage&lt;/a&gt; now reads: "self-service portal has been decommissioned on July 17th, this website is for Amadeus Enterprise API Portal only".&lt;/li&gt;
&lt;li&gt;The old Hotel Search docs URL redirects to that homepage.&lt;/li&gt;
&lt;li&gt;The official &lt;a href="https://github.com/amadeus4dev/amadeus-python" rel="noopener noreferrer"&gt;amadeus-python&lt;/a&gt; repo was archived on 17 July 2026 at 10:04 UTC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only Self-Service closed. Amadeus's Enterprise APIs continue, under contract.&lt;/p&gt;

&lt;p&gt;If you had flight code on Amadeus too, I wrote a &lt;a href="https://dev.to/ishemah/amadeus-self-service-shut-down-on-july-17-heres-a-drop-in-replacement-for-flight-prices-1npn"&gt;call-by-call replacement for the flight endpoints&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Booking.com: contract first, API second
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developers.booking.com/demand/docs/getting-started/prerequisites" rel="noopener noreferrer"&gt;Demand API prerequisites&lt;/a&gt; list two things before any key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Registered as a Booking.com Managed Affiliate Partner."&lt;/li&gt;
&lt;li&gt;"Access to Partner Centre (provided by your Booking.com Account Manager after signing the agreed contract)."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Booking.com also ended its direct affiliate programme in 2025 and moved affiliates to networks such as CJ and Awin (&lt;a href="https://skift.com/2025/05/30/why-booking-com-cut-thousands-of-affiliate-partners-and-what-comes-next/" rel="noopener noreferrer"&gt;Skift, May 2025&lt;/a&gt;). One developer &lt;a href="https://github.com/wanderes-dev/wanderes/pull/1" rel="noopener noreferrer"&gt;documented in September 2026&lt;/a&gt; that applying through CJ is separate from Demand API access. Their Partner Centre login said they had "no access rights".&lt;/p&gt;

&lt;h2&gt;
  
  
  Expedia Rapid: partner, approval, site review
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.expediagroup.com/docs/products/rapid/setup/getting-started" rel="noopener noreferrer"&gt;Rapid's getting-started page&lt;/a&gt; is clear: "To integrate with Rapid API, you need to be an Expedia partner." After approval you get a key, but it stays in development mode. You "ask your business development manager for a site review", and only then is the key enabled for production.&lt;/p&gt;

&lt;p&gt;That's a sensible process for a booking business. It's a wall for a side project, a price tracker or a research script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left: the public page
&lt;/h2&gt;

&lt;p&gt;Google Hotels already shows what you want: for each hotel, the nightly and total price for your dates, plus a list of booking sites and what each one charges. The page is public. You have three ways to get it into code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Who maintains the parser&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write your own scraper&lt;/td&gt;
&lt;td&gt;Proxies and your time&lt;/td&gt;
&lt;td&gt;You, every time Google changes the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SerpApi Google Hotels&lt;/td&gt;
&lt;td&gt;Free 250 searches a month, then from $25 per 1,000 searches (&lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;SerpApi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apify Actors, including mine&lt;/td&gt;
&lt;td&gt;Pay per result&lt;/td&gt;
&lt;td&gt;The Actor's developer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even paid APIs struggle with this page. SerpApi's own public roadmap has open issues titled &lt;a href="https://github.com/serpapi/public-roadmap/issues/2693" rel="noopener noreferrer"&gt;"Some prices are missing in the search results"&lt;/a&gt; (14 comments) and &lt;a href="https://github.com/serpapi/public-roadmap/issues/2836" rel="noopener noreferrer"&gt;"Some regional results don't show up"&lt;/a&gt; (16 comments). Whichever route you pick, check for gaps before you trust a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted version
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/kuezi/google-hotels-scraper" rel="noopener noreferrer"&gt;Google Hotels Scraper&lt;/a&gt; on Apify takes a city or a search, your dates, guests and currency, and returns one JSON record per hotel. Turn on &lt;code&gt;includeVendorPrices&lt;/code&gt; and each hotel also gets every booking site Google lists, cheapest first.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built this. It costs $1.50 per 1,000 hotels, plus $0.006 per hotel for the booking-site price list. Hotels removed by your filters and failed searches aren't charged. Apify's free plan includes $5 of credit a month.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;apify-client&lt;/code&gt; 3.x (&lt;code&gt;pip install apify-client&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

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

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

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kuezi/google-hotels-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;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&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;queries&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;Lisbon&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;checkInDate&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;2026-11-20&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;checkOutDate&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;2026-11-22&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;adults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&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;USD&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;maxResultsPerQuery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeVendorPrices&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxVendorsPerHotel&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="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pricePerNight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/night on Google, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendorCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; sites&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendorPrices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;  &lt;span class="c1"&gt;# cheapest first
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pricePerNight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&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;status_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output from a real run on 12 October 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Brown's | Avenue Hotel | 4.6 | $180/night on Google, 20 sites
    Brown's Avenue $180
    Vio.com        $185
    Etrip.net      $185
Iberostar Selection Lisboa | 4.7 | $118/night on Google, 20 sites
    Evendo         $118
    goseek.com     $123
    dealbase.com   $123
Patio São Vicente Guest Houses | 4.9 | $112/night on Google, 11 sites
    Hotels.com     $112
    Expedia.com    $119
    Travelocity.com $119
Saved 3 hotels from 1/1 searches (2026-11-20 → 2026-11-22, 2 adults, prices per night). "hotels in Lisbon" (3 hotels): cheapest Patio São Vicente Guest Houses 112 USD (Hotels.com), Iberostar Selection Lisboa 118 USD (Evendo), Brown's | Avenue Hotel 180 USD (Brown's Avenue); median 118 USD/night; best rated Patio São Vicente Guest Houses 4.9★ at 112.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That run took 12 seconds and cost $0.0225: three hotels plus three booking-site lists. &lt;code&gt;maxVendorsPerHotel&lt;/code&gt; keeps the 20 cheapest sites, so "20 sites" means "at least 20".&lt;/p&gt;

&lt;p&gt;The full lists show why the per-site data matters. For the same two nights:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hotel&lt;/th&gt;
&lt;th&gt;Cheapest site&lt;/th&gt;
&lt;th&gt;Booking.com&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Brown's Avenue Hotel&lt;/td&gt;
&lt;td&gt;$180 (the hotel's own site)&lt;/td&gt;
&lt;td&gt;$221&lt;/td&gt;
&lt;td&gt;+23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patio São Vicente&lt;/td&gt;
&lt;td&gt;$112 (Hotels.com)&lt;/td&gt;
&lt;td&gt;$140&lt;/td&gt;
&lt;td&gt;+25%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two hotels prove nothing on their own. I ran the same comparison on 47 hotels in &lt;a href="https://dev.to/ishemah/i-compared-24-booking-sites-on-47-hotels-bookingcom-expedia-and-hotelscom-were-26-above-the-4egk"&gt;an earlier study&lt;/a&gt;, with the data and code public.&lt;/p&gt;

&lt;p&gt;Other inputs worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;minRating&lt;/code&gt;, &lt;code&gt;maxPricePerNight&lt;/code&gt; and &lt;code&gt;minHotelClass&lt;/code&gt; filter before saving, so you don't pay for hotels you drop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;country&lt;/code&gt; sets Google's region, which changes which booking sites appear. Prices above are from a US point of sale.&lt;/li&gt;
&lt;li&gt;Schedule the run daily to build a price history for your dates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a ready-made task to &lt;a href="https://apify.com/kuezi/google-hotels-scraper/examples/compare-hotel-prices-across-booking-sites" rel="noopener noreferrer"&gt;compare one hotel's price across booking sites&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't give you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Booking or availability guarantees.&lt;/strong&gt; These are the prices Google shows at the moment of the run. Rooms, taxes and cancellation terms differ by site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Room-level rate plans.&lt;/strong&gt; You get the headline price per site, not every room type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Official status.&lt;/strong&gt; This is public-page data, not a partner feed. If you need a contract and an SLA, the partner programmes above are the way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you found another legitimate route to hotel prices that's open to small developers in 2026, post it in the comments and I'll add it to the table.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance. Every quote, number and output above was checked or run on 12 October 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>api</category>
      <category>travel</category>
    </item>
    <item>
      <title>my.telegram.org says ERROR? For public channels you may not need an api_id</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Sat, 03 Oct 2026 17:19:10 +0000</pubDate>
      <link>https://dev.to/ishemah/mytelegramorg-says-error-for-public-channels-you-may-not-need-an-apiid-a9c</link>
      <guid>https://dev.to/ishemah/mytelegramorg-says-error-for-public-channels-you-may-not-need-an-apiid-a9c</guid>
      <description>&lt;p&gt;You filled in the form at my.telegram.org/apps, clicked &lt;strong&gt;Create application&lt;/strong&gt;, and got a box that says "ERROR". No code, no reason. You tried again. Same box.&lt;/p&gt;

&lt;p&gt;You're in large company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Stack Overflow question &lt;a href="https://stackoverflow.com/questions/68965496" rel="noopener noreferrer"&gt;"my.telegram.org sends an ERROR when I want to create an API id/hash"&lt;/a&gt; has almost 96,000 views.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tdlib/telegram-bot-api/issues/597" rel="noopener noreferrer"&gt;tdlib/telegram-bot-api #597&lt;/a&gt; has 52 comments. The latest one is from 16 September 2026.&lt;/li&gt;
&lt;li&gt;Telethon's maintainer &lt;a href="https://github.com/LonamiWebs/Telethon/issues/4661" rel="noopener noreferrer"&gt;answered the same report&lt;/a&gt; with "I don't have any control over that page".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody outside Telegram knows exactly why the form fails. Before you spend another evening on it, check whether you need an api_id at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you actually need an api_id?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to do&lt;/th&gt;
&lt;th&gt;api_id?&lt;/th&gt;
&lt;th&gt;Use instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read posts from a &lt;strong&gt;public&lt;/strong&gt; channel&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The web preview at &lt;code&gt;t.me/s/&amp;lt;channel&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search inside a public channel&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t.me/s/&amp;lt;channel&amp;gt;?q=&amp;lt;word&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get new posts from a public channel every hour&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Poll the web preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send messages or run a bot&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;A bot token from &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read a &lt;strong&gt;private&lt;/strong&gt; channel, a group, your own chats, member lists&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MTProto (Telethon, Kurigram)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your project is in the first three rows, you can skip my.telegram.org entirely. If it's in the last row, there's no way around it, and the fixes people report are further down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a bot token doesn't solve it
&lt;/h2&gt;

&lt;p&gt;Another common answer is "just ask BotFather for a token". That works for sending messages. It doesn't work for reading someone else's channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Telegram's &lt;a href="https://core.telegram.org/bots/faq" rel="noopener noreferrer"&gt;Bots FAQ&lt;/a&gt; says bots receive "all messages from channels where they are a member". The channel's admin has to add your bot.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Bot API&lt;/a&gt; has no method to fetch a chat's past messages. A bot only sees what arrives after it joins.&lt;/li&gt;
&lt;li&gt;Updates wait on Telegram's server for "not longer than 24 hours". If your bot is down for a day, those posts are gone from its queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you do need MTProto: library status
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Status (checked 3 Oct 2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pyrogram&lt;/td&gt;
&lt;td&gt;Archived on GitHub on 23 Dec 2024. Last release April 2023, yet still 275,823 PyPI downloads last month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pyrofork&lt;/td&gt;
&lt;td&gt;Archived on 14 Sep 2026. Last release December 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telethon&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Active.&lt;/strong&gt; The GitHub repo was archived in February 2026 because the project &lt;a href="https://codeberg.org/Lonami/Telethon" rel="noopener noreferrer"&gt;moved to Codeberg&lt;/a&gt;. v1.45.0 shipped on 10 Sep 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kurigram (a Pyrogram fork)&lt;/td&gt;
&lt;td&gt;Active. v2.2.26 on 12 Sep 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All four need an api_id and a phone login. If you only look at GitHub, Telethon looks dead. It isn't.&lt;/p&gt;

&lt;p&gt;Telegram's own &lt;a href="https://core.telegram.org/api/obtaining_api_id" rel="noopener noreferrer"&gt;api_id page&lt;/a&gt; adds two rules worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Each number can only have one api_id connected to it."&lt;/li&gt;
&lt;li&gt;"All accounts that log in using unofficial Telegram API clients are automatically put under observation."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a scraper built on your personal number puts your personal account on the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What people report fixes the ERROR
&lt;/h2&gt;

&lt;p&gt;None of these is official, and none works for everyone. They come from the Stack Overflow thread and tdlib #597:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable ad blockers and VPN browser extensions. This is the top Stack Overflow answer.&lt;/li&gt;
&lt;li&gt;Try another browser or an incognito window.&lt;/li&gt;
&lt;li&gt;Switch from Wi-Fi to mobile data, or submit from your phone.&lt;/li&gt;
&lt;li&gt;Use an IP address in the same country as your phone number. Several people report this; one commenter disputes it.&lt;/li&gt;
&lt;li&gt;Wait 5 to 10 minutes and submit again. One user got through "after about 2 months" with no other change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One warning. The tdlib thread also has posts from accounts offering to "apply for you" for a fee. That means giving a stranger a login code for your Telegram account. Don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The route that needs nothing: t.me/s/
&lt;/h2&gt;

&lt;p&gt;Every public channel has a web preview at &lt;code&gt;https://t.me/s/&amp;lt;channel&amp;gt;&lt;/code&gt;. It's plain HTML. It shows the newest 20 posts with text, date, views, reactions and media. You page back with &lt;code&gt;?before=&amp;lt;id&amp;gt;&lt;/code&gt; and search with &lt;code&gt;?q=&amp;lt;word&amp;gt;&lt;/code&gt;. No account, no phone number, no api_id.&lt;/p&gt;

&lt;p&gt;Private channels, groups, bots and users have no preview. For them, &lt;code&gt;t.me/s/&amp;lt;name&amp;gt;&lt;/code&gt; redirects to &lt;code&gt;t.me/&amp;lt;name&amp;gt;&lt;/code&gt;. That redirect is a quick test of whether this route works for your target.&lt;/p&gt;

&lt;p&gt;I wrote up the selectors, a 30-line Python scraper and the gotchas in &lt;a href="https://dev.to/ishemah/how-to-scrape-any-public-telegram-channel-without-the-api-no-phone-number-no-login-46ih"&gt;an earlier post&lt;/a&gt;. If you'd rather not maintain a parser, there's a hosted version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted version
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/kuezi/telegram-channel-scraper" rel="noopener noreferrer"&gt;Telegram Channel Scraper&lt;/a&gt; on Apify reads the same public preview and returns JSON.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built this. It costs $0.50 per 1,000 posts plus $0.001 per channel per run, and failed channels aren't charged. Apify's free plan includes $5 of credit a month.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;apify-client&lt;/code&gt; 3.x (&lt;code&gt;pip install apify-client&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

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

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

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kuezi/telegram-channel-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;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&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;channels&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;telegram&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;BotFather&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;searchQuery&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;passkey&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;maxMessagesPerChannel&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="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&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;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messageId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&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;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&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;status_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;failed&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;FAILED&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;failed&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output from a real run on 3 October 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;423 2026-01-07 2720000 Happy New Year! Telegram released 13 major updates in 2025 –
420 2025-12-15 3020000 For all the details about creating passkeys and making offer
417 2025-12-15 2020000 Passkeys. You can now create a passkey to instantly log in t
Saved 3 messages from 2 channel(s). telegram (9.4M subscribers): 3 posts from 2025-12-15 to 2026-01-07; most viewed 3M views https://t.me/telegram/420. BotFather: failed (no public preview).
[{'channel': 'BotFather', 'error': '"BotFather" has no public web preview (it may be private, a group, a user or a bot, or it doesn\'t exist).'}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That run cost $0.0025: three posts plus one channel. BotFather is a bot, so it has no preview. The run reports it in &lt;code&gt;FAILED&lt;/code&gt; and doesn't charge for it.&lt;/p&gt;

&lt;p&gt;Other inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dateFrom&lt;/code&gt; and &lt;code&gt;dateTo&lt;/code&gt; stop paging once a run passes your date range, so you don't pay for old posts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onlyNewMessages&lt;/code&gt; turns on monitoring. Schedule the run, and each one returns only posts published since the previous run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also a ready-made task to &lt;a href="https://apify.com/kuezi/telegram-channel-scraper/examples/search-telegram-channel-keywords" rel="noopener noreferrer"&gt;search a Telegram channel for keywords&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can't do without an api_id
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private channels, groups and DMs.&lt;/strong&gt; There's no preview. You need MTProto and an account that's a member.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact view counts.&lt;/strong&gt; The preview rounds them ("1.6M" becomes 1600000).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some media.&lt;/strong&gt; Certain polls and stickers aren't rendered in the preview. The post still has its date, views and usually its text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Posting.&lt;/strong&gt; Use a bot token for that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hit the ERROR and found a fix that isn't listed here, add it in the comments. I'll update the list.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance. Every number and output above was checked or run on 3 October 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>python</category>
      <category>api</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Amadeus Self-Service shut down on July 17. Here's a drop-in replacement for flight prices</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Sat, 03 Oct 2026 11:30:51 +0000</pubDate>
      <link>https://dev.to/ishemah/amadeus-self-service-shut-down-on-july-17-heres-a-drop-in-replacement-for-flight-prices-1npn</link>
      <guid>https://dev.to/ishemah/amadeus-self-service-shut-down-on-july-17-heres-a-drop-in-replacement-for-flight-prices-1npn</guid>
      <description>&lt;p&gt;If your fare tracker, travel bot or class project suddenly can't reach &lt;code&gt;test.api.amadeus.com&lt;/code&gt; or gets auth errors, it's not your code. &lt;strong&gt;Amadeus decommissioned its Self-Service APIs on 17 July 2026.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://developers.amadeus.com/" rel="noopener noreferrer"&gt;developer portal&lt;/a&gt; now says the self-service portal "has been decommissioned on July 17th".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.phocuswire.com/amadeus-shut-down-self-service-apis-portal-developers" rel="noopener noreferrer"&gt;PhocusWire reported the plan on 9 February&lt;/a&gt;: new registrations paused, then keys disabled from 17 July. Enterprise APIs continue.&lt;/li&gt;
&lt;li&gt;The official SDKs (&lt;a href="https://github.com/amadeus4dev/amadeus-python" rel="noopener noreferrer"&gt;amadeus-python&lt;/a&gt;, amadeus-node, amadeus-java, Ruby, .NET) were archived on GitHub the same day.&lt;/li&gt;
&lt;li&gt;On 2 October, &lt;code&gt;test.api.amadeus.com&lt;/code&gt;, the SDK's default host, had no DNS A record at Google or Cloudflare DNS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub is full of projects hitting this. More than 190 issues and PRs mention Amadeus Self-Service since February. Some remove their Amadeus adapter (&lt;a href="https://github.com/HelpCode-ai/anythingmcp/pull/713" rel="noopener noreferrer"&gt;anythingmcp #713&lt;/a&gt;). Others swap in another API (&lt;a href="https://github.com/barry800414/multi-flight-search/pull/1" rel="noopener noreferrer"&gt;multi-flight-search #1&lt;/a&gt;). Many are student projects that started an Amadeus integration &lt;em&gt;after&lt;/em&gt; registrations closed.&lt;/p&gt;

&lt;p&gt;I needed the same three calls for my own projects, so I built a replacement with the same parameter names and the same JSON shape. This post covers the options, the migration code, and what you can't replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  The free options are mostly closed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Status (checked 2 Oct 2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amadeus Self-Service&lt;/td&gt;
&lt;td&gt;Decommissioned 17 July 2026. Enterprise only, via a commercial agreement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google QPX Express&lt;/td&gt;
&lt;td&gt;Ended 10 April 2018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kiwi.com Tequila&lt;/td&gt;
&lt;td&gt;New partnerships "on an invitation only basis" since May 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skyscanner Travel API&lt;/td&gt;
&lt;td&gt;"Commercial use only". Not for students, start-ups without a product, or sites under 100K monthly users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SerpApi Google Flights&lt;/td&gt;
&lt;td&gt;Works. 250 free searches a month, then $25 per 1,000 to $275 per 30,000 ($0.025–$0.009 per search). Return legs need a second request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;fast-flights&lt;/code&gt; (Python)&lt;/td&gt;
&lt;td&gt;Alive and popular, but the maintainer says it can break whenever Google changes something (&lt;a href="https://github.com/AWeirdDev/flights/issues/92" rel="noopener noreferrer"&gt;#92&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you need &lt;strong&gt;booking&lt;/strong&gt; (Flight Create Orders, ticketing), none of these help. You need a booking API from an airline, a consolidator or Amadeus Enterprise. The rest of this post is about the read-only price calls, which is what most hobby and research projects used.&lt;/p&gt;

&lt;h2&gt;
  
  
  A drop-in for the three price calls
&lt;/h2&gt;

&lt;p&gt;I published &lt;a href="https://apify.com/kuezi/flight-offers-api" rel="noopener noreferrer"&gt;Flight Offers API&lt;/a&gt; on Apify. It takes the Amadeus Self-Service parameter names, fetches live prices from Google Flights, and returns Amadeus-shaped JSON:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Amadeus Self-Service&lt;/th&gt;
&lt;th&gt;Replacement&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flight Offers Search &lt;code&gt;GET /v2/shopping/flight-offers&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;endpoint: "flight-offers"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same parameters, &lt;code&gt;flight-offer&lt;/code&gt; objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flight Cheapest Date Search &lt;code&gt;GET /v1/shopping/flight-dates&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;endpoint: "cheapest-date"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scans every day in a range, up to 90 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flight Price Analysis &lt;code&gt;GET /v1/analytics/itinerary-price-metrics&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;endpoint: "price-metrics"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quartiles from Google's recent price history for that date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flight Offers Price, Create Orders, Seat Maps&lt;/td&gt;
&lt;td&gt;✘&lt;/td&gt;
&lt;td&gt;Not replaced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built and sell this. It's pay per use: $0.003 per search plus $0.0005 per saved offer. A Flight Offers Search with 10 offers costs $0.008. Apify's free plan includes $5 of monthly credit, which covers about 600 such searches.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating Flight Offers Search (Python)
&lt;/h2&gt;

&lt;p&gt;Before, with the archived SDK:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;amadeus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amadeus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shopping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flight_offers_search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;originLocationCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JFK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destinationLocationCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LAX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;departureDate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-11-12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&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;offer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;itineraries&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&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;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carrierCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departure&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;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After (&lt;code&gt;pip install apify-client&lt;/code&gt;, version 3.x):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_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;amadeus_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Runs the replacement and returns an Amadeus-style {meta, data, dictionaries} dict.&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;kuezi/flight-offers-api&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;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&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;endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&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;RESPONSE&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;amadeus_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flight-offers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;originLocationCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JFK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destinationLocationCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LAX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;departureDate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-11-12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&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;offer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;itineraries&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&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;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carrierCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departure&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;at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop is unchanged. Only &lt;code&gt;response.data&lt;/code&gt; becomes &lt;code&gt;response["data"]&lt;/code&gt;. Output from a real run on 2 October:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;204.00 DL 1788 2026-11-12T12:00:00
204.00 B6 1023 2026-11-12T12:00:00
204.00 B6 623 2026-11-12T15:29:00
204.00 B6 123 2026-11-12T05:45:00
204.00 DL 742 2026-11-12T07:00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The run took 5 seconds. Its status message is a one-line answer, handy for logs and AI agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 flight offers JFK-LAX on 2026-11-12. Cheapest 204.00 USD: DL 1788, departs 2026-11-12T12:00:00, PT6H8M.
Google says prices are typical (typical 144-240).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cheapest Date Search
&lt;/h2&gt;

&lt;p&gt;Amadeus took a date range in &lt;code&gt;departureDate&lt;/code&gt;. So does this:&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;amadeus_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheapest-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;origin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SFO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HNL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;departureDate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-11-01,2026-11-30&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;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;      &lt;span class="c1"&gt;# sorted cheapest first
&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;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departureDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&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;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priceLevel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real run: 30 dates priced in 18 seconds. Cheapest was $163 on 1 November, with 11 more dates at the same price. The priciest was $444 on 20 November. For round trips, pass &lt;code&gt;duration=7&lt;/code&gt; (one trip length; Amadeus-style ranges like &lt;code&gt;1,15&lt;/code&gt; aren't supported).&lt;/p&gt;

&lt;h2&gt;
  
  
  Price Analysis
&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;amadeus_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price-metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;originIataCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JFK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destinationIataCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LAX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;departureDate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-11-12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quartileRanking&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&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;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priceMetrics&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;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="err"&gt;'MINIMUM':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;167.00&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'FIRST':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;178.00&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'MEDIUM':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;178.00&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'THIRD':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;204.00&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'MAXIMUM':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;215.00&lt;/span&gt;&lt;span class="err"&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;Amadeus built these quartiles from its booking data. This version builds them from Google Flights' price history for that exact date, 61 days in this case, and also returns Google's own verdict (&lt;code&gt;typical&lt;/code&gt;) and typical range ($144–240). The idea is the same, but the source isn't, so don't compare the numbers one to one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's different from Amadeus
&lt;/h2&gt;

&lt;p&gt;Be aware of these before you switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prices are what Google Flights shows a shopper&lt;/strong&gt; in the chosen country (&lt;code&gt;country: "us"&lt;/code&gt; by default). That includes low-cost and airline-direct fares that Amadeus often didn't have. They're shopping prices, not bookable fares.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some fields are &lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; because Google doesn't publish them: &lt;code&gt;numberOfBookableSeats&lt;/code&gt;, &lt;code&gt;lastTicketingDate&lt;/code&gt;, &lt;code&gt;price.base&lt;/code&gt;, and &lt;code&gt;aircraft.code&lt;/code&gt; (the aircraft name is in &lt;code&gt;aircraft.name&lt;/code&gt; instead).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round-trip offers&lt;/strong&gt; carry the total trip price but only the outbound itinerary. Google shows return options after you pick an outbound flight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use IATA airport codes.&lt;/strong&gt; There's no airport or city search endpoint yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extras Amadeus never had&lt;/strong&gt; are under &lt;code&gt;googleFlights&lt;/code&gt; in each offer: CO₂, legroom, layovers, Google's price level and a link to the search.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plain HTTP, no SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/kuezi~flight-offers-api/run-sync-get-dataset-items?token=&lt;/span&gt;&lt;span class="nv"&gt;$APIFY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"endpoint":"flight-offers","originLocationCode":"LHR","destinationLocationCode":"JFK","departureDate":"2026-12-01","max":5}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns the &lt;code&gt;data&lt;/code&gt; array directly.&lt;/p&gt;

&lt;p&gt;If you were using another Amadeus Self-Service endpoint and want it replaced, tell me which one in the comments. I'll prioritize by demand.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: this post and the tool were produced with an AI agent (Claude) working for me. Every number above comes from real runs on 2 October 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>travel</category>
      <category>webdev</category>
      <category>python</category>
    </item>
    <item>
      <title>How to scrape any public Telegram channel without the API (no phone number, no login)</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Fri, 02 Oct 2026 12:37:05 +0000</pubDate>
      <link>https://dev.to/ishemah/how-to-scrape-any-public-telegram-channel-without-the-api-no-phone-number-no-login-46ih</link>
      <guid>https://dev.to/ishemah/how-to-scrape-any-public-telegram-channel-without-the-api-no-phone-number-no-login-46ih</guid>
      <description>&lt;p&gt;There are two usual ways to read a Telegram channel from code, and both are annoying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bot API:&lt;/strong&gt; a bot only sees posts from channels &lt;strong&gt;where it's an admin&lt;/strong&gt;, so it can't read someone else's channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MTProto (Telethon / Pyrogram):&lt;/strong&gt; works, but needs an &lt;code&gt;api_id&lt;/code&gt;/&lt;code&gt;api_hash&lt;/code&gt; from my.telegram.org, which is tied to &lt;strong&gt;your phone number&lt;/strong&gt;. You also manage a logged-in session, and risk your account if you scrape hard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a third way that most people miss: &lt;strong&gt;every public channel has a web preview at &lt;code&gt;https://t.me/s/&amp;lt;channel&amp;gt;&lt;/code&gt;&lt;/strong&gt;. It's plain HTML, needs no login or account, and contains the full post text, date, view count, reactions, photos, videos and link previews.&lt;/p&gt;

&lt;p&gt;Here's how it works, a 30-line Python scraper, and the gotchas I ran into.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the preview works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://t.me/s/durov&lt;/code&gt; returns the &lt;strong&gt;20 newest posts&lt;/strong&gt;, ordered oldest-first on the page.&lt;/li&gt;
&lt;li&gt;Each post is a &lt;code&gt;div.tgme_widget_message&lt;/code&gt; with &lt;code&gt;data-post="durov/548"&lt;/code&gt; (channel/ID).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paging back:&lt;/strong&gt; &lt;code&gt;https://t.me/s/durov?before=528&lt;/code&gt; returns the 20 posts before ID 528.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search inside the channel:&lt;/strong&gt; &lt;code&gt;https://t.me/s/durov?q=privacy&lt;/code&gt; (it combines with &lt;code&gt;before&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel stats:&lt;/strong&gt; the right column (&lt;code&gt;.tgme_channel_info&lt;/code&gt;) has the title, description, subscriber count ("10.5M subscribers") and photo/video/link counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private channels, groups, bots and users have no preview.&lt;/strong&gt; The request redirects to &lt;code&gt;t.me/&amp;lt;name&amp;gt;&lt;/code&gt; instead of returning posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful selectors inside a post:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Selector&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.tgme_widget_message_text&lt;/code&gt; (keep the &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; as newlines)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.tgme_widget_message_date time[datetime]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Views&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.tgme_widget_message_views&lt;/code&gt; ("1.9M")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.tgme_reaction&lt;/code&gt; (emoji or custom-emoji ID plus count)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photos&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.tgme_widget_message_photo_wrap&lt;/code&gt; (URL in &lt;code&gt;background-image&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Videos&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.tgme_widget_message_video_player video[src]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link preview&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.tgme_widget_message_link_preview&lt;/code&gt; (title, description, image)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forwarded from&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.tgme_widget_message_forwarded_from_name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A minimal scraper (Python)
&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;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;channel_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&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;Newest-first posts from a public Telegram channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s web preview (t.me/s/&amp;lt;channel&amp;gt;).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;while&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;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://t.me/s/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?before=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&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;Mozilla/5.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.tgme_widget_message[data-post]&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# the page is oldest-first
&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.tgme_widget_message_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.tgme_widget_message_views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data-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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.tgme_widget_message_date time&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;datetime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strip&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;else&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;views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# be polite
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;limit&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;channel_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;durov&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&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="mi"&gt;3&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;548 2026-09-11T16:04:02+00:00 1.9M 🤝 Telegram has become the sponsor of Codeforces — the larges
547 2026-09-06T17:40:39+00:00 2.81M 💸 In just one month, Telegram has awarded over $2,222,000 to
546 2026-08-31T16:30:15+00:00 3.18M 🏳️ Gram Wallet in Telegram is ready and is now accessible to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the right &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt;.&lt;/strong&gt; Video posts contain other &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; tags (durations), so a bare &lt;code&gt;select_one("time")["datetime"]&lt;/code&gt; crashes. Use &lt;code&gt;.tgme_widget_message_date time&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Views are rounded strings&lt;/strong&gt; ("1.9M", "14.3K"). Convert them before you sort or sum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reactions can be custom emoji.&lt;/strong&gt; These have an &lt;code&gt;emoji-id&lt;/code&gt; instead of a Unicode character. Store the ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message IDs have gaps&lt;/strong&gt; where posts were deleted. Page with &lt;code&gt;before=&amp;lt;smallest ID you have&amp;gt;&lt;/code&gt;, not &lt;code&gt;before=id-20&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media URLs expire.&lt;/strong&gt; Photo and video links point to Telegram's CDN with tokens, so download during the run if you need them later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some media isn't rendered&lt;/strong&gt; in the web preview (certain polls and stickers). The post still has its date, views and usually the text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop by date, not by count&lt;/strong&gt;, when you only need recent posts. Check each post's date and break as soon as you pass your cutoff, instead of paging the whole history.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If you don't want to maintain it
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built this.&lt;/em&gt; The &lt;a href="https://apify.com/kuezi/telegram-channel-scraper" rel="noopener noreferrer"&gt;Telegram Channel Scraper on Apify&lt;/a&gt; does all of the above and returns clean JSON or CSV:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;views as numbers, reactions, photos, videos, documents, link previews, forwards, replies, polls, hashtags and mentions;&lt;/li&gt;
&lt;li&gt;channel stats;&lt;/li&gt;
&lt;li&gt;search inside channels, date filters, and a &lt;strong&gt;monitoring mode&lt;/strong&gt; that returns only new posts since the last run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It costs &lt;strong&gt;$0.50 per 1,000 posts&lt;/strong&gt;, with no login and no proxy needed.&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;"channels"&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;"durov"&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://t.me/telegram"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxMessagesPerChannel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dateFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onlyNewMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy to answer questions about edge cases in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>telegram</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Booking.com ran ~40x more Google ads than Expedia last month. I pulled the ad counts for 8 travel brands.</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Fri, 02 Oct 2026 12:33:42 +0000</pubDate>
      <link>https://dev.to/ishemah/bookingcom-ran-40x-more-google-ads-than-expedia-last-month-i-pulled-the-ad-counts-for-8-travel-40no</link>
      <guid>https://dev.to/ishemah/bookingcom-ran-40x-more-google-ads-than-expedia-last-month-i-pulled-the-ad-counts-for-8-travel-40no</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I pulled Google's own ad counts for 8 travel brands from the &lt;strong&gt;Google Ads Transparency Center&lt;/strong&gt;: every ad shown in the &lt;strong&gt;US over the last 30 days&lt;/strong&gt; (2 Sep – 2 Oct 2026), split by platform and format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Booking.com showed ~300k–400k distinct ads, about 40× Expedia (8k–9k).&lt;/strong&gt; Tripadvisor and Priceline were at 100k–200k each, and Kayak at 50k–60k.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking.com is almost entirely Search text ads.&lt;/strong&gt; It ran just &lt;strong&gt;29 video ads&lt;/strong&gt;. &lt;strong&gt;Expedia ran 2k–3k&lt;/strong&gt;, about 90× more, and Priceline ran &lt;strong&gt;none&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking.com also ran 20k–30k ads on Google Maps.&lt;/strong&gt; Kayak ran 0 there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expedia Group refreshes its ads; Booking.com and Kayak barely do.&lt;/strong&gt; Among the first 400 ads Google lists for each brand, the number launched in the last 30 days was 84 for Vrbo, 71 for Hotels.com, 58 for Expedia, 1 for Booking.com and 0 for Kayak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rjet82ccztvjf2w57pw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rjet82ccztvjf2w57pw.png" alt="Distinct Google ads shown in the US in 30 days" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Brand&lt;/th&gt;
&lt;th&gt;All ads (US, 30 days)&lt;/th&gt;
&lt;th&gt;Google Search&lt;/th&gt;
&lt;th&gt;YouTube*&lt;/th&gt;
&lt;th&gt;Google Maps&lt;/th&gt;
&lt;th&gt;Video format&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Booking.com&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;300k–400k&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300k–400k&lt;/td&gt;
&lt;td&gt;90k–100k&lt;/td&gt;
&lt;td&gt;20k–30k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tripadvisor&lt;/td&gt;
&lt;td&gt;100k–200k&lt;/td&gt;
&lt;td&gt;100k–200k&lt;/td&gt;
&lt;td&gt;10k–20k&lt;/td&gt;
&lt;td&gt;4k–5k&lt;/td&gt;
&lt;td&gt;100–200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priceline&lt;/td&gt;
&lt;td&gt;100k–200k&lt;/td&gt;
&lt;td&gt;100k–200k&lt;/td&gt;
&lt;td&gt;30k–40k&lt;/td&gt;
&lt;td&gt;8k–9k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kayak&lt;/td&gt;
&lt;td&gt;50k–60k&lt;/td&gt;
&lt;td&gt;50k–60k&lt;/td&gt;
&lt;td&gt;10k–20k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Airbnb&lt;/td&gt;
&lt;td&gt;30k–40k&lt;/td&gt;
&lt;td&gt;30k–40k&lt;/td&gt;
&lt;td&gt;10k–20k&lt;/td&gt;
&lt;td&gt;100–200&lt;/td&gt;
&lt;td&gt;500–600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vrbo&lt;/td&gt;
&lt;td&gt;9k–10k&lt;/td&gt;
&lt;td&gt;8k–9k&lt;/td&gt;
&lt;td&gt;700–800&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;600–700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expedia&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8k–9k&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5k–6k&lt;/td&gt;
&lt;td&gt;3k–4k&lt;/td&gt;
&lt;td&gt;800–900&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2k–3k&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hotels.com&lt;/td&gt;
&lt;td&gt;1k–2k&lt;/td&gt;
&lt;td&gt;900–1k&lt;/td&gt;
&lt;td&gt;400–500&lt;/td&gt;
&lt;td&gt;200–300&lt;/td&gt;
&lt;td&gt;400–500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* "YouTube" counts every ad shown on YouTube, including text ads in YouTube search. That's why it's much bigger than the video column.&lt;/p&gt;

&lt;p&gt;Google Shopping was small for everyone (25 ads or fewer per brand), and &lt;strong&gt;none of the 8 ran Google Play ads&lt;/strong&gt; in the US in this window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7dfnmavfuexzz3rdq80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7dfnmavfuexzz3rdq80.png" alt="Video ads by brand" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What stands out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Booking.com plays a Search-volume game.&lt;/strong&gt; Hundreds of thousands of distinct text ads usually means programmatic ads: one per destination, hotel or search phrase. The few headlines I could read look templated, for example "Book Now, Change Later" and "Book for Tonight".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Expedia plays a video game.&lt;/strong&gt; Expedia had about 90× more video ads than Booking.com, and its sister brands Vrbo and Hotels.com lean on video too. Airbnb is the only other brand with hundreds of videos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Ads that never die.&lt;/strong&gt; Among the first 400 ads Google lists for Booking.com, half were first shown in January 2022 or earlier, and they were still running on 2 Oct 2026. The archive's earliest "first shown" dates are in October 2021, so many are probably older still. For Expedia, the median listed ad was about 7 months old.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Maps is a real channel for some brands and ignored by others.&lt;/strong&gt; Booking.com (20k–30k), Priceline (8k–9k) and Tripadvisor (4k–5k) advertise heavily on Google Maps. Kayak and Vrbo almost don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Pricing hooks are everywhere.&lt;/strong&gt; Priceline: "$39+ Room Deals - Last Minute Rooms". Tripadvisor: "The 10 Best Hotels (2026) - … Special Offer: Now From $58".&lt;/p&gt;

&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; the &lt;a href="https://adstransparency.google.com/" rel="noopener noreferrer"&gt;Google Ads Transparency Center&lt;/a&gt;, Google's public database of every ad from verified advertisers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters:&lt;/strong&gt; country United States, ads shown from 2 Sep to 2 Oct 2026, one website at a time (&lt;code&gt;booking.com&lt;/code&gt;, &lt;code&gt;expedia.com&lt;/code&gt;, …). I repeated the query for each platform (Search, YouTube, Maps, Shopping, Play) and each format (text, image, video).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counts:&lt;/strong&gt; these are the ad counts Google itself reports for each filter, which come as &lt;strong&gt;ranges&lt;/strong&gt; (e.g. "300000–400000"). They count &lt;strong&gt;distinct ads (creatives), not impressions or spend&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ad-level sample:&lt;/strong&gt; the first 400 ads Google lists per brand (3,200 in total), used for "first shown" dates and the new-ad counts. This sample follows Google's order, so it isn't random.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open data:&lt;/strong&gt; both CSVs (counts per brand × platform/format, plus the 3,200 ad records) are in &lt;a href="https://github.com/ISHEMAH/travel-price-studies" rel="noopener noreferrer"&gt;github.com/ISHEMAH/travel-price-studies&lt;/a&gt;, under CC BY 4.0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Counts are not spend.&lt;/strong&gt; A brand with 300k programmatic text ads may spend less than one with 3k videos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A website search returns every advertiser that ran ads for that domain.&lt;/strong&gt; That's mostly the brand itself, plus a few resellers or partners (e.g. 13 different advertisers for airbnb.com, 386 of the 400 sampled ads from Airbnb, Inc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One country, one 30-day window, one collection day.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ad text is only partly available.&lt;/strong&gt; Google archives many text ads as images, so only some ads expose readable copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduce it (or watch your own competitors)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the tool I used.&lt;/em&gt; It's the &lt;a href="https://apify.com/kuezi/google-ads-transparency-scraper" rel="noopener noreferrer"&gt;Google Ads Transparency Scraper on Apify&lt;/a&gt;. Give it advertiser names, IDs or websites, and it returns every ad with format, first and last shown, preview, link, and (optionally) the ad text, landing pages and YouTube video. It costs $1 per 1,000 ads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"searchTargets"&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.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;"airbnb.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;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"youtube"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastDays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxAdsPerTarget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"includeAdDetails"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install apify-client
&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_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;kuezi/google-ads-transparency-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;searchTargets&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;your-competitor.com&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;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastDays&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;onlyNewAds&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;ad&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;ad&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ad&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firstShown&lt;/span&gt;&lt;span class="sh"&gt;"&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;span class="n"&gt;ad&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it daily with &lt;code&gt;onlyNewAds: true&lt;/code&gt; and you get a feed of every new ad your competitors launch.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;More open travel data from the same tools:&lt;/em&gt; &lt;a href="https://dev.to/ishemah/i-compared-24-booking-sites-on-47-hotels-bookingcom-expedia-and-hotelscom-were-26-above-the-4egk"&gt;24 booking sites compared on 47 hotels&lt;/a&gt; · &lt;a href="https://dev.to/ishemah/i-priced-150-flights-across-november-tuesday-was-the-cheapest-day-to-fly-friday-cost-20-more-3i7i"&gt;the cheapest day to fly&lt;/a&gt;&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>webscraping</category>
      <category>datascience</category>
      <category>seo</category>
    </item>
    <item>
      <title>I priced 150 flights across November. Tuesday was the cheapest day to fly, Friday cost 20% more.</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Fri, 02 Oct 2026 11:00:48 +0000</pubDate>
      <link>https://dev.to/ishemah/i-priced-150-flights-across-november-tuesday-was-the-cheapest-day-to-fly-friday-cost-20-more-3i7i</link>
      <guid>https://dev.to/ishemah/i-priced-150-flights-across-november-tuesday-was-the-cheapest-day-to-fly-friday-cost-20-more-3i7i</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; 5 routes × every departure day in November 2026, cheapest one-way economy fare from Google Flights. Prices were seen on 2 Oct 2026.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tuesday was the cheapest day&lt;/strong&gt; to depart, &lt;strong&gt;−5%&lt;/strong&gt; vs the route's typical price. Monday and Wednesday were close behind (−3%, −2%).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friday was the most expensive, +20%.&lt;/strong&gt; Saturday and Sunday were both about +13%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Picking the wrong day cost up to 2.7×:&lt;/strong&gt; San Francisco → Honolulu was &lt;strong&gt;$163&lt;/strong&gt; on the cheapest day and &lt;strong&gt;$444&lt;/strong&gt; on the priciest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Sunday after Thanksgiving (29 Nov)&lt;/strong&gt; was the most expensive day of the month on New York → LA ($489 vs $184 at best) and on Chicago → Miami ($195 vs $113).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17ulb9tstbhp5tw6rha7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17ulb9tstbhp5tw6rha7.png" alt="Tuesday was the cheapest day to fly, Friday cost 20% more than the route's typical price" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The routes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Cheapest day&lt;/th&gt;
&lt;th&gt;Priciest day&lt;/th&gt;
&lt;th&gt;Spread&lt;/th&gt;
&lt;th&gt;Google's "price level" on most days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New York JFK → Los Angeles&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$184&lt;/strong&gt; (Fri 6, Mon 16, Thu 19)&lt;/td&gt;
&lt;td&gt;$489 (Sun 29)&lt;/td&gt;
&lt;td&gt;+166%&lt;/td&gt;
&lt;td&gt;typical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;London LHR → New York JFK&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$399&lt;/strong&gt; (14 different days from 11 Nov)&lt;/td&gt;
&lt;td&gt;$641 (Sun 1)&lt;/td&gt;
&lt;td&gt;+61%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;low&lt;/strong&gt; on 21 of 30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chicago ORD → Miami&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$113&lt;/strong&gt; (Sat 14)&lt;/td&gt;
&lt;td&gt;$195 (Sun 29)&lt;/td&gt;
&lt;td&gt;+73%&lt;/td&gt;
&lt;td&gt;typical / high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;San Francisco → Honolulu&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$163&lt;/strong&gt; (12 different days, every one a Sun–Wed)&lt;/td&gt;
&lt;td&gt;$444 (Fri 20)&lt;/td&gt;
&lt;td&gt;+172%&lt;/td&gt;
&lt;td&gt;low / typical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Los Angeles → Tokyo NRT&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$329&lt;/strong&gt; (Wed 25)&lt;/td&gt;
&lt;td&gt;$823 (Fri 20)&lt;/td&gt;
&lt;td&gt;+150%&lt;/td&gt;
&lt;td&gt;typical&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbyc9by6ja0r5702eymwd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbyc9by6ja0r5702eymwd.png" alt="Cheapest vs priciest day in November on each route" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;p&gt;For each route I searched every departure date from 1 to 30 November: &lt;strong&gt;150 searches&lt;/strong&gt; in total, one-way, economy, 1 adult, USD, US point of sale. I took the cheapest itinerary per search.&lt;/p&gt;

&lt;p&gt;The weekday numbers compare each day's cheapest fare with &lt;strong&gt;that route's median&lt;/strong&gt; over the month, then average across the 5 routes. A $300 route and a $100 route count equally.&lt;/p&gt;

&lt;p&gt;Google also labels prices as &lt;em&gt;low&lt;/em&gt;, &lt;em&gt;typical&lt;/em&gt; or &lt;em&gt;high&lt;/em&gt; against its own history. I recorded that label too: London → New York was "low" on 21 of 30 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All the data and the analysis script are open:&lt;/strong&gt; &lt;a href="https://github.com/ISHEMAH/travel-price-studies" rel="noopener noreferrer"&gt;github.com/ISHEMAH/travel-price-studies&lt;/a&gt; (CSV, CC BY 4.0). &lt;code&gt;python3 analysis.py&lt;/code&gt; reproduces every number in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Five routes and one month&lt;/strong&gt;, and November includes US Thanksgiving. Read it as a strong hint, not a law.&lt;/li&gt;
&lt;li&gt;Prices were seen on &lt;strong&gt;one day&lt;/strong&gt; (2 Oct). Fares change as departure gets closer.&lt;/li&gt;
&lt;li&gt;Cheapest fares are often basic economy, so check bags and seat rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be flexible by a day or two.&lt;/strong&gt; Use a date grid, because the difference can be bigger than any coupon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Friday and Sunday departures&lt;/strong&gt; when you can, especially around holidays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch Google's price level.&lt;/strong&gt; When it says &lt;em&gt;low&lt;/em&gt;, that's usually your moment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reproduce it (or scan your own route)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the scraper I used.&lt;/em&gt; It's the &lt;a href="https://apify.com/kuezi/google-flights-scraper" rel="noopener noreferrer"&gt;Google Flights Scraper on Apify&lt;/a&gt;. Set &lt;code&gt;scanDepartureUntil&lt;/code&gt; and it searches every day in the range, then reports the &lt;strong&gt;cheapest day per route&lt;/strong&gt; in &lt;code&gt;SUMMARY&lt;/code&gt;.&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;"routes"&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;"JFK-LAX"&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-JFK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-MIA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SFO-HNL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LAX-NRT"&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;"2026-11-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scanDepartureUntil"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxFlightsPerSearch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install apify-client
&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;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;routes&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;JFK-LAX&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;departureDate&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;2026-11-01&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;scanDepartureUntil&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;2026-11-30&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;maxFlightsPerSearch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_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;kuezi/google-flights-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;summary&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;SUMMARY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheapestByRoute&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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheapestPrice&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;on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departureDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schedule it weekly and you have a fare-drop alert for your own trips.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Same method, hotels:&lt;/em&gt; &lt;a href="https://dev.to/ishemah/i-compared-24-booking-sites-on-47-hotels-bookingcom-expedia-and-hotelscom-were-26-above-the-4egk"&gt;I compared 24 booking sites on 47 hotels. Booking.com, Expedia and Hotels.com were ~26% above the cheapest.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Update: robustness check (thanks to a reader)
&lt;/h2&gt;

&lt;p&gt;A reader pointed out that November has only four Fridays, and two of them sit in the Thanksgiving window. Rerunning the same method on &lt;strong&gt;1–19 Nov only&lt;/strong&gt;: Mon −5.2%, Tue −2.8%, Wed +2.1%, Thu +10.2%, &lt;strong&gt;Fri +13.9%&lt;/strong&gt;, Sat +7.6%, Sun +5.2%.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The holiday inflated the headline: Friday falls from +19.7% to +13.9%, and Sunday from +12.7% to +5.2%.&lt;/li&gt;
&lt;li&gt;Monday edges out Tuesday as the cheapest day.&lt;/li&gt;
&lt;li&gt;Friday is still the priciest, but that rests on just 2 Fridays × 5 routes. Treat it as a hint, not a rule. A non-holiday month is the next run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Update 2 (17 Oct): same flights, three pull dates
&lt;/h2&gt;

&lt;p&gt;Another point from the same reader: all 150 fares were pulled on one day, so the early-November dates were also the closest to departure. I pulled the same searches again on 9 and 17 Oct and compared weekdays at the same lead time (route + weekday + 7-day lead-time bucket).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Friday is still the priciest day&lt;/strong&gt;, at about 8–12% above the average weekday: +12.0% for all of November, +8.1% for 1–19 Nov. That's smaller than the headline +19.7%, which mixed in lead time and Thanksgiving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunday is also pricier&lt;/strong&gt;, at about +7%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monday and Tuesday are both 7–10% below average.&lt;/strong&gt; The difference between them is noise, so "Tuesday is cheapest" really means "early in the week is cheapest".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fares held, then rose.&lt;/strong&gt; From 2 to 9 Oct the median fare didn't change. From 9 to 17 Oct it rose 5.2%, with 84 of 150 fares up and 41 down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Data and script (&lt;code&gt;lead_time.py&lt;/code&gt;): &lt;a href="https://github.com/ISHEMAH/travel-price-studies" rel="noopener noreferrer"&gt;travel-price-studies on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>travel</category>
      <category>webscraping</category>
      <category>datascience</category>
      <category>python</category>
    </item>
    <item>
      <title>I compared 24 booking sites on 47 hotels. Booking.com, Expedia and Hotels.com were ~26% above the cheapest.</title>
      <dc:creator>Hugues Ishema</dc:creator>
      <pubDate>Fri, 02 Oct 2026 09:37:38 +0000</pubDate>
      <link>https://dev.to/ishemah/i-compared-24-booking-sites-on-47-hotels-bookingcom-expedia-and-hotelscom-were-26-above-the-4egk</link>
      <guid>https://dev.to/ishemah/i-compared-24-booking-sites-on-47-hotels-bookingcom-expedia-and-hotelscom-were-26-above-the-4egk</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; 47 hotels in 5 cities, 24 booking sites per hotel on average, same dates for all (12–15 Nov 2026, 3 nights, 2 adults, USD).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;big names were a median 26–27% more expensive&lt;/strong&gt; than the cheapest site for the &lt;em&gt;same&lt;/em&gt; hotel: Booking.com +27%, Priceline +27%, Expedia +26%, Hotels.com +26%.&lt;/li&gt;
&lt;li&gt;The cheapest offer usually came from &lt;strong&gt;smaller sites&lt;/strong&gt;: Super.com, Vio.com and Traveluro were cheapest on 9 hotels each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking Expedia, Hotels.com, Orbitz, Travelocity and CheapTickets is one check, not five.&lt;/strong&gt; They showed the exact same price on 37 of 45 hotels (all Expedia Group).&lt;/li&gt;
&lt;li&gt;The hotel's &lt;strong&gt;own website&lt;/strong&gt; was the cheapest on only &lt;strong&gt;4 of 34&lt;/strong&gt; hotels where I could identify it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google's headline price matched the cheapest site 42 times out of 47.&lt;/strong&gt; It's a good reference if you only look at one number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbo27shwd77ee2m8k531h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbo27shwd77ee2m8k531h.png" alt="Same room, same dates: the big booking sites cost about 26% more than the cheapest one" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I did it
&lt;/h2&gt;

&lt;p&gt;Google Hotels shows a "Prices" list for each hotel: every booking site it has a rate from. I pulled that list for the top 10 hotels Google returned in &lt;strong&gt;Paris, London, New York, Barcelona and Bangkok&lt;/strong&gt;, all for the same stay. Then for each hotel I compared every site's &lt;strong&gt;total price for the stay&lt;/strong&gt; with the cheapest one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Point of sale: US (&lt;code&gt;gl=us&lt;/code&gt;), currency USD, collected on &lt;strong&gt;2 Oct 2026&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;50 hotels requested. 47 had at least two sites listed, which gives 24.4 sites per hotel on average.&lt;/li&gt;
&lt;li&gt;"Premium" = (site's total − cheapest total) / cheapest total, for the same hotel. The median is taken across hotels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All the data and the analysis script are open:&lt;/strong&gt; &lt;a href="https://github.com/ISHEMAH/travel-price-studies" rel="noopener noreferrer"&gt;github.com/ISHEMAH/travel-price-studies&lt;/a&gt; (CSV, CC BY 4.0). &lt;code&gt;python3 analysis.py&lt;/code&gt; reproduces every number in this post.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Hotels it was listed on&lt;/th&gt;
&lt;th&gt;Median premium vs cheapest&lt;/th&gt;
&lt;th&gt;Times it &lt;em&gt;was&lt;/em&gt; the cheapest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Super.com&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;+8.5%&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kiwi.com&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;+12.7%&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trip.com&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;+21.9%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hotels.com&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;+25.8%&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expedia&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;+26.4%&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priceline&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;+26.8%&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Booking.com&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;+27.2%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eDreams&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;+27.7%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fun5ja3yer8rec1ryacxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fun5ja3yer8rec1ryacxu.png" alt="Three things worth knowing before you book a hotel" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gap between the cheapest and the priciest site for the same room was big:&lt;/strong&gt; median &lt;strong&gt;67%&lt;/strong&gt;, ranging from New York (41%) to London (98%) and Bangkok (116%).&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats (read these before you book the cheapest one)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cheapest isn't always best.&lt;/strong&gt; Smaller sites can mean stricter cancellation rules, member-only rates, or fees added at checkout. Check the refund policy and the final price before paying.&lt;/li&gt;
&lt;li&gt;This is &lt;strong&gt;one stay window, five cities, one day of prices&lt;/strong&gt;. Rates move hourly, and other dates or countries can look different.&lt;/li&gt;
&lt;li&gt;I compared the prices exactly as Google listed them. I didn't book anything.&lt;/li&gt;
&lt;li&gt;"The hotel's own site" was matched by name, so a few may be missed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the hotel on Google Hotels and look at the &lt;strong&gt;whole&lt;/strong&gt; price list, not the first logo.&lt;/li&gt;
&lt;li&gt;Skip comparing Expedia vs Hotels.com vs Orbitz vs Travelocity: they're almost always the same price.&lt;/li&gt;
&lt;li&gt;If a smaller site is much cheaper, check its reviews and refund policy, then decide whether the saving is worth it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reproduce it (or track your own hotels)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the scraper I used.&lt;/em&gt; It's the &lt;a href="https://apify.com/kuezi/google-hotels-scraper" rel="noopener noreferrer"&gt;Google Hotels Scraper on Apify&lt;/a&gt;. With &lt;code&gt;includeVendorPrices&lt;/code&gt; turned on, it returns every booking site's price for your dates.&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;"queries"&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;"Paris"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Barcelona"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bangkok"&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;"2026-11-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checkOutDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"adults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxResultsPerQuery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeVendorPrices"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install apify-client
&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;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;queries&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;Lisbon&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;checkInDate&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;2026-11-12&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;checkOutDate&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;2026-11-15&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;adults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&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;USD&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;maxResultsPerQuery&lt;/span&gt;&lt;span class="sh"&gt;"&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeVendorPrices&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_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;kuezi/google-hotels-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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalPrice&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendorPrices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])}&lt;/span&gt;
    &lt;span class="k"&gt;if&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;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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;hotel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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 daily and you have a rate-shopping tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update (17 Oct): does the same cheap site keep winning?
&lt;/h2&gt;

&lt;p&gt;I re-ran the same 50 searches on 9 and 17 Oct. With a rule fixed before the new data, I tested whether three small sites (Super.com, Traveluro, Vio.com) keep most of the saving against the cheapest big brand.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;9 Oct:&lt;/strong&gt; they captured 75% of the gap (95% CI 56–90%). Pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 Oct:&lt;/strong&gt; 65% (95% CI 48–82%). Inconclusive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verdict: inconclusive.&lt;/strong&gt; A fixed shortlist of "cheap sites" isn't reliable enough to recommend. Compare every site, or start from Google's headline price.&lt;/li&gt;
&lt;li&gt;The cheapest site for a hotel repeats from week to week far more often than chance: 63% of hotels from 2 to 9 Oct, 56% from 9 to 17 Oct. Over longer gaps it repeats less: 40% from 2 to 17 Oct.&lt;/li&gt;
&lt;li&gt;On the same hotels, the gap between the cheapest big brand and the cheapest small site fell from about 18% on 2 Oct to 13.5% on 17 Oct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full method, data and scripts: &lt;a href="https://github.com/ISHEMAH/travel-price-studies" rel="noopener noreferrer"&gt;travel-price-studies on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>travel</category>
      <category>webscraping</category>
      <category>datascience</category>
      <category>python</category>
    </item>
  </channel>
</rss>
