<?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: Emmanuel Uchenna</title>
    <description>The latest articles on DEV Community by Emmanuel Uchenna (@eunit).</description>
    <link>https://dev.to/eunit</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%2F118821%2F5bddf58c-edd7-47c2-a806-44f549477601.jpg</url>
      <title>DEV Community: Emmanuel Uchenna</title>
      <link>https://dev.to/eunit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eunit"/>
    <language>en</language>
    <item>
      <title>How to Build an Automated SEO Pipeline: Connecting Sitemap Generator and Google Indexer to Notion</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:15:19 +0000</pubDate>
      <link>https://dev.to/apify/how-to-build-an-automated-seo-pipeline-connecting-sitemap-generator-and-google-indexer-to-notion-3f51</link>
      <guid>https://dev.to/apify/how-to-build-an-automated-seo-pipeline-connecting-sitemap-generator-and-google-indexer-to-notion-3f51</guid>
      <description>&lt;p&gt;Generating an XML sitemap is only the first step in modern technical Search Engine Optimization (SEO). If you have ever published a new blog post, launched a product line on an e-commerce platform, or updated critical landing pages, you know the frustration of waiting days, weeks, or even months for Google to crawl and index your content.&lt;/p&gt;

&lt;p&gt;Relying on search engine crawlers to discover changes organically is no longer viable for high-growth websites. If you run a dynamic content platform, an online store with rapidly changing inventory, or a news publication, delayed indexing directly equates to lost traffic, lower visibility, and missed revenue.&lt;/p&gt;

&lt;p&gt;In this guide, we will walk through the process of building an automated, enterprise-grade SEO pipeline. We will start by covering automated URL discovery using the &lt;a href="https://apify.com/eunit/sitemap-generator" rel="noopener noreferrer"&gt;Fast Sitemap Generator&lt;/a&gt; &lt;a href="https://www.apify.com?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actor&lt;/a&gt;. Then, we will look at programmatically pushing those URLs to Google using the &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt; Actor. Finally, we will solve the critical tracking challenge by using &lt;a href="https://blog.apify.com/what-is-model-context-protocol/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; Connectors to stream real-time indexing logs directly into a &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; database.&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%2F608psx0zx4lbunpittzs.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%2F608psx0zx4lbunpittzs.png" alt="Build an Automated SEO Pipeline: Connecting Sitemap Generator and Google Indexer to Notion" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The evolution of programmatic indexing
&lt;/h2&gt;

&lt;p&gt;Historically, website owners managed SEO by submitting a static Extensible Markup Language (XML) sitemap through &lt;a href="https://search.google.com/search-console" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt;. While essential, submitting a sitemap is fundamentally a passive notification. You are effectively dropping a note in Google's mailbox, asking Googlebot to visit your site whenever it gets around to it.&lt;/p&gt;

&lt;p&gt;For small, static websites, this passive approach works. However, for modern dynamic sites, it falls short due to several structural challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Crawl budget constraints&lt;/strong&gt;: Search engines assign a finite "crawl budget" to every domain. On large sites with thousands of pages, Googlebot may exhaust its budget on legacy pages, completely ignoring newly published or updated content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orphaned or deep pages&lt;/strong&gt;: Content buried deep within subdirectories or pagination chains often fails to receive internal link equity, making it nearly invisible to organic crawling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-sensitive content&lt;/strong&gt;: E-commerce price updates, breaking news, real estate listings, and job postings require instant indexation. Waiting two weeks for organic discovery renders the content stale before it ever reaches search results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop crawler overhead&lt;/strong&gt;: Traditional site auditors like Screaming Frog run locally, consuming heavy system resources, tying up developer machines, and requiring manual CSV exports to process updates.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Moving from passive crawling to active push pipelines
&lt;/h3&gt;

&lt;p&gt;To solve these limitations, search engines introduced programmatic indexing APIs. Instead of waiting for Googlebot to discover changes, you can actively send a push notification to Google the instant a page is created or updated.&lt;/p&gt;

&lt;p&gt;By combining cloud-based crawling with active API submissions and real-time database logging, you can replace manual SEO workflows with a fully automated "set and forget" pipeline.&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%2Foea5w26jalag6dnu3dkk.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%2Foea5w26jalag6dnu3dkk.png" alt="Moving from passive crawling to active push pipelines" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 1: Automated URL discovery with Fast Sitemap Generator
&lt;/h2&gt;

&lt;p&gt;Before you can submit URLs to Google, you need an accurate, deduplicated list of active links from your website. Hand-coding XML files or relying on CMS plugins that crash on large databases creates maintenance bottlenecks.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/sitemap-generator" rel="noopener noreferrer"&gt;Fast Sitemap Generator&lt;/a&gt; Actor runs on the &lt;a href="https://apify.com" rel="noopener noreferrer"&gt;Apify platform&lt;/a&gt; as a serverless cloud program. It uses a Direct Connection to crawl websites at high speeds directly from data centers, without requiring proxy infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring the URL crawler
&lt;/h3&gt;

&lt;p&gt;Setting up automated URL discovery requires configuring key parameters inside the &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify Console&lt;/a&gt; to balance thoroughness with execution speed.&lt;/p&gt;

&lt;p&gt;Here is an example input configuration for a production crawl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxCrawlDepth"&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;"maxPagesPerCrawl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".*/blog/.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".*/products/.*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"excludePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".*/admin/.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".*/login.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".*/cart.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".*/checkout/.*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sitemapFormats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"txt"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"respectRobotsTxt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changefreq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"daily"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"defaultPriority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeImages"&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;h3&gt;
  
  
  Key configuration settings explained
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start URLs (&lt;code&gt;startUrls&lt;/code&gt;)&lt;/strong&gt;: The entry point for the crawler. Usually, this is your primary domain homepage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max Crawl Depth (&lt;code&gt;maxCrawlDepth&lt;/code&gt;)&lt;/strong&gt;: Controls how deep the crawler traverses link structures. A depth of &lt;code&gt;3&lt;/code&gt; to &lt;code&gt;5&lt;/code&gt; is typically sufficient to discover all essential content while preventing infinite loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regex Filtering (&lt;code&gt;includePatterns&lt;/code&gt; and &lt;code&gt;excludePatterns&lt;/code&gt;)&lt;/strong&gt;: Prevents cluttering your index with utility pages. You should explicitly exclude administrative paths like &lt;code&gt;.*/admin/.*&lt;/code&gt;, checkout pages &lt;code&gt;.*/cart.*&lt;/code&gt;, and query parameters that create duplicate content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robots.txt Respect (&lt;code&gt;respectRobotsTxt&lt;/code&gt;)&lt;/strong&gt;: Ensures your pipeline adheres to site governance rules and crawler restrictions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Output storage and Dataset generation
&lt;/h3&gt;

&lt;p&gt;When execution completes, the Actor stores output artifacts in two locations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Key-Value Store&lt;/strong&gt;: Contains compiled &lt;code&gt;sitemap.xml&lt;/code&gt;, &lt;code&gt;sitemap.html&lt;/code&gt;, and &lt;code&gt;sitemap.txt&lt;/code&gt; files ready for static hosting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apify Dataset&lt;/strong&gt;: Stores structured JSON records for every discovered page. The Dataset ID produced during this run serves as the input payload for the next stage in our pipeline.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Stage 2: Programmatic submission via Google Cloud infrastructure
&lt;/h2&gt;

&lt;p&gt;With your site URLs discovered and structured into an Apify Dataset, the next phase is notifying Google. This step relies on the official Google Indexing API, which allows site owners to submit batch update requests directly to Google's indexing pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Setting up Google Cloud credentials
&lt;/h3&gt;

&lt;p&gt;Before making programmatic requests, you must configure authentication through the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2Fu4tc103vitcg242vl84s.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.amazonaws.com%2Fuploads%2Farticles%2Fu4tc103vitcg242vl84s.png" alt="Google Cloud Console" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a Google Cloud Project&lt;/strong&gt;: Log in to Google Cloud Console, click the project dropdown, and select &lt;strong&gt;New Project&lt;/strong&gt;. Name it &lt;code&gt;seo-automation-pipeline&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable the Indexing API&lt;/strong&gt;: Navigate to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Library&lt;/strong&gt;, search for "Web Search Indexing API", and click &lt;strong&gt;Enable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a Service Account&lt;/strong&gt;: Go to &lt;strong&gt;IAM &amp;amp; Admin &amp;gt; Service Accounts&lt;/strong&gt;, click &lt;strong&gt;Create Service Account&lt;/strong&gt;, name it &lt;code&gt;indexing-bot&lt;/code&gt;, and assign it the &lt;strong&gt;Project &amp;gt; Editor&lt;/strong&gt; role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate a JSON Key&lt;/strong&gt;: Select your new service account, go to the &lt;strong&gt;Keys&lt;/strong&gt; tab, click &lt;strong&gt;Add Key &amp;gt; Create new key&lt;/strong&gt;, select &lt;strong&gt;JSON&lt;/strong&gt;, and download the file. Keep this file secure, as it grants API permissions.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service Account Email Example:
indexing-bot@seo-automation-pipeline.iam.gserviceaccount.com
&lt;/code&gt;&lt;/pre&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.amazonaws.com%2Fuploads%2Farticles%2Far9p2a94i4o4gi7wgley.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.amazonaws.com%2Fuploads%2Farticles%2Far9p2a94i4o4gi7wgley.png" alt="Enable Indexing API" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Granting Google Search Console access
&lt;/h3&gt;

&lt;p&gt;Enabling the API in Google Cloud is not enough on its own. Google requires verified domain ownership to prevent unauthorized indexing requests.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://search.google.com/search-console" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select your verified domain property.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Settings &amp;gt; Users and permissions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add user&lt;/strong&gt;, paste your service account email address, set the role to &lt;strong&gt;Owner&lt;/strong&gt;, and save.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;IMPORTANT&lt;br&gt;
Google Search Console requires &lt;strong&gt;Owner&lt;/strong&gt; permissions for Service Accounts calling the Indexing API. Setting lower permission levels such as "Full" or "Restricted" will cause authentication errors during API calls.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Bypassing DIY limitations: Python vs. Apify Actors
&lt;/h3&gt;

&lt;p&gt;Developers often attempt to write custom scripts to interact with the Google Indexing API. Below is a standard Python implementation using official Google authentication libraries:&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;json&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;google.oauth2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;service_account&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.auth.transport.requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AuthorizedSession&lt;/span&gt;

&lt;span class="n"&gt;SERVICE_ACCOUNT_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-key.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;INDEXING_API_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://indexing.googleapis.com/v3/urlNotifications:publish&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_authenticated_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="n"&gt;scopes&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;https://www.googleapis.com/auth/indexing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_service_account_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;key_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;scopes&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AuthorizedSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&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;submit_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&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;action_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;URL_UPDATED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&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="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action_type&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INDEXING_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="k"&gt;return&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;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_authenticated_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SERVICE_ACCOUNT_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;target_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/blog/automated-seo-pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
 &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;submit_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&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;Status Code: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&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="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&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;Response: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why custom scripts break at scale
&lt;/h3&gt;

&lt;p&gt;While the DIY Python script works for single URLs, operating it in production exposes major infrastructure hurdles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rate limit handling&lt;/strong&gt;: Google enforces daily quotas (typically 200 URL notifications per day for standard projects). Custom scripts will fail with &lt;code&gt;HTTP 429 Too Many Requests&lt;/code&gt; unless you write exponential backoff algorithms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State management&lt;/strong&gt;: A basic script does not remember which URLs were submitted yesterday. Running it daily resubmits duplicate URLs, wasting your daily quota.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution server maintenance&lt;/strong&gt;: You must deploy the script to a cloud server, manage cron schedules, and store credentials securely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The solution: Google Indexer &amp;amp; Instant SEO Submitter
&lt;/h3&gt;

&lt;p&gt;Instead of writing and hosting custom boilerplate code, we use the &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt; Actor.&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%2Fo2soxiqbbn3b08op470c.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%2Fo2soxiqbbn3b08op470c.png" alt="Google Indexer &amp;amp; Instant SEO Submitter" width="800" height="788"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Custom Python Script&lt;/th&gt;
&lt;th&gt;Google Indexer Actor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quotas &amp;amp; Rate Limiting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual handling required&lt;/td&gt;
&lt;td&gt;Built-in automatic rate limiting &amp;amp; backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hardcoded or manual files&lt;/td&gt;
&lt;td&gt;Direct integration with Apify Datasets &amp;amp; Sitemaps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server hosting costs&lt;/td&gt;
&lt;td&gt;Pay-Per-Event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Test Mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires code flags&lt;/td&gt;
&lt;td&gt;Built-in dry-run toggle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom logging setup&lt;/td&gt;
&lt;td&gt;Real-time console logs &amp;amp; dataset outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To configure the Actor, pass the JSON credentials and your Dataset ID from Stage 1 directly into the input schema:&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;"datasetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_STAGE_1_DATASET_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"serviceAccountJson"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"seo-automation-pipeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"private_key_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abcdef123456789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"private_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-----END PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indexing-bot@seo-automation-pipeline.iam.gserviceaccount.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"URL_UPDATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"testMode"&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;h3&gt;
  
  
  Stage 3: The missing link: Logging to Notion via MCP Connectors
&lt;/h3&gt;

&lt;p&gt;Automating crawls and API submissions solves the execution side of SEO. However, engineering teams and SEO leads still face a visibility gap: &lt;strong&gt;How do you monitor pipeline results without manually checking console logs after each run?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Exporting CSVs after every run is tedious, and setting up custom webhooks requires maintaining dedicated receiver endpoints. This is where &lt;a href="https://docs.apify.com/integrations/mcp-connectors" rel="noopener noreferrer"&gt;MCP connectors&lt;/a&gt; fit into the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding MCP connectors
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.apify.com/what-is-model-context-protocol/" rel="noopener noreferrer"&gt;The Model Context Protocol (MCP)&lt;/a&gt; is an open standard that lets Actors call third-party services like Notion, Slack, and GitHub on your behalf, using your own credentials, without the Actor ever holding your token directly.&lt;/p&gt;

&lt;p&gt;MCP connectors on Apify work differently from the Apify MCP server. The &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt; exposes Apify Actors as tools to external AI clients (such as Claude or Cursor). MCP connectors do the opposite: they let Actors running on the Apify platform call external MCP-compatible services. The two are independent and can be used together.&lt;/p&gt;

&lt;p&gt;In this pipeline, we use the &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; Actor together with an Apify MCP connector to push the Google Indexer's results into a Notion database automatically after each run.&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%2Fxewket6m8suo4xdqm7yp.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%2Fxewket6m8suo4xdqm7yp.png" alt="Setting up the Notion database" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the Notion database
&lt;/h3&gt;

&lt;p&gt;Create a dedicated database in your Notion workspace titled &lt;strong&gt;SEO Indexing Tracker&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Configure the database columns with the following properties:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column Name&lt;/th&gt;
&lt;th&gt;Property Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;The target webpage URL submitted to Google&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Submission Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Indexed&lt;/code&gt;, &lt;code&gt;Failed&lt;/code&gt;, or &lt;code&gt;Quota Exceeded&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;The status response code returned by Google (e.g., &lt;code&gt;200&lt;/code&gt;, &lt;code&gt;429&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Last Updated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Timestamp of when the request occurred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Source Actor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Name of the Apify Actor that executed the request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Authorizing the Notion MCP connector
&lt;/h3&gt;

&lt;p&gt;MCP connectors are authorized once at the account level, not on individual Actor or task pages. To set one up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify Console&lt;/a&gt; and go to &lt;strong&gt;Settings → API &amp;amp; Integrations → MCP connectors&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add connector&lt;/strong&gt; and select &lt;strong&gt;Notion&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Complete the OAuth flow — you will be redirected to Notion to authorize access to your workspace. The Apify platform never sees your token directly; authentication is handled server-side via the MCP proxy.&lt;/li&gt;
&lt;li&gt;Once authorized, the connector appears as a selectable option when running any compatible Actor.&lt;/li&gt;
&lt;/ol&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%2F7xr1hlpwn129dvuy2nlk.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%2F7xr1hlpwn129dvuy2nlk.png" alt="Notion MCP Connector" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting the Notion Uploader Actor
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; Actor reads any Apify dataset and uploads its rows into Notion. It accepts your MCP connector ID so you do not need to paste a Notion API key anywhere.&lt;/p&gt;

&lt;p&gt;When the Notion Uploader runs as an Actor-to-Actor integration (triggered by the completion of the Google Indexer run), it automatically picks up the triggering run's output dataset — no explicit &lt;code&gt;datasetId&lt;/code&gt; is needed in the payload template.&lt;/p&gt;

&lt;p&gt;Here is an example input for the Notion Uploader when configured as a standalone run:&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;"notionConnector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-connector-id&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notionDatabaseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.notion.so/workspace/SEO-Indexing-Tracker-&amp;lt;db-id&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"datasetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;google-indexer-output-dataset-id&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dedupeMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"upsert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keyProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dryRun"&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Enable &lt;code&gt;"dryRun": true&lt;/code&gt; on your first run. It resolves the field mapping, validates it against your Notion schema, and shows exactly what would be written — without creating any rows.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Orchestrating the full "set and forget" pipeline
&lt;/h2&gt;

&lt;p&gt;Now that all three Actors are configured, we can chain them into an automated workflow: &lt;a href="https://apify.com/eunit/sitemap-generator" rel="noopener noreferrer"&gt;Fast Sitemap Generator&lt;/a&gt; discovers URLs, &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt; submits them to Google, and &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; logs the results into Notion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Creating saved Actor tasks
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;Actor task&lt;/strong&gt; is a saved, reusable configuration of an Actor in &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify Console&lt;/a&gt;. Tasks let you pre-define all input parameters so they can be run on demand, via API, or on a schedule.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Crawler task&lt;/strong&gt;: Open &lt;a href="https://apify.com/eunit/sitemap-generator" rel="noopener noreferrer"&gt;Fast Sitemap Generator&lt;/a&gt; in Apify Console, configure your &lt;code&gt;startUrls&lt;/code&gt;, crawl depth, and regex filters, then click &lt;strong&gt;Save as a new task&lt;/strong&gt; in the top-right corner. Name it &lt;code&gt;sitemap-generator-task&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexer task&lt;/strong&gt;: Open &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt;, fill in your Google Cloud Service Account JSON key, then click &lt;strong&gt;Save as a new task&lt;/strong&gt;. Name it &lt;code&gt;google-indexer-task&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uploader task&lt;/strong&gt;: Open &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt;, set your Notion MCP connector and target database URL, then click &lt;strong&gt;Save as a new task&lt;/strong&gt;. Name it &lt;code&gt;notion-uploader-task&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All saved tasks are listed under &lt;strong&gt;Saved tasks&lt;/strong&gt; in the Apify Console left navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Chaining tasks using Actor-to-Actor integrations
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Integrations&lt;/strong&gt; tab on each Actor or task page lets you chain runs together. When one task succeeds, Apify automatically triggers the next one and passes dynamic run data — such as the output dataset ID — via a payload template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chain 1: Sitemap Generator → Google Indexer
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;sitemap-generator-task&lt;/code&gt; in Apify Console and click the &lt;strong&gt;Integrations&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add integration&lt;/strong&gt; and select &lt;strong&gt;Run another Actor or task&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;google-indexer-task&lt;/code&gt; as the target.&lt;/li&gt;
&lt;li&gt;Set the trigger to &lt;strong&gt;Run succeeded&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set the payload template so the indexer receives the crawler's output dataset:
&lt;/li&gt;
&lt;/ol&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;"datasetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{resource.defaultDatasetId}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"URL_UPDATED"&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;h3&gt;
  
  
  Chain 2: Google Indexer → Notion Uploader
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;google-indexer-task&lt;/code&gt; in Apify Console and click the &lt;strong&gt;Integrations&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add integration&lt;/strong&gt; and select &lt;strong&gt;Run another Actor or task&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;notion-uploader-task&lt;/code&gt; as the target.&lt;/li&gt;
&lt;li&gt;Set the trigger to &lt;strong&gt;Run succeeded&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;datasetId&lt;/code&gt; out of the payload template — the Notion Uploader automatically uses the triggering run's dataset when no explicit ID is provided:
&lt;/li&gt;
&lt;/ol&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;"dedupeMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"upsert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keyProperty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"URL"&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;Now the full chain fires end-to-end on its own. When &lt;code&gt;sitemap-generator-task&lt;/code&gt; completes, Apify triggers &lt;code&gt;google-indexer-task&lt;/code&gt; with the crawler's dataset. When the indexer finishes, Apify triggers &lt;code&gt;notion-uploader-task&lt;/code&gt;, which reads the indexer's output dataset and writes each result row into your Notion database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Scheduling automated pipeline execution
&lt;/h3&gt;

&lt;p&gt;Configure a recurring schedule so the entire chain runs automatically.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Apify Console left sidebar, click &lt;strong&gt;Schedules&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create new schedule&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;sitemap-generator-task&lt;/code&gt; as the target. The full chain — crawl, index, upload to Notion — fires automatically when this task runs.&lt;/li&gt;
&lt;li&gt;Set the frequency using a Cron expression or one of the presets.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For high-frequency publishing (news, job boards)&lt;/strong&gt;: Schedule daily at 2:00 AM UTC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For standard marketing blogs and commercial sites&lt;/strong&gt;: Schedule weekly, every Monday morning.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cron expression example (daily at 2:00 AM UTC):
0 2 * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best practices for automated SEO indexing
&lt;/h2&gt;

&lt;p&gt;To maintain a healthy indexing strategy while avoiding Google API flags, keep these best practices in mind:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Submit only modified or new content
&lt;/h3&gt;

&lt;p&gt;Do not resubmit your entire site inventory every day. The Google Indexing API is intended for pages that have new content (&lt;code&gt;URL_UPDATED&lt;/code&gt;) or pages that have been deleted (&lt;code&gt;URL_DELETED&lt;/code&gt;). Resubmitting unchanged pages wastes daily quotas and can result in rate limiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Match canonical URLs strictly
&lt;/h3&gt;

&lt;p&gt;Google requires strict URL matching. If your canonical structure uses &lt;code&gt;https://example.com/blog/post-name/&lt;/code&gt; with a trailing slash, ensure your crawler filters and API submissions match that format exactly. Submitting &lt;code&gt;http://&lt;/code&gt; or non-trailing-slash variations can trigger redirect loops and indexing failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use Test Mode during initial setup
&lt;/h3&gt;

&lt;p&gt;Before running full-scale API operations, turn on the &lt;strong&gt;Test Mode&lt;/strong&gt; toggle in the &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt; Actor. Test Mode executes the full crawl, verifies JSON authentication keys, and simulates Notion MCP logging without consuming your daily Google API quota.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Monitor quota usage in Google Cloud Console
&lt;/h3&gt;

&lt;p&gt;Standard Google Cloud projects receive an initial quota of 200 Indexing API requests per day. If your site publishes hundreds of new pages daily, submit a quota increase request directly through the &lt;strong&gt;Quotas&lt;/strong&gt; tab in Google Cloud Console.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Building an automated technical SEO pipeline transforms indexing from a passive waiting game into a predictable, programmatic system.&lt;/p&gt;

&lt;p&gt;Combining the cloud crawling capabilities of the &lt;a href="https://apify.com/eunit/sitemap-generator" rel="noopener noreferrer"&gt;Fast Sitemap Generator&lt;/a&gt;, the push notifications of the &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt;, and real-time database logging through &lt;a href="https://apify.com/store/collections/mcp-connectors" rel="noopener noreferrer"&gt;Notion MCP Connectors&lt;/a&gt;, you establish an automated SEO engine that operates reliably in the background.&lt;/p&gt;

&lt;p&gt;Instead of waiting weeks for Google to discover your newest content organically, your site updates are crawled, verified, submitted, and logged in Notion within minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read more
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Explore the &lt;a href="https://apify.com/eunit/sitemap-generator" rel="noopener noreferrer"&gt;Fast Sitemap Generator&lt;/a&gt; on the Apify Store.&lt;/li&gt;
&lt;li&gt;Set up the &lt;a href="https://apify.com/eunit/google-indexing" rel="noopener noreferrer"&gt;Google Indexer &amp;amp; Instant SEO Submitter&lt;/a&gt; Actor with your Google Cloud Service Account.&lt;/li&gt;
&lt;li&gt;Connect your Notion workspace via MCP Connectors to start tracking real-time indexing status today.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seoautomation</category>
      <category>googleindexingapi</category>
      <category>technicalseo</category>
      <category>webcrawling</category>
    </item>
    <item>
      <title>How to Automate Instagram Reels to Notion via MCP</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:14:53 +0000</pubDate>
      <link>https://dev.to/apify/how-to-automate-instagram-reels-to-notion-via-mcp-b55</link>
      <guid>https://dev.to/apify/how-to-automate-instagram-reels-to-notion-via-mcp-b55</guid>
      <description>&lt;p&gt;Managing short-form video content across multiple campaigns often turns into an administrative nightmare. Your marketing team publishes dozens of public Reels, short video posts, and IGTV clips every month. Everything looks great on Instagram, but as soon as someone asks to archive these assets into a centralized brand library or content calendar, the entire operation grinds to a halt.&lt;/p&gt;

&lt;p&gt;Suddenly, you are forced to rely on shady, ad-heavy download websites that fail half the time, paste individual links into manual file converters, or copy caption text into spreadsheets by hand. Even when you do manage to extract a direct video file URL from Instagram, that URL expires within hours due to Instagram's content delivery network (CDN) session restrictions.&lt;/p&gt;

&lt;p&gt;What if you could &lt;a href="https://www.eunit.me/blog/how-to-download-instagram-videos-and-reels-at-scale-with-apify" rel="noopener noreferrer"&gt;automatically extract high-quality Instagram video files&lt;/a&gt;, store them permanently, and stream that structured data straight into Notion in real time?&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt;, the &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; Actor, and Apify &lt;a href="https://mcp.apify.com/?fpr=eunit" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; connectors, you can build a zero-maintenance, automated pipeline. This guide walks you through setting up an automated Instagram-to-Notion video content calendar that automatically syncs dataset outputs upon run completion without requiring manual CSV exports or fragile custom backend glue code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://postimg.cc/9z9rTp65" rel="noopener noreferrer"&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%2F3ctibhb1sweb6m14nu7s.png" alt="How to Automate Instagram Reels to Notion via MCP" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The content archiving nightmare
&lt;/h2&gt;

&lt;p&gt;Picture this scenario. You are a creative organization, and your creative team just finished a quarter-long influencer campaign. Thirty public Instagram Reels and video posts were published. Your client or department head wants every asset logged inside Notion with direct video links, preview thumbnails, shortcodes, captions, and publishing dates for future repurposing.&lt;/p&gt;

&lt;p&gt;The traditional approach to this task usually follows one of two painful paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The manual downloader route:&lt;/strong&gt; A team member spends hours copying Instagram links, pasting them into third-party downloader sites, closing popup ads, downloading &lt;code&gt;.mp4&lt;/code&gt; files locally, uploading those files into Google Drive or Notion, and typing metadata by hand. This process consumes dozens of work hours, produces inconsistent video resolutions, and risks downloading malware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The legacy scraping route:&lt;/strong&gt; A developer sets up a web scraper to pull Instagram data. The scraper runs for 10 minutes, completes its execution, and dumps a static JSON or CSV file to cloud storage. A developer then has to write custom webhooks or cron scripts to parse that output file, format the payload, and send REST API requests to Notion. If any record fails or if the script breaks mid-transit, debugging becomes a headache.
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A["Scraper / Web Downloader"] --&amp;gt; B["CSV / JSON Export"]
    B --&amp;gt; C["Custom Glue Script / Manual Upload"]
    C --&amp;gt; D["Notion Database"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Neither approach is ideal for growing teams or production workflows. Legacy scraping forces you to write custom integration pipelines to move data downstream. Meanwhile, manual downloading fails to scale beyond a handful of links.&lt;/p&gt;

&lt;p&gt;Apify Actor integrations and &lt;a href="https://mcp.apify.com/?fpr=eunit" rel="noopener noreferrer"&gt;Apify MCP&lt;/a&gt; connectors solve this problem seamlessly. Rather than manually writing custom export scripts or handling fragile API glue, you can attach the supported &lt;a href="https://apify.com/filip_cicvarek/notion-uploader?fpr=eunit" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; Actor directly to your extraction workflow inside Apify Console.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A["Instagram Reels"] --&amp;gt; B["Instagram Video and Reel Downloader"]
    B -- On Run Succeeded --&amp;gt; C["Notion Uploader Actor Integration"]
    C -- Apify MCP Connector / API Key --&amp;gt; D["Notion Content Calendar"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Once &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader?fpr=eunit" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; finishes extracting video assets and storing permanent &lt;code&gt;.mp4&lt;/code&gt; file links in Apify Key-Value Store, Apify automatically executes the &lt;strong&gt;Notion Uploader&lt;/strong&gt; integration. &lt;strong&gt;Notion Uploader&lt;/strong&gt; connects securely to your Notion workspace using an Apify MCP connector (configured in Apify Console under &lt;strong&gt;Settings → Integrations → MCP connectors&lt;/strong&gt;) or a Notion API key, mapping fields and creating structured database rows automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline architecture: how MCP connectors change the game
&lt;/h2&gt;

&lt;p&gt;To understand why this architecture is so powerful, let's examine what happens under the hood during an automated run.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    subgraph APIFY["APIFY PLATFORM"]
        A["Instagram /reel/ URLs"] --&amp;gt; B["Dual-Path Extraction Engine"]
        B --&amp;gt; C["Apify Key-Value Store&amp;lt;br/&amp;gt;(Permanent Video URL)"]
        B --&amp;gt; D["Apify Dataset&amp;lt;br/&amp;gt;(Structured Metadata)"]
    end

    D -- On Run Succeeded --&amp;gt; E["Notion Uploader Actor&amp;lt;br/&amp;gt;(filip_cicvarek/notion-uploader)"]
    E -- Apify MCP Connector / API Key --&amp;gt; F["Notion Content Calendar&amp;lt;br/&amp;gt;(Automated Database Rows)"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  1. Dual-path extraction engine
&lt;/h3&gt;

&lt;p&gt;Instagram does not provide direct, permanent video download links through a simple public REST API. Furthermore, different post types behave differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some standard post pages expose media tags within initial static HTML.&lt;/li&gt;
&lt;li&gt;Complex Reel pages render content dynamically via JavaScript, obscuring video source URLs behind client-side network calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; handles this seamlessly using a dual-path architecture. It first attempts a lightweight static HTTP request to extract the media link instantly. If the target page requires dynamic execution, the Actor automatically falls back to a browser session to capture media delivery requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Solving CDN URL expiration via Key-Value Store
&lt;/h3&gt;

&lt;p&gt;Direct video URLs returned straight from Instagram's servers (&lt;code&gt;*.cdninstagram.com&lt;/code&gt; or &lt;code&gt;*.fbcdn.net&lt;/code&gt;) are session-bound and expire after a short period. If you paste a raw CDN video link into Notion, the video will stop playing within 24 to 48 hours.&lt;/p&gt;

&lt;p&gt;To solve this, &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; includes a &lt;code&gt;storeVideoFiles&lt;/code&gt; configuration option in Apify Console. When set to &lt;code&gt;true&lt;/code&gt;, the Actor streams the raw &lt;code&gt;.mp4&lt;/code&gt; file directly into Apify Key-Value Store during the run. This generates a permanent, publicly accessible URL (&lt;code&gt;videoPublicUrl&lt;/code&gt;) that Notion can embed reliably forever.&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%2Ftkdqxri0e2gz5lsg4i1z.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%2Ftkdqxri0e2gz5lsg4i1z.png" alt="Instagram Video and Reel Downloader Input on Apify Console" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Automated dataset push via Notion Uploader and MCP connectors
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; is an open standard that allows tools and AI systems to exchange context and perform actions securely across application boundaries. In the Apify ecosystem, MCP connectors allow Actors like &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; to communicate directly with external services - such as Notion, HubSpot, Google Sheets, or Slack.&lt;/p&gt;

&lt;p&gt;By configuring an &lt;strong&gt;Actor Integration&lt;/strong&gt; on &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; set to trigger on &lt;code&gt;Run succeeded&lt;/code&gt;, Apify automatically hands off the generated dataset to &lt;strong&gt;Notion Uploader&lt;/strong&gt;. Notion Uploader reads the structured items and uses the configured Notion MCP connector (or Notion API key) to map and upsert rows directly into your Notion database.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Zero-trust token security and governance
&lt;/h3&gt;

&lt;p&gt;When connecting third-party services to web automations, storing API tokens in raw scripts or public Actor inputs poses severe security risks. Apify handles MCP authentication via OAuth 2.0 proxies managed centrally inside Apify Console under &lt;strong&gt;Settings → Integrations → MCP connectors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your Notion authorization token is never exposed in plain text, never saved in Actor dataset output, and never shared with the underlying script environment. The Apify platform handles identity verification and permission scope tokens securely behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting up the Notion database
&lt;/h2&gt;

&lt;p&gt;Before configuring the Actor, you need a designated Notion database structured to store video metadata and file references.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your Notion workspace and create a new database page (select &lt;strong&gt;Full page database&lt;/strong&gt; or &lt;strong&gt;Inline database&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Name your database &lt;code&gt;Instagram Content Calendar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure the database properties to match the schema exported by &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property Name&lt;/th&gt;
&lt;th&gt;Notion Property Type&lt;/th&gt;
&lt;th&gt;Purpose / Payload Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Title&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;Stores post caption snippet or title (&lt;code&gt;title&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instagram Link&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;Direct link to original Instagram post (&lt;code&gt;canonicalUrl&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct Video Link&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;Permanent Apify storage link (&lt;code&gt;videoPublicUrl&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Thumbnail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Files &amp;amp; media (or URL)&lt;/td&gt;
&lt;td&gt;Preview thumbnail image (&lt;code&gt;thumbnailUrl&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shortcode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Instagram unique ID (&lt;code&gt;shortcode&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Media Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Post type (&lt;code&gt;reel&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, or &lt;code&gt;tv&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fetch Date&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Timestamp of processing (&lt;code&gt;fetchedAt&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Status / Select&lt;/td&gt;
&lt;td&gt;Processing result (&lt;code&gt;ready&lt;/code&gt; or &lt;code&gt;error&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;erDiagram
    INSTAGRAM_CONTENT_CALENDAR {
        string Title "Summer Campaign Teaser"
        string Instagram_Link "instagram.com/reel/C123abc/"
        string Direct_Video_Link "api.apify.com/.../video-c123.mp4"
        string Media_Type "reel"
        string Status "ready"
    }&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Ensure your Notion database view is set up to display URL and media properties clearly so your creative team can preview videos directly within the calendar workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configuring Instagram Video and Reel Downloader
&lt;/h2&gt;

&lt;p&gt;With your Notion database ready, navigate to &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; on Apify Store.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Try for free&lt;/strong&gt; or open the Actor inside Apify Console. Select the &lt;strong&gt;Input&lt;/strong&gt; tab to configure your run.&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%2Fy28uyvs58qmcofmtgmru.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%2Fy28uyvs58qmcofmtgmru.png" alt="Instagram Video Downloader Actor" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Input JSON configuration
&lt;/h3&gt;

&lt;p&gt;To ensure video URLs remain active inside Notion indefinitely, you must set &lt;code&gt;storeVideoFiles&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. This instructs the Actor to download the binary media file into the Apify Key-Value Store and produce the &lt;code&gt;videoPublicUrl&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Here is an ideal production configuration snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/C8XyZ123abc/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/p/C9AbC456def/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/C-Ghi789jkl/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storeVideoFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storeThumbnailFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includePageHtml"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filenamePrefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notion-archive-2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxConcurrency"&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;"maxRequestRetries"&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;"requestTimeoutSecs"&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;"proxyConfiguration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"useApifyProxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key configuration settings explained
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;startUrls&lt;/code&gt;&lt;/strong&gt;: An array of target public Instagram URLs. Supports &lt;code&gt;/reel/&lt;/code&gt;, &lt;code&gt;/p/&lt;/code&gt;, and &lt;code&gt;/tv/&lt;/code&gt; paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;storeVideoFiles&lt;/code&gt;&lt;/strong&gt;: Set to &lt;code&gt;true&lt;/code&gt;. This generates a permanent &lt;code&gt;videoPublicUrl&lt;/code&gt; host link on &lt;code&gt;api.apify.com&lt;/code&gt;, avoiding expired CDN link errors inside Notion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;storeThumbnailFiles&lt;/code&gt;&lt;/strong&gt;: Set to &lt;code&gt;true&lt;/code&gt; if you want stored key-value records for thumbnail images as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;filenamePrefix&lt;/code&gt;&lt;/strong&gt;: A custom string prepended to all stored files inside Apify storage (e.g., &lt;code&gt;notion-archive-2026&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;maxConcurrency&lt;/code&gt;&lt;/strong&gt;: Set to &lt;code&gt;2&lt;/code&gt; or &lt;code&gt;3&lt;/code&gt;. Instagram actively monitors aggressive parallel scraping. Keeping concurrency low ensures reliable extraction without triggering rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;proxyConfiguration&lt;/code&gt;&lt;/strong&gt;: Enabling Apify Proxy distributes requests across residential or datacenter IP pools, preventing unexpected block screens.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Wiring the Notion Uploader integration in Apify Console
&lt;/h2&gt;

&lt;p&gt;Once your input JSON is defined for &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt;, you attach the &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; Actor integration directly to the run configuration inside Apify Console.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Creating your Notion MCP connector (or API key)
&lt;/h3&gt;

&lt;p&gt;Before attaching the integration, establish your connection to Notion inside Apify Console:&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%2Fnn8my7i1i43ruc2znp0p.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%2Fnn8my7i1i43ruc2znp0p.png" alt="Notion MCP Connector" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Apify Console, open &lt;strong&gt;Settings → API &amp;amp; Integrations → MCP connectors&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add connector&lt;/strong&gt; and select &lt;strong&gt;Notion&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Complete the one-click OAuth authorization window to grant Apify access to your Notion workspace and database.&lt;/li&gt;
&lt;li&gt;Apify Console generates a secure connector ID (e.g. &lt;code&gt;mcp-connector-...&lt;/code&gt;) that allows Actors to interact with Notion with your permissions, without ever exposing raw private tokens.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;(Alternative: You can also use a classic internal integration secret starting with &lt;code&gt;ntn_&lt;/code&gt; created at &lt;a href="https://www.notion.so/profile/integrations" rel="noopener noreferrer"&gt;notion.so/profile/integrations&lt;/a&gt;. Be sure to invite your integration to the Notion database via *&lt;/em&gt;••• → Connections*&lt;em&gt;).&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A["Apify Console Settings"] --&amp;gt; B["MCP Connectors"]
    B --&amp;gt; C["Add Notion Connector (OAuth)"]
    C --&amp;gt; D["Secure Connector Session ID"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  2. Adding the Notion Uploader integration to your Actor
&lt;/h3&gt;

&lt;p&gt;With your Notion MCP connector ready, wire the integration to &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt;:&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%2Fh1en78eephqqy4ci6l1j.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%2Fh1en78eephqqy4ci6l1j.png" alt="Instagram Video Downloader + Notion Uploader" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; in Apify Console.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Integrations&lt;/strong&gt; tab located in the navigation panel below the Actor title.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add integration&lt;/strong&gt;. Under &lt;strong&gt;Suggested for this Actor&lt;/strong&gt;, click &lt;strong&gt;Notion Uploader&lt;/strong&gt; (&lt;code&gt;filip_cicvarek/notion-uploader&lt;/code&gt;). &lt;em&gt;(Note: Apify Console also lists third-party integration routes like Make and Zapier under "Integrate with third-party tools", but Notion Uploader provides the direct, native Apify Actor integration path)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Start when&lt;/strong&gt; to &lt;strong&gt;Run succeeded&lt;/strong&gt;. This ensures Notion Uploader fires automatically the moment your Instagram extraction run completes cleanly.&lt;/li&gt;
&lt;/ol&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%2F7ym66zaa9rrtiaitplrq.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%2F7ym66zaa9rrtiaitplrq.png" alt="Instagram Video Downloader + Notion Uploader" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Mapping fields and configuring Notion Uploader input
&lt;/h3&gt;

&lt;p&gt;Under the &lt;strong&gt;Actor input&lt;/strong&gt; section for the Notion Uploader integration, configure the target database and field mapping:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Notion connector&lt;/strong&gt;: Select your authorized Notion MCP connector from the dropdown list (or paste your &lt;code&gt;notionApiKey&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion database&lt;/strong&gt;: Paste your Notion database URL or ID (&lt;code&gt;notionDatabaseId&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit field mapping (&lt;code&gt;dataMapping&lt;/code&gt;)&lt;/strong&gt;: Map the dataset output fields from Instagram Video and Reel Downloader to your Notion database properties:
&lt;/li&gt;
&lt;/ol&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;"Title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Instagram Link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"canonicalUrl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Direct Video Link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"videoPublicUrl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Thumbnail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thumbnailUrl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Shortcode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shortcode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Media Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Fetch Date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fetchedAt"&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate handling (rerun safety)&lt;/strong&gt;: Set &lt;strong&gt;Dedupe mode&lt;/strong&gt; (&lt;code&gt;dedupeMode&lt;/code&gt;) to &lt;code&gt;upsert&lt;/code&gt; and set &lt;strong&gt;Key property&lt;/strong&gt; (&lt;code&gt;keyProperty&lt;/code&gt;) to &lt;code&gt;Instagram Link&lt;/code&gt; (or &lt;code&gt;Shortcode&lt;/code&gt;). This guarantees that re-running the Actor with previously processed URLs will update existing database rows instead of creating duplicate entries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click &lt;strong&gt;Save integration&lt;/strong&gt;. The integration is now active and bound to your Actor task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcw3ftbv193j5mqedcmt1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcw3ftbv193j5mqedcmt1.gif" alt="Instagram Video and Reel Downloader gif" width="560" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Executing the run and verifying the content calendar
&lt;/h2&gt;

&lt;p&gt;With the input parameters set and the Notion Uploader integration saved, click the &lt;strong&gt;Start&lt;/strong&gt; button in the top-right corner of Apify Console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watching automated workflow execution
&lt;/h3&gt;

&lt;p&gt;As &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; runs, you can monitor the progress through the &lt;strong&gt;Log&lt;/strong&gt; tab:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Actor extracts public Instagram video posts, Reels, and IGTV links specified in &lt;code&gt;startUrls&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For each URL, it resolves media endpoints and streams binary &lt;code&gt;.mp4&lt;/code&gt; files into Apify Key-Value Store, generating permanent &lt;code&gt;videoPublicUrl&lt;/code&gt; host links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration handoff&lt;/strong&gt;: When the extraction run finishes successfully (&lt;code&gt;Run succeeded&lt;/code&gt;), Apify immediately launches &lt;strong&gt;Notion Uploader&lt;/strong&gt; (&lt;code&gt;filip_cicvarek/notion-uploader&lt;/code&gt;), passing the run's default dataset ID automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion Database update&lt;/strong&gt;: Notion Uploader uses your Notion MCP connector to read the dataset items, validate properties against your database schema, apply &lt;code&gt;upsert&lt;/code&gt; deduplication, and populate your &lt;code&gt;Instagram Content Calendar&lt;/code&gt; in Notion.
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant Downloader as Instagram Video Downloader
    participant KVS as Key-Value Store
    participant Integration as Notion Uploader Integration
    participant Notion as Notion Database

    Note over Downloader: Downloader starts execution
    Downloader-&amp;gt;&amp;gt;KVS: Extracts Reel &amp;amp; uploads .mp4 file
    Note over Downloader: Run completes (Status: SUCCESS)
    Downloader-&amp;gt;&amp;gt;Integration: Triggers Notion Uploader automatically
    Integration-&amp;gt;&amp;gt;Notion: Reads dataset &amp;amp; upserts database rows
    Note over Notion: Content calendar fully updated!&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If an invalid or private URL is encountered, the extraction Actor records a structured dataset item containing an &lt;code&gt;errorCode&lt;/code&gt; (such as &lt;code&gt;NO_VIDEO_URL&lt;/code&gt;) and &lt;code&gt;errorMessage&lt;/code&gt;. Notion Uploader writes an &lt;code&gt;error&lt;/code&gt; status row into Notion, maintaining a complete audit log without breaking the pipeline execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Extending the workflow with API triggers and scheduling
&lt;/h2&gt;

&lt;p&gt;Building a one-off run inside Apify Console is great for testing, but real content operations require automated execution. You can automate this pipeline using code triggers or scheduled jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Programmatic execution via JavaScript / TypeScript
&lt;/h3&gt;

&lt;p&gt;You can trigger the integrated pipeline programmatically using the official &lt;code&gt;apify-client&lt;/code&gt; SDK in Node.js or TypeScript by invoking your saved task (which includes the Notion Uploader integration):&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// Initialize the client with your Apify API token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_APIFY_API_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Define input parameters with video storage enabled&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;startUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.instagram.com/reel/C8XyZ123abc/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.instagram.com/reel/C9AbC456def/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;storeVideoFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;storeThumbnailFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;filenamePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekly-content-sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxConcurrency&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="na"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;useApifyProxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runInstagramSync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Starting Instagram to Notion sync workflow...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Call the saved task (or Actor). Configured integrations run automatically on completion.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SAVED_TASK_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Run completed! View dataset at: https://console.apify.com/storage/datasets/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notion Uploader integration triggered automatically.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;runInstagramSync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Programmatic execution via Python
&lt;/h3&gt;

&lt;p&gt;If your infrastructure runs on Python, use &lt;code&gt;apify-client&lt;/code&gt; to trigger the sync:&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="c1"&gt;# Initialize client
&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_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Prepare input dictionary
&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;startUrls&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&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;https://www.instagram.com/reel/C8XyZ123abc/&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;url&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;https://www.instagram.com/reel/C9AbC456def/&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;storeVideoFiles&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;storeThumbnailFiles&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;filenamePrefix&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;python-content-sync&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;maxConcurrency&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;proxyConfiguration&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;useApifyProxy&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="c1"&gt;# Run the saved task with Notion integration attached
&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;Launching Instagram Video Downloader with Notion Uploader Integration...&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;task&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_SAVED_TASK_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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_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="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;Workflow complete. Dataset ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated set-and-forget scheduling
&lt;/h3&gt;

&lt;p&gt;To eliminate manual execution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save your input configuration and Notion Uploader integration inside Apify Console as a &lt;strong&gt;Saved Task&lt;/strong&gt; (e.g., &lt;code&gt;Instagram-to-Notion-Sync-Task&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Schedules&lt;/strong&gt; tab in Apify Console and click &lt;strong&gt;Create schedule&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Link the schedule to your saved Actor task.&lt;/li&gt;
&lt;li&gt;Set a cron cadence, such as every Friday at 5:00 PM (&lt;code&gt;0 17 * * 5&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every week, Apify will automatically run &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt;, extract new video links, upload permanent &lt;code&gt;.mp4&lt;/code&gt; binaries to Key-Value Store, and trigger &lt;strong&gt;Notion Uploader&lt;/strong&gt; to push the assets into your Notion database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Relying on ad-ridden video download websites, manual file management, and fragile spreadsheet uploads is no longer necessary for modern media operations.&lt;/p&gt;

&lt;p&gt;By combining the extraction capabilities of &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; with the &lt;a href="https://apify.com/filip_cicvarek/notion-uploader" rel="noopener noreferrer"&gt;Notion Uploader&lt;/a&gt; Actor and Apify MCP connectors, you can build a seamless content archiving pipeline. Permanent video hosting via Apify Key-Value Store eliminates broken CDN links, while automated Actor integrations push structured data straight into Notion without requiring custom backend glue code.&lt;/p&gt;

&lt;p&gt;Ready to automate your social media asset pipeline?&lt;/p&gt;

&lt;p&gt;Try &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader on Apify Store&lt;/a&gt; today, connect your Notion workspace via Notion Uploader, and build your automated content calendar in minutes.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>apify</category>
      <category>webscraping</category>
      <category>instagramreels</category>
    </item>
    <item>
      <title>Headless browsers vs. SERP APIs: Why your Playwright scraper fails in production</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:14:32 +0000</pubDate>
      <link>https://dev.to/eunit/headless-browsers-vs-serp-apis-why-your-playwright-scraper-fails-in-production-4cgo</link>
      <guid>https://dev.to/eunit/headless-browsers-vs-serp-apis-why-your-playwright-scraper-fails-in-production-4cgo</guid>
      <description>&lt;p&gt;Every developer who builds web scrapers goes through the same initial rite of passage. You need search engine data for an SEO tracking tool, an AI retrieval pipeline, or a market research project. Your first instinct is to reach for a browser automation tool like &lt;a href="https://www.eunit.me/blog/playwright-vs-webdriverio-uncovering-the-differences" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, Puppeteer, or Selenium.&lt;/p&gt;

&lt;p&gt;You write a quick script, launch a &lt;a href="https://www.browserless.io/blog/headless-chrome" rel="noopener noreferrer"&gt;headless Chromium instance&lt;/a&gt;, navigate to Google, inspect the Document Object Model (DOM), and extract text from &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; tags and organic result containers.&lt;/p&gt;

&lt;p&gt;On your local development machine, it feels like magic. The script runs cleanly for your first five test queries.&lt;/p&gt;

&lt;p&gt;Then, you deploy your script to a production server on AWS or DigitalOcean, and everything breaks down. Within minutes, your scraper encounters roadblock CAPTCHAs, gets flagged by automated anti-bot systems, or silently returns empty arrays because the search engine rolled out an unannounced layout tweak.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Execution Path&lt;/th&gt;
&lt;th&gt;Production Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Localhost (Prototyping)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Playwright ==&amp;gt; Residential Home IP ==&amp;gt; Google&lt;/td&gt;
&lt;td&gt;🟢 &lt;strong&gt;200 OK&lt;/strong&gt; (5 results parsed successfully)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Server (DIY)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Playwright ==&amp;gt; Datacenter Cloud IP ==&amp;gt; Google&lt;/td&gt;
&lt;td&gt;🔴 &lt;strong&gt;429 Blocked / CAPTCHA Wall&lt;/strong&gt; (Scraper fails)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Managed API (SearchApi)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;App Worker ==&amp;gt; HTTP GET ==&amp;gt; SearchApi Infrastructure&lt;/td&gt;
&lt;td&gt;⚡ &lt;strong&gt;200 OK&lt;/strong&gt; (Instant structured JSON payload)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While headless browsers are exceptional tools for end-to-end web testing and scraping low-security websites, using them to scrape &lt;a href="https://www.coursera.org/articles/what-is-serp" rel="noopener noreferrer"&gt;Search Engine Results Pages (SERPs)&lt;/a&gt; is an expensive engineering trap.&lt;/p&gt;

&lt;p&gt;In this guide, we break down the hidden operational costs of DIY browser automation, compare side-by-side Python implementations, and explore why switching to a dedicated SERP API like &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt; saves your engineering team time, infrastructure budget, and maintenance headaches.&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%2Frsdggff2zc51dajtq2uo.jpeg" 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%2Frsdggff2zc51dajtq2uo.jpeg" alt="Headless browsers vs. SERP APIs: Why your Playwright scraper will fail in production" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost of doing it yourself
&lt;/h2&gt;

&lt;p&gt;Developers naturally enjoy building systems from the ground up. However, building a custom search engine scraper means entering an ongoing cat-and-mouse game against some of the most &lt;a href="https://www.broworks.net/blog/googles-anti-scraping-crackdown-shakes-seo-tools-worldwide" rel="noopener noreferrer"&gt;sophisticated anti-bot security&lt;/a&gt; teams in the world.&lt;/p&gt;

&lt;p&gt;When you scale a headless browser scraper beyond a handful of casual requests, you immediately run into four severe architectural bottlenecks:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    Root["DIY Browser Scraping Bottlenecks"] --&amp;gt; B1["1. Proxy Infrastructure Tax&amp;lt;br/&amp;gt;High residential bandwidth costs"]
    Root --&amp;gt; B2["2. Fragile Selector Maintenance&amp;lt;br/&amp;gt;Random CSS class changes break code"]
    Root --&amp;gt; B3["3. Server Resource Drain&amp;lt;br/&amp;gt;150MB+ RAM per tab, CPU spikes"]
    Root --&amp;gt; B4["4. Anti-Bot Fingerprinting&amp;lt;br/&amp;gt;TLS, Canvas, WebGL, JA3 detection"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  1. The proxy infrastructure tax
&lt;/h3&gt;

&lt;p&gt;Search engines can easily identify and block automated requests originating from data center IP ranges (such as AWS, Google Cloud Platform, or DigitalOcean). If you send five consecutive queries from an AWS EC2 instance without a proxy, your IP address is flagged almost immediately.&lt;/p&gt;

&lt;p&gt;To bypass this restriction, you must purchase and configure a residential proxy network. Residential proxies route your requests through real residential consumer connections, making them appear legitimate. However, managing proxies introduces heavy technical complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bandwidth costs&lt;/strong&gt;: Residential proxy providers charge based on data transfer, usually between $5 and $15 per gigabyte. When a headless browser loads a search results page, it downloads not just the text, but also JavaScript bundles, stylesheets, tracking pixels, and images. Loading these unnecessary assets inflates your proxy bandwidth bill significantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pool rotation and stickiness&lt;/strong&gt;: You must write custom pooling logic to rotate IP addresses across queries while maintaining sticky sessions when handling multi-page pagination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway latency&lt;/strong&gt;: Routing traffic through multiple proxy hops adds anywhere from 1 to 4 seconds of network latency to every single request.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Fragile HTML selector maintenance
&lt;/h3&gt;

&lt;p&gt;Search engines do not use semantic, human-readable HTML markup. Instead of providing clear class names like &lt;code&gt;.search-result-item&lt;/code&gt; or &lt;code&gt;.result-title&lt;/code&gt;, they rely on minified, randomized, or hashed CSS selectors that change frequently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- What you hope to see in the DOM --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"search-result"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Web Scraping Guide&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"snippet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Learn how to extract data...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- What Google actually renders --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MjjYud"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"g Ww4FFb vt6AZc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"kvH3df"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"VwiC3b yD755b xDu2fd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Learn how to extract data...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your code relies on fragile selectors like &lt;code&gt;div.g&lt;/code&gt; or &lt;code&gt;.VwiC3b&lt;/code&gt;, an unannounced A/B test or markup refresh by the search engine will cause your parser to fail silently. You will receive empty datasets, triggering alerts and requiring developers to drop their current sprint tasks to inspect DOM trees and update CSS queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Massive server resource drain
&lt;/h3&gt;

&lt;p&gt;Headless browsers are full browser execution environments. Running an instance of headless Chromium or Firefox forces your host server to parse HTML, evaluate complex JavaScript scripts, construct DOM trees, and calculate CSS layout rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory consumption&lt;/strong&gt;: A single headless browser tab typically consumes between 150MB and 300MB of RAM. If your application needs to handle 30 concurrent search queries, your server needs at least 8GB to 16GB of dedicated RAM just to keep the browser processes alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU spikes&lt;/strong&gt;: Initializing browser contexts and rendering client-side JavaScript creates sharp CPU spikes, often requiring expensive multi-core cloud compute instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process zombie leaks&lt;/strong&gt;: Long-running browser processes frequently experience memory leaks. Without aggressive lifecycle management and process termination routines, orphaned Chromium processes will gradually consume all available server memory and crash your host container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Advanced anti-bot fingerprinting and CAPTCHAs
&lt;/h3&gt;

&lt;p&gt;Modern search engines employ sophisticated behavioral and cryptographic bot detection techniques. They do not just check your User-Agent header; they inspect deep browser characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TLS and JA3/JA4 fingerprinting&lt;/strong&gt;: The way your networking stack negotiates SSL/TLS handshakes reveals whether your request comes from a genuine desktop browser or an automated runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigator and JavaScript runtime properties&lt;/strong&gt;: Automated environments often leak telltale attributes such as &lt;code&gt;navigator.webdriver = true&lt;/code&gt;, missing system plugin arrays, or default WebGL vendor strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas and audio fingerprinting&lt;/strong&gt;: Scripts render hidden shapes or audio signals to generate a unique hardware signature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral heuristics&lt;/strong&gt;: Mouse movement velocity, keystroke timings, and scroll dynamics are analyzed in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even when using stealth plugins like &lt;code&gt;puppeteer-extra-plugin-stealth&lt;/code&gt; or custom Playwright evasion flags, these countermeasures are brittle. Anti-bot vendors continuously update their detection models, leaving your custom scraper vulnerable to sudden blocks and CAPTCHA walls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The abstracted alternative: Dedicated SERP APIs
&lt;/h2&gt;

&lt;p&gt;A dedicated Search Engine Results Page (SERP) API transforms search engine data collection into a fully managed infrastructure service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fevhjogolxocysfqkhzlz.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fmedia%2Fiij6ovinfhm.png" alt="SearchApi.io logo" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of launching browsers, rotating proxies, handling CAPTCHAs, and maintaining brittle DOM selectors, you offload the entire operational pipeline to a specialized provider like &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    App["Your Application"] --&amp;gt;|1. Simple HTTP GET| API["SearchApi.io Engine"]

    subgraph ManagedInfra["Managed Infrastructure Layer"]
        API --&amp;gt; Proxies["Residential &amp;amp; Mobile Proxies"]
        API --&amp;gt; Browsers["Stealth Browser Fleet"]
        API --&amp;gt; Parsers["Auto-Updating DOM Parsers"]
    end

    ManagedInfra --&amp;gt;|2. Automated Request| Google["Search Engines (Google, Bing)"]
    Google --&amp;gt;|3. Live SERP Response| ManagedInfra
    API --&amp;gt;|4. Clean Structured JSON| App&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt; handles every layer of the extraction process behind a clean REST Application Programming Interface (API):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent proxy rotation&lt;/strong&gt;: Automatically routes queries through optimized residential and mobile IP pools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fingerprint emulation&lt;/strong&gt;: Emulates authentic browser signatures at both the network (TLS/HTTP2) and JavaScript runtime levels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated parsing&lt;/strong&gt;: Upstream parsers continuously monitor search engine layout updates. When Google changes a CSS class, &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt; updates its internal parsers immediately, ensuring your application always receives structured, consistent JSON without code modifications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight delivery&lt;/strong&gt;: Your server sends a lightweight HTTP request and receives clean, parsed JSON in milliseconds, using minimal CPU and memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Side-by-side implementation: Playwright vs. SearchApi
&lt;/h2&gt;

&lt;p&gt;To see the difference in code simplicity, maintainability, and resource footprint, let us compare two complete Python implementations designed to accomplish the exact same task: searching Google for &lt;code&gt;"Top web scraping frameworks in 2026"&lt;/code&gt; and extracting the top organic results (position, title, URL, and snippet text).&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a custom Playwright scraper in Python
&lt;/h3&gt;

&lt;p&gt;The following script represents the DIY approach. It launches an automated Chromium browser, configures viewport parameters, attempts to bypass basic bot flags, waits for network idle states, and parses HTML using CSS selectors.&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
DIY Google Search Scraper using Playwright
Requires: pip install playwright
Run: playwright install chromium
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.async_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;async_playwright&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scrape_google_diy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&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="s"&gt;Launching headless Chromium for query: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="si"&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;search_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;async_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Launch Chromium with anti-detection flags
&lt;/span&gt;        &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;args&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;--disable-blink-features=AutomationControlled&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;--no-sandbox&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;--disable-setuid-sandbox&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;--disable-infobars&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;--window-size=1920,1080&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="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Configure browser context with realistic desktop parameters
&lt;/span&gt;        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;user_agent&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;Mozilla/5.0 (Windows NT 10.0; Win64; x64) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AppleWebKit/537.36 (KHTML, like Gecko) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chrome/124.0.0.0 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;viewport&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;width&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en-US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timezone_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;America/New_York&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Format Google search URL
&lt;/span&gt;        &lt;span class="n"&gt;encoded_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search_query&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="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;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://www.google.com/search?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;hl=en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Navigate and wait for DOM network idle state
&lt;/span&gt;            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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;wait_until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domcontentloaded&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;30000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Define fragile CSS selectors for organic search results
&lt;/span&gt;            &lt;span class="n"&gt;organic_container_selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;div.g, div.MjjYud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;organic_container_selector&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;7000&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Query all potential organic result cards
&lt;/span&gt;            &lt;span class="n"&gt;containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;organic_container_selector&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Extract title from h3 tag
&lt;/span&gt;                &lt;span class="n"&gt;title_elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c1"&gt;# Extract link anchor tag
&lt;/span&gt;                &lt;span class="n"&gt;link_elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c1"&gt;# Extract snippet text using known class heuristics
&lt;/span&gt;                &lt;span class="n"&gt;snippet_elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;div.VwiC3b, div[style*=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-webkit-line-clamp&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;title_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inner_text&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;title_elem&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;link_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;href&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;link_elem&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;snippet_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inner_text&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;snippet_elem&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Filter out nested navigational links and empty blocks
&lt;/span&gt;                &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;
                &lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;search_results&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="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;snippet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="p"&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;position&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;search_results&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;5&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;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&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="s"&gt;Scraping failed due to selector timeout or block: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&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="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;search_results&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top web scraping frameworks in 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;scrape_google_diy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Retrieved &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; results via Playwright:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&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="s"&gt;[&lt;/span&gt;&lt;span class="si"&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;position&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;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;title&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    Link: &lt;/span&gt;&lt;span class="si"&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;link&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The streamlined SearchApi approach
&lt;/h3&gt;

&lt;p&gt;Now, let us examine the equivalent implementation using &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt;. Notice how all browser lifecycle management, selector queries, and network evasions disappear.&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Production-ready Google Search Extraction using SearchApi
Requires: pip install requests python-dotenv
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&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;scrape_google_with_searchapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;United States&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;desktop&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&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="s"&gt;Requesting structured payload for: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="si"&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;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEARCHAPI_API_KEY&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;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing SEARCHAPI_API_KEY environment variable.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# SearchApi Google Search endpoint
&lt;/span&gt;    &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.searchapi.io/api/v1/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&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;engine&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;google&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;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hl&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;en&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;gl&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&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;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;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&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="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;payload&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;organic_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&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;organic_results&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;extracted_data&lt;/span&gt; &lt;span class="o"&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;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;organic_results&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="n"&gt;extracted_data&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="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;link&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;displayed_link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;displayed_link&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;snippet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;snippet&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="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;extracted_data&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http_err&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="s"&gt;SearchApi HTTP error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;http_err&lt;/span&gt;&lt;span class="p"&gt;.&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;status_code&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;http_err&lt;/span&gt;&lt;span class="p"&gt;.&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;text&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&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="s"&gt;Network request error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&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="k"&gt;return&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top web scraping frameworks in 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scrape_google_with_searchapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;New York, New York, United States&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Retrieved &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; results via SearchApi:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&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="s"&gt;[&lt;/span&gt;&lt;span class="si"&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;position&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;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;title&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    Link: &lt;/span&gt;&lt;span class="si"&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;link&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    Display: &lt;/span&gt;&lt;span class="si"&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;displayed_link&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code walkthrough and key differences
&lt;/h3&gt;

&lt;p&gt;Let us break down what happens behind the scenes in both codebases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;DIY Playwright Scraper&lt;/th&gt;
&lt;th&gt;Managed SearchApi Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~100 lines of boilerplate&lt;/td&gt;
&lt;td&gt;~35 lines of standard Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Playwright + Chromium binary (~300MB)&lt;/td&gt;
&lt;td&gt;Standard HTTP client (&lt;code&gt;requests&lt;/code&gt; &amp;lt; 5MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Footprint (RAM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;150MB - 300MB per active tab&lt;/td&gt;
&lt;td&gt;&amp;lt; 10MB per HTTP worker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance &amp;amp; Recovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual DOM re-inspection on layout updates&lt;/td&gt;
&lt;td&gt;Automatic upstream parser updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lines of code and complexity&lt;/strong&gt;: The Playwright script requires over 90 lines of boilerplate, including browser process configuration, context options, selector query strings, element null-checks, and error catching. The SearchApi implementation requires under 35 lines of standard, clean Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary footprint&lt;/strong&gt;: Running Playwright in a Docker container or serverless function requires downloading and bundling a full Chromium binary (~300MB). SearchApi runs with a lightweight HTTP client like &lt;code&gt;requests&lt;/code&gt; or &lt;code&gt;httpx&lt;/code&gt; (&amp;lt; 5MB total dependency size).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data format integrity&lt;/strong&gt;: In the Playwright snippet, if Google alters &lt;code&gt;.VwiC3b&lt;/code&gt; to a new CSS class name, the script returns &lt;code&gt;N/A&lt;/code&gt; for all snippets without throwing a visible network error. In contrast, SearchApi returns a normalized JSON object where fields like &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;link&lt;/code&gt;, &lt;code&gt;snippet&lt;/code&gt;, &lt;code&gt;displayed_link&lt;/code&gt;, and &lt;code&gt;sitelinks&lt;/code&gt; remain consistent regardless of Google's internal layout tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Technical performance and cost showdown
&lt;/h2&gt;

&lt;p&gt;When deciding between building an internal headless browser cluster and integrating an API, you must evaluate total operational overhead across compute, proxies, reliability, and engineering salaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detailed architectural comparison
&lt;/h3&gt;

&lt;p&gt;The table below highlights the technical trade-offs between both approaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Metric&lt;/th&gt;
&lt;th&gt;DIY Headless Browsers (Playwright / Puppeteer)&lt;/th&gt;
&lt;th&gt;Dedicated SERP API (SearchApi)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Initial Setup Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Days to weeks (proxy setup, evasion scripting)&lt;/td&gt;
&lt;td&gt;5 minutes (API key integration)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance Overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (ongoing selector patches and bot updates)&lt;/td&gt;
&lt;td&gt;Zero (handled upstream by provider)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Usage (RAM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;150MB - 300MB per concurrent tab&lt;/td&gt;
&lt;td&gt;&amp;lt; 10MB per HTTP worker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Utilization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (JavaScript parsing and layout rendering)&lt;/td&gt;
&lt;td&gt;Minimal (JSON deserialization)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bandwidth per Query&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.5MB - 3.5MB (full HTML, CSS, JS assets)&lt;/td&gt;
&lt;td&gt;15KB - 40KB (compressed JSON response)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proxy Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual (residential pools, rotation, sticky IPs)&lt;/td&gt;
&lt;td&gt;Fully managed and automated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CAPTCHA Resolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires third-party solvers or browser hooks&lt;/td&gt;
&lt;td&gt;Handled automatically with 99.9% success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raw HTML / Unstructured DOM elements&lt;/td&gt;
&lt;td&gt;Pre-parsed, structured JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex (orchestrating browser clusters on Kubernetes)&lt;/td&gt;
&lt;td&gt;Seamless (call API endpoints with high concurrency)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Availability / SLA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unpredictable (prone to sudden bans)&lt;/td&gt;
&lt;td&gt;Guaranteed 99.9% uptime SLA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Real-world cost breakdown at scale
&lt;/h3&gt;

&lt;p&gt;Many engineering teams assume that DIY scraping is "free" because open-source tools like Playwright cost nothing to download. However, hosting and maintaining a high-volume scraper reveals significant hidden costs.&lt;/p&gt;

&lt;p&gt;Let us model the estimated monthly expense of running &lt;strong&gt;100,000 search queries per month&lt;/strong&gt; across both architectures:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    subgraph DIY["DIY Headless Browsers: ~$2,296 / month"]
        D_Proxies["Residential Proxies (200GB @ $8/GB)&amp;lt;br/&amp;gt;$1,600"]
        D_Eng["Engineering Maintenance (6 hrs @ $90/hr)&amp;lt;br/&amp;gt;$540"]
        D_Compute["Cloud Compute (2x 8GB Instances)&amp;lt;br/&amp;gt;$96"]
        D_Captcha["CAPTCHA Solver Service&amp;lt;br/&amp;gt;$60"]
    end

    subgraph API["Managed SERP API (SearchApi): $250 / month"]
        A_Tier["100k Searches (BigData Plan)&amp;lt;br/&amp;gt;$250"]
        A_Maint["Zero Infrastructure &amp;amp; Maintenance Overhead&amp;lt;br/&amp;gt;$0"]
    end&lt;/code&gt;&lt;/pre&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Proxy bandwidth amplification&lt;/strong&gt;: Because headless browsers download client assets, 100k queries easily consume 200GB+ of residential proxy bandwidth. At $8 per GB, proxies alone cost $1,600 per month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server infrastructure&lt;/strong&gt;: Running 20 to 30 concurrent Playwright instances requires dedicated virtual machines (e.g. AWS EC2 &lt;code&gt;t4g.xlarge&lt;/code&gt; or &lt;code&gt;c6i.xlarge&lt;/code&gt;), costing roughly $96 to $140 per month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering time&lt;/strong&gt;: When selectors break twice a month, an engineer spends 3 to 4 hours diagnosing issues, testing selectors, and deploying hotfixes. At an average developer cost of $90 per hour, maintenance costs exceed $540 every month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;By contrast, using a managed SERP API&lt;/strong&gt; (such as SearchApi's BigData Plan at $250 per month for 100,000 searches) consolidates your entire bill into a single, predictable subscription with a 99.9% SLA, saving over $2,000 every month while eliminating developer on-call fatigue.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Handling advanced search engine layouts and rich snippets
&lt;/h2&gt;

&lt;p&gt;Modern search results are no longer simple lists of ten blue links. Search engines present dynamic, highly interactive features designed to answer user questions directly on the page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;People Also Ask (PAA)&lt;/strong&gt;: Accordion dropdowns that load dynamic content via asynchronous JavaScript calls upon being clicked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge Graph panels&lt;/strong&gt;: Rich sidebars containing entity facts, social profiles, founders, and related topics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Maps and local packs&lt;/strong&gt;: Interactive business listings with customer review ratings, operating hours, and geocoordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Overviews and rich snippets&lt;/strong&gt;: Dynamically streamed answers, product price tags, ratings, and video carousels.
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    SERP["Search Engine Results Page (SERP)"] --&amp;gt; AI["AI Overview / Rich Answer Box&amp;lt;br/&amp;gt;(Dynamic streaming content)"]
    SERP --&amp;gt; Main["Main Results Stream"]
    SERP --&amp;gt; Side["Entity Sidebar"]

    Main --&amp;gt; PAA["People Also Ask (PAA)&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;(Interactive JavaScript accordions)&amp;lt;/i&amp;gt;"]
    Main --&amp;gt; Maps["Local 3-Pack / Google Maps&amp;lt;br/&amp;gt;(Dynamic scroll &amp;amp; geocoordinates)"]
    Main --&amp;gt; Organic["Organic Search Results&amp;lt;br/&amp;gt;(Titles, URLs, sitelinks, snippets)"]

    Side --&amp;gt; KG["Knowledge Graph Panel&amp;lt;br/&amp;gt;(Entity attributes, social links, facts)"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  The challenge with DIY extraction of interactive elements
&lt;/h3&gt;

&lt;p&gt;Extracting these advanced components with Playwright is remarkably complex:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To scrape &lt;strong&gt;People Also Ask&lt;/strong&gt; questions, your script must locate each accordion element, issue click actions to trigger JavaScript expand events, wait for animation frames to finish, and parse the newly inserted DOM nodes.&lt;/li&gt;
&lt;li&gt;To scrape &lt;strong&gt;Local Maps packs&lt;/strong&gt;, your script must interact with map containers, handle scrolling viewports, and extract coordinates embedded inside nested JavaScript payloads.&lt;/li&gt;
&lt;li&gt;To scrape &lt;strong&gt;Knowledge Graph boxes&lt;/strong&gt;, your selectors must account for dozens of entity variations (companies, celebrities, movies, books, and locations), each of which renders with different HTML tags.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing and maintaining code for all these edge cases requires hundreds of lines of brittle automation logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extracting structured SERP data effortlessly
&lt;/h3&gt;

&lt;p&gt;With SearchApi, extracting interactive widgets requires zero browser actions. The API automatically parses every SERP feature into clean, dedicated JSON objects:&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;"search_metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search_65df89bc12e4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&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-08-29T05:12:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"request_time_taken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parsing_time_taken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_time_taken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.84&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ai_overview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"markdown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright is an open-source automation library and framework created by Microsoft for end-to-end testing across Chromium, Firefox, and WebKit."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reference_links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright: Fast and reliable end-to-end testing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://playwright.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"playwright.dev"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"knowledge_graph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kgmid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/g/11f555cn8l"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"knowledge_graph_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Software library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Software library"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright is an open-source automation library for browser testing and web scraping developed by Microsoft."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wikipedia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://en.wikipedia.org/wiki/Playwright_(software)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"website"&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://playwright.dev"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"related_questions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is Playwright better than Selenium?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright offers faster execution, native auto-waiting, built-in async support, and modern dev tools integration compared to Selenium."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright vs Selenium: Modern Web Automation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://example.com/playwright-vs-selenium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"displayed_link"&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://example.com &amp;gt; playwright-vs-selenium"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"organic_results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright: Fast and reliable end-to-end testing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://playwright.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"displayed_link"&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://playwright.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"playwright.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"snippet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Playwright enables reliable end-to-end testing for modern web apps across all modern rendering engines."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sitelinks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://playwright.dev/docs/intro"&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;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Python API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://playwright.dev/python/docs/intro"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your data pipeline can instantly access &lt;code&gt;payload["ai_overview"]["markdown"]&lt;/code&gt;, &lt;code&gt;payload["knowledge_graph"]&lt;/code&gt;, or &lt;code&gt;payload["related_questions"]&lt;/code&gt; without having to simulate user clicks or manage animation delays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision matrix: Choosing the right tool for your project
&lt;/h2&gt;

&lt;p&gt;Headless browsers and SERP APIs both have distinct places in modern software engineering. The key is applying the right tool to the right problem.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    Start{"What are you scraping?"}

    Start --&amp;gt;|Search Engine Results&amp;lt;br/&amp;gt;Google, Bing, Yahoo, Baidu| SERPBranch["Use a Dedicated SERP API&amp;lt;br/&amp;gt;(e.g. SearchApi)"]
    Start --&amp;gt;|General Web Pages&amp;lt;br/&amp;gt;Internal tools, small blogs, custom apps| WebBranch["Use Headless Browsers&amp;lt;br/&amp;gt;(Playwright, Puppeteer)"]

    SERPBranch --&amp;gt; S1["Fast, pre-parsed structured JSON"]
    SERPBranch --&amp;gt; S2["Zero proxy or CAPTCHA management"]
    SERPBranch --&amp;gt; S3["Guaranteed 99.9% uptime SLA"]

    WebBranch --&amp;gt; W1["Full UI interaction &amp;amp; custom click flows"]
    WebBranch --&amp;gt; W2["Session &amp;amp; SSO authentication handling"]
    WebBranch --&amp;gt; W3["Visual testing &amp;amp; PDF/screenshot generation"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  When to use headless browsers (Playwright, Puppeteer, Selenium)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end application testing&lt;/strong&gt;: Automating user journeys, validating form submissions, and testing web applications in CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scraping authenticated enterprise portals&lt;/strong&gt;: Logging into internal corporate dashboards or web apps behind custom Single Sign-On (SSO) login systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual regression testing and screenshots&lt;/strong&gt;: Capturing full-page screenshots, rendering PDFs, and verifying UI visual consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-security, dynamic niche sites&lt;/strong&gt;: Extracting content from single-page web applications (SPAs) that do not enforce strict anti-bot measures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to use a dedicated SERP API (SearchApi)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SEO rank tracking and SERP monitoring&lt;/strong&gt;: Tracking organic rankings, featured snippets, and local search visibility for thousands of keywords daily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agent web search and RAG pipelines&lt;/strong&gt;: Providing real-time search capabilities and clean web context to Large Language Models (LLMs) without latency bottlenecks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-commerce price intelligence and Google Shopping&lt;/strong&gt;: Extracting structured product pricing, seller ratings, and stock availability at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market research and competitor tracking&lt;/strong&gt;: Monitoring news trends, brand mentions, and Knowledge Graph developments with guaranteed uptime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Building your own search engine scraper with Playwright feels like a quick win during prototyping. But as your project scales, the hidden costs of residential proxies, anti-bot defenses, fragile DOM selectors, and heavy server hardware turn a simple script into a continuous infrastructure burden.&lt;/p&gt;

&lt;p&gt;Developer time is your team's most valuable asset. Spending hours debugging broken CSS selectors and configuring proxy rotation takes engineering focus away from building core features that deliver real customer value.&lt;/p&gt;

&lt;p&gt;By shifting your search data collection to a managed service like &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt;, you eliminate infrastructure complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace 100+ lines of fragile automation scripts with a clean, single HTTP GET call.&lt;/li&gt;
&lt;li&gt;Reduce cloud compute and proxy bandwidth expenses by over 80%.&lt;/li&gt;
&lt;li&gt;Gain instant access to structured JSON data for organic results, People Also Ask questions, Knowledge Graphs, and local packs.&lt;/li&gt;
&lt;li&gt;Enjoy guaranteed 99.9% uptime backed by enterprise-grade proxy rotation and automatic parser maintenance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop wrestling with headless browser memory leaks and CAPTCHA blocks. &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;Sign up for SearchApi&lt;/a&gt;, claim your free API credits, and start extracting reliable search engine data in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading and references
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.guvi.in/blog/what-is-web-scraping-and-how-to-use-it/" rel="noopener noreferrer"&gt;Web Scraping Architecture and Techniques&lt;/a&gt; - Overview of web data collection patterns.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://automatetheboringstuff.com/3e/chapter13.html" rel="noopener noreferrer"&gt;Automate the Boring Stuff with Python&lt;/a&gt; - Fundamentals of web scraping and browser control.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.browserless.io/blog/observations-running-headless-browser" rel="noopener noreferrer"&gt;Observations from Running Headless Browsers&lt;/a&gt; - Deep dive into memory, CPU, and process management.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://latenode.com/blog/selenium-headless" rel="noopener noreferrer"&gt;Headless Selenium and Playwright Operations&lt;/a&gt; - Technical trade-offs of headless automation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.nimbleway.com/blog/what-is-a-headless-browser" rel="noopener noreferrer"&gt;Understanding Modern Headless Browsers&lt;/a&gt; - Guide to browser architectures and anti-bot systems.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://onlinesuccessgenie.com/web-scraping-without-browser/" rel="noopener noreferrer"&gt;Web Scraping Without a Browser&lt;/a&gt; - Strategies for reducing compute overhead with APIs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://brightdata.com/blog/web-data/proxy-scraping-vs-scraping-browser" rel="noopener noreferrer"&gt;Proxy Scraping vs. Scraping Browsers&lt;/a&gt; - Comprehensive proxy comparison and bandwidth analysis.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://crawlbase.com/blog/headless-browsers-vs-api-scraping/" rel="noopener noreferrer"&gt;Headless Browsers vs. API Scraping&lt;/a&gt; - Performance and cost evaluation for web scraping.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://scrapingant.com/blog/google-web-scraping-alternatives" rel="noopener noreferrer"&gt;Alternatives for Google Search Data Extraction&lt;/a&gt; - Evaluating scraping proxies and SERP APIs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.designrush.com/agency/it-services/trends/serp-scraper-api" rel="noopener noreferrer"&gt;Search Engine Scraping Trends and Tools&lt;/a&gt; - Industry benchmarks for SERP data collection.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>playwright</category>
      <category>dataextraction</category>
      <category>serpapi</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Building a real-time AI search agent with SearchApi and OpenAI</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Fri, 21 Aug 2026 04:38:31 +0000</pubDate>
      <link>https://dev.to/eunit/building-a-real-time-ai-search-agent-with-searchapi-and-openai-16g8</link>
      <guid>https://dev.to/eunit/building-a-real-time-ai-search-agent-with-searchapi-and-openai-16g8</guid>
      <description>&lt;p&gt;Large Language Models (LLMs) are remarkably capable, but they suffer from two fundamental flaws: &lt;a href="https://arxiv.org/html/2603.08274" rel="noopener noreferrer"&gt;knowledge cutoffs and hallucinations&lt;/a&gt;. Ask an offline model about a breaking news event, a shifting stock price, or a new library release that dropped this morning, and it will either confidently fabricate a wrong answer or admit it has no idea. To build production-grade AI tools, your models need eyes and ears on the live web; your models need to be in the know of happenings as they break.&lt;/p&gt;

&lt;p&gt;While static &lt;a href="https://aws.amazon.com/what-is/retrieval-augmented-generation/" rel="noopener noreferrer"&gt;Retrieval-Augmented Generation (RAG)&lt;/a&gt; works well for searching internal vector databases, it fails the moment an accurate answer requires a real-time Google search. That is where AI search agents come in. An autonomous search agent turns an LLM into a reasoning engine that can choose when to browse the web, pull down search engine results pages (SERPs), synthesize real-time data, and deliver accurate, fact-checked answers.&lt;/p&gt;

&lt;p&gt;In this article, you will build a production-ready AI search agent from scratch using Python, OpenAI's tool-calling framework, and &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt; as the live web data pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why traditional web scraping fails for AI agents
&lt;/h2&gt;

&lt;p&gt;When developers realize their AI agent needs Google data, the first instinct is usually to reach for raw browser automation tools like &lt;a href="https://www.eunit.me/blog/playwright-vs-webdriverio-uncovering-the-differences" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, Puppeteer, or BeautifulSoup. You write code to open a headless browser, navigate to Google, type a query, and scrape the HTML.&lt;/p&gt;

&lt;p&gt;In a local testing environment, this approach works for about three requests. In production, it breaks immediately.&lt;/p&gt;

&lt;p&gt;Building your own search engine scraper means constantly fighting multi-billion-dollar anti-bot security teams. Your script will quickly run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP blocks and CAPTCHAs:&lt;/strong&gt; Search engines rapidly flag and block data center IP addresses. Rotating your own proxy pool is expensive and time-consuming to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragile DOM structures:&lt;/strong&gt; Search engines constantly shift their HTML layout and CSS classes. A script targeting &lt;code&gt;div.g&lt;/code&gt; tonight will silently return empty data by tomorrow morning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute overhead:&lt;/strong&gt; Spinning up heavy browser environments (like headless Chromium) consumes significant server memory and CPU cycles, adding cost and latency to every query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To keep AI agents fast and reliable, you need an abstraction layer that handles proxy rotation, bypasses CAPTCHAs, and returns structured data instantly. By offloading this infrastructure to &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt;, you retrieve clean, pre-parsed JSON payloads using a lightweight HTTP request - at a fraction of the compute overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  System architecture
&lt;/h2&gt;

&lt;p&gt;Before writing any code, it helps to understand how an autonomous tool-calling loop operates. This system does not blindly search the web on every turn. Instead, it follows a four-step conversation loop:&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%2Frug2xhakyk3daovg5jis.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%2Frug2xhakyk3daovg5jis.png" alt="AI Search Agent architecture - 4-step flow: User query to LLM decision to SearchApi call to AI synthesis" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The intent check:&lt;/strong&gt; The user asks a question. The LLM evaluates whether it can answer using its internal knowledge or whether it needs live web data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tool call trigger:&lt;/strong&gt; If it needs live data, the LLM stops generating text and outputs a structured request to invoke the &lt;code&gt;search_web&lt;/code&gt; tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The infrastructure layer:&lt;/strong&gt; The Python script intercepts this request, queries SearchApi's Google engine, and returns the top organic search results as structured JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The synthesis:&lt;/strong&gt; The text snippets are injected back into the LLM's context window. The model reads the live web data and generates a grounded, hallucination-free response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This architecture is efficient because the LLM acts as the decision-maker. It avoids unnecessary API calls for questions it can already answer accurately from its training data.&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%2Fevhjogolxocysfqkhzlz.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fmedia%2Fiij6ovinfhm.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%2Fevhjogolxocysfqkhzlz.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fmedia%2Fiij6ovinfhm.png" alt="SearchApi logo" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites and project setup
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you will need three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.8+&lt;/strong&gt; installed on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An OpenAI API key&lt;/strong&gt; to power the model's reasoning loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A SearchApi API key&lt;/strong&gt; to handle live web access. You can sign up at &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt; and get 100 free searches right out of the box - no credit card required.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start by initializing the project folder and installing the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create and move into your project folder&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;ai-search-agent &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ai-search-agent

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;requests openai python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a &lt;code&gt;.env&lt;/code&gt; file in the root of your project directory to keep your API tokens secure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_actual_openai_api_key_here"&lt;/span&gt;
&lt;span class="nv"&gt;SEARCHAPI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_actual_searchapi_api_key_here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the project environment ready, here is an overview of the full project structure you will build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Building a Real-Time AI Search Agent/
├── ai-search-agent/           &amp;lt;- Python CLI Implementation
│   ├── search_helper.py       &amp;lt;- SearchApi wrapper function (Step 1)
│   ├── agent.py               &amp;lt;- OpenAI tool schema and ask_agent() (Step 2)
│   ├── run.py                 &amp;lt;- Orchestrator loop (Step 3)
│   ├── .env.example           &amp;lt;- Template for environment variables
│   └── requirements.txt       &amp;lt;- pip dependencies
└── realtime-search/           &amp;lt;- Production Next.js 14 Application
    ├── app/
    │   ├── api/
    │   │   ├── search/        &amp;lt;- Server-side SearchApi route handler
    │   │   └── synthesize/    &amp;lt;- Real-time OpenAI SSE streaming handler
    │   ├── globals.css        &amp;lt;- Design system tokens &amp;amp; glassmorphism
    │   ├── layout.tsx         &amp;lt;- Root layout &amp;amp; SSR theme script
    │   └── page.tsx           &amp;lt;- Agent dashboard container
    ├── components/            &amp;lt;- React UI components
    ├── hooks/                 &amp;lt;- Pipeline orchestration &amp;amp; history hooks
    ├── lib/                   &amp;lt;- Server utilities &amp;amp; TypeScript definitions
    └── README.md              &amp;lt;- Next.js application documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Writing the search API helper function
&lt;/h2&gt;

&lt;p&gt;The first module is a &lt;code&gt;search_web()&lt;/code&gt; function that sends a query to &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi's Google Search engine&lt;/a&gt; and returns a compressed block of text containing the titles, snippets, and source links from the top organic results.&lt;/p&gt;

&lt;p&gt;The key design decision here is to extract only three fields - &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;snippet&lt;/code&gt;, and &lt;code&gt;link&lt;/code&gt; - from SearchApi's &lt;code&gt;organic_results&lt;/code&gt; array. This keeps the payload lightweight and prevents the LLM from wasting tokens on irrelevant metadata like ad blocks, knowledge panels, or navigation breadcrumbs.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;search_helper.py&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;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;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="c1"&gt;# Load API keys from environment variables
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;SEARCHAPI_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEARCHAPI_API_KEY&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;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;United States&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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 the SearchApi.io Google Search engine and returns a compressed
    string containing the titles, snippets, and links of organic results.
    &lt;/span&gt;&lt;span class="sh"&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;SEARCHAPI_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: SearchApi API key missing from environment variables.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.searchapi.io/api/v1/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Configure payload parameters according to the SearchApi documentation
&lt;/span&gt;    &lt;span class="n"&gt;params&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;engine&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;google&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;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SEARCHAPI_KEY&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&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;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;params&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="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract organic search results
&lt;/span&gt;        &lt;span class="n"&gt;organic_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;organic_results&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;organic_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&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;No relevant web search results found for: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;

        &lt;span class="c1"&gt;# Parse and stringify the results into a compact context block for the LLM
&lt;/span&gt;        &lt;span class="n"&gt;formatted_results&lt;/span&gt; &lt;span class="o"&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;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organic_results&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Limit to top 5 results
&lt;/span&gt;            &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No Title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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;snippet&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;No Snippet Available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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;link&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="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;formatted_results&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="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;index&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;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Snippet: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Source: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&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="k"&gt;return&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatted_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;An infrastructure error occurred while querying the search API: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="c1"&gt;# Quick standalone test execution
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;test_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Who won the men&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s 100m sprint in the 2024 Paris Olympics?&lt;/span&gt;&lt;span class="sh"&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="s"&gt;Testing SearchApi wrapper with query: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;organic_results[:5]&lt;/code&gt; slice is intentional. Feeding more than five results into the LLM context rarely improves answer quality and meaningfully increases token consumption and cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Defining the LLM agent framework (tool calling)
&lt;/h2&gt;

&lt;p&gt;With the data pipeline ready, you need to teach the LLM when and how to use it. This is done through &lt;a href="https://developers.openai.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI's &lt;strong&gt;tool calling&lt;/strong&gt;&lt;/a&gt; (formerly called function calling) feature, which lets you define structured schemas that the model can invoke when it determines external data is required.&lt;/p&gt;

&lt;p&gt;Create a file, and name it &lt;code&gt;agent.py&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;search_helper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;search_web&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the OpenAI client
&lt;/span&gt;&lt;span class="n"&gt;openai_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Define the SearchApi tool schema for the model
&lt;/span&gt;&lt;span class="n"&gt;search_tool_definition&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;type&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;function&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;function&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;name&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;search_web&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;description&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;Call this tool whenever you need up-to-date information, news, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current events, or web data that occurred after your knowledge cutoff date.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameters&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;type&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;object&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;properties&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;query&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;type&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;string&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;description&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;The optimized search query string to look up on Google &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tesla Model Y 2026 pricing specifications&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="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&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;type&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;string&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;description&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;Geographical location filter for search targeting. Defaults to &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;United States&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&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;United States&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;required&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;query&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="p"&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;ask_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Sends a query to the LLM along with the available search tool definition.
    The LLM determines autonomously whether it needs to execute a Google search.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&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;role&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;system&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;content&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;You are a factual, precise AI search agent with live web access via SearchApi. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use the search_web tool when you need current or time-sensitive information.&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Request model execution with the tools parameter exposed
&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;openai_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# Efficient, tool-capable model
&lt;/span&gt;        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;search_tool_definition&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;tool_choice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# Let the model autonomously decide whether to use the tool
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response_message&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="n"&gt;choices&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="n"&gt;message&lt;/span&gt;
    &lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool_calls&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;Agent decision: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;I need to use SearchApi to look up live information on the web.&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;tool_call_required&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;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;tool_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&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;Agent decision: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;I can answer this question from my existing knowledge.&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;completed&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;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;response_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tool_choice="auto"&lt;/code&gt; parameter is the key to the agent's autonomy. It tells OpenAI to let the model decide whether external search is necessary rather than forcing a tool call on every request. Simple questions like "What is Python?" get answered directly, while questions about current events correctly trigger a SearchApi call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Stitching the search agent loop together
&lt;/h2&gt;

&lt;p&gt;The final piece is the orchestrator - a runtime loop that ties everything together. It interprets the model's decision, calls &lt;code&gt;search_web()&lt;/code&gt; with the model's chosen arguments, injects the live results back into the conversation history, and requests a final synthesized answer from OpenAI.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;run.py&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;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ask_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;openai_client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;search_helper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;search_web&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    The orchestrator runtime loop. It prompts the agent, detects tool requests,
    executes the local SearchApi call, updates the conversation history, and
    generates the final web-informed answer.
    &lt;/span&gt;&lt;span class="sh"&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;User: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_prompt&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="c1"&gt;# First turn: ask the agent what it wants to do
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Case A: The LLM answered immediately without needing the web
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&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="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Final answer:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&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="si"&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="k"&gt;return&lt;/span&gt;

    &lt;span class="c1"&gt;# Case B: The LLM explicitly requested a SearchApi tool call
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&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="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call_required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Append the model's tool request to keep the chat history balanced
&lt;/span&gt;        &lt;span class="n"&gt;assistant_msg&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;role&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;assistant&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_calls&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="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;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;function&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&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;tc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;messages&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="n"&gt;assistant_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Process each tool call (handles potential parallel calls from the model)
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="n"&gt;function_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&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="s"&gt;Invoking tool: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;() with arguments &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;function_args&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;search_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;function_args&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;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;search_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;function_args&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;location&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;United States&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Fetch structured data live from SearchApi.io
&lt;/span&gt;                &lt;span class="n"&gt;raw_search_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Append the search result payload back to the chat history
&lt;/span&gt;                &lt;span class="n"&gt;tool_response_msg&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;role&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;tool&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;tool_call_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;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;raw_search_results&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;messages&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="n"&gt;tool_response_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&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="s"&gt;Warning: model requested unknown tool: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;function_name&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="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Second turn: send the full conversation history back to OpenAI
&lt;/span&gt;        &lt;span class="c1"&gt;# (This now contains: user query + agent tool call + SearchApi results)
&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;Synthesizing live search data into a comprehensive answer...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;final_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Final answer:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&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="c1"&gt;# End-to-end system test
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Test case 1: Requires SearchApi intervention (current events)
&lt;/span&gt;    &lt;span class="nf"&gt;run_agent_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What were the top 3 tech stock market trends in Q1 of 2026?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test case 2: Native knowledge execution (no web tool needed)
&lt;/span&gt;    &lt;span class="nf"&gt;run_agent_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain the fundamental difference between a SQL and NoSQL database in two sentences.&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;Pay close attention to how the &lt;code&gt;messages&lt;/code&gt; list is built across both turns. After the model requests a tool call, you must append the assistant's tool request and then the tool's response before sending the history back to OpenAI. Omitting either step causes the API to return an error - this is the most common breaking point for developers new to tool calling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live test drives
&lt;/h2&gt;

&lt;p&gt;With all three files in place, run the agent with &lt;code&gt;python run.py&lt;/code&gt;. Here is what a real execution looks like for each scenario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario A - Current events query:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: What were the top 3 tech stock market trends in Q1 of 2026?

Agent decision: 'I need to use SearchApi to look up live information on the web.'
Invoking tool: search_web() with arguments {'query': 'top tech stock market trends Q1 2026'}
Synthesizing live search data into a comprehensive answer...

Final answer:
Based on recent web data, the top three tech stock market trends in Q1 2026 were:

1. AI infrastructure stocks surged as hyperscalers accelerated GPU procurement...
2. Semiconductor supply chain stabilization drove AMD and TSMC to multi-year highs...
3. Enterprise SaaS consolidation continued, with several major M&amp;amp;A announcements...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scenario B - Native knowledge query:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: Explain the fundamental difference between a SQL and NoSQL database in two sentences.

Agent decision: 'I can answer this question from my existing knowledge.'

Final answer:
SQL databases organize data into structured tables with predefined schemas and use
relational joins to link data across tables, making them ideal for consistent,
transactional workloads. NoSQL databases use flexible document, key-value, or graph
data models with no fixed schema, trading strict consistency for horizontal scalability
and performance on unstructured or rapidly evolving data.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent correctly identifies that the first query requires live web data and that the second can be answered from its own training knowledge - skipping an unnecessary API call entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo: see the agent in action
&lt;/h2&gt;

&lt;p&gt;The interactive demo below visualizes the exact agent loop described above - including the tool decision badge, the &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi&lt;/a&gt; result cards, and the synthesized answer panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://realtime-search.vercel.app/" rel="noopener noreferrer"&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%2Fzw44b9zj9qj5b3wdant0.png" alt="AI Search Agent demo - initial state showing the query input and architecture panel" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Interactive Demos &amp;amp; Repositories:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🚀 Live Web Application:&lt;/strong&gt; Test the full-stack agent live on Vercel at &lt;a href="https://realtime-search.vercel.app/" rel="noopener noreferrer"&gt;realtime-search.vercel.app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🐍 Python CLI Repository:&lt;/strong&gt; Clone the Python tutorial source code at &lt;a href="https://github.com/Eunit99/ai-search-agent" rel="noopener noreferrer"&gt;Eunit99/ai-search-agent&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚡ Next.js App Repository:&lt;/strong&gt; Clone the production web application code at &lt;a href="https://github.com/Eunit99/realtime-search" rel="noopener noreferrer"&gt;Eunit99/realtime-search&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-Stack Next.js Application:&lt;/strong&gt; Located in &lt;a href="https://github.com/Eunit99/realtime-search" rel="noopener noreferrer"&gt;&lt;code&gt;realtime-search/&lt;/code&gt;&lt;/a&gt; - features server-side API key isolation (&lt;code&gt;/api/search&lt;/code&gt;), real-time OpenAI GPT-4o-mini SSE streaming (&lt;code&gt;/api/synthesize&lt;/code&gt;), geo-targeting, and query history persistence.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Production best practices
&lt;/h2&gt;

&lt;p&gt;Before shipping this agent to production, consider these refinements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter low-quality snippets to save tokens
&lt;/h3&gt;

&lt;p&gt;SearchApi's &lt;code&gt;organic_results&lt;/code&gt; array occasionally includes results with very short or unhelpful snippets (e.g. login-gated pages or index-only entries). Add a simple length filter to skip any snippet under 40 characters:&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="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;snippet&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;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;formatted_results&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="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;index&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;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Snippet: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Source: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&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;h3&gt;
  
  
  Use geo-targeting for localized results
&lt;/h3&gt;

&lt;p&gt;SearchApi supports a &lt;code&gt;location&lt;/code&gt; parameter that localizes search results geographically. This is useful for agents that serve users across different regions, where price, news, or availability data differs by market. The &lt;code&gt;search_web()&lt;/code&gt; function already exposes this parameter:&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="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current petrol prices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lagos, Nigeria&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;h3&gt;
  
  
  Cache duplicate queries
&lt;/h3&gt;

&lt;p&gt;In production, users frequently ask similar questions within a short time window. Caching the &lt;code&gt;search_web()&lt;/code&gt; response avoids burning SearchApi credits on identical lookups. A Redis-backed wrapper keeps it simple:&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;

&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TTL_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# Cache results for 5 minutes
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_web_cached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;United States&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cache_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&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;query&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;location&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;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&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;cache_key&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;cached&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;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TTL_SECONDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&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;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keep the system prompt specific
&lt;/h3&gt;

&lt;p&gt;Your system prompt directly affects how often the model decides to call &lt;code&gt;search_web&lt;/code&gt;. Be explicit about when the tool should and should not be used:&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use the search_web tool for any question involving current events, prices, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;people, companies, or anything that may have changed after your training cutoff. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer directly only for timeless facts, historical events, or technical concepts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;You have built a production-ready AI search agent that solves the two fundamental limitations of offline LLMs: knowledge cutoffs and hallucinations on real-time data.&lt;/p&gt;

&lt;p&gt;The architecture is intentionally minimal but extensible. The &lt;code&gt;search_web()&lt;/code&gt; function can be swapped for any SearchApi engine (News, YouTube, Google Scholar, and more). The tool schema can be extended with additional tools for math, code execution, or database lookup. The agent loop can be wrapped in a web server, a Slack bot, or a voice assistant.&lt;/p&gt;

&lt;p&gt;The key insight is that SearchApi handles the hard infrastructure work - proxy rotation, CAPTCHA solving, HTML parsing, and structured JSON delivery - so you can focus entirely on the reasoning layer on top of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to build?&lt;/strong&gt; Grab your free API key at &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi.io&lt;/a&gt;, check out the live web app on &lt;a href="https://realtime-search.vercel.app/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, clone the Python CLI repository from &lt;a href="https://github.com/Eunit99/ai-search-agent" rel="noopener noreferrer"&gt;GitHub (ai-search-agent)&lt;/a&gt; or the full-stack web application from &lt;a href="https://github.com/Eunit99/realtime-search" rel="noopener noreferrer"&gt;GitHub (realtime-search)&lt;/a&gt;, and drop a comment describing what you build.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions or improvements? Open an issue or submit a pull request on &lt;a href="https://github.com/Eunit99/ai-search-agent" rel="noopener noreferrer"&gt;GitHub (Python CLI)&lt;/a&gt; or &lt;a href="https://github.com/Eunit99/realtime-search" rel="noopener noreferrer"&gt;GitHub (Next.js App)&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is sponsored by &lt;a href="https://www.searchapi.io/?utm_source=Dev&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=eunit.me" rel="noopener noreferrer"&gt;SearchApi&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>llm</category>
      <category>openai</category>
      <category>searchapi</category>
    </item>
    <item>
      <title>How I automated hyper-local lead routing with Nextdoor Scraper and Slack</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Tue, 04 Aug 2026 16:25:45 +0000</pubDate>
      <link>https://dev.to/eunit/how-i-automated-hyper-local-lead-routing-with-nextdoor-scraper-and-slack-5db0</link>
      <guid>https://dev.to/eunit/how-i-automated-hyper-local-lead-routing-with-nextdoor-scraper-and-slack-5db0</guid>
      <description>&lt;p&gt;Picture this. A sales rep or local contractor sitting in their truck on a Tuesday morning, burning fuel driving 45 minutes across town to quote a home repair, only to discover the prospect is a renter who cannot authorize work. Meanwhile, three streets over, in a hidden, high-density homeowner neighborhood, local residents are actively posting on &lt;a href="https://nextdoor.com/" rel="noopener noreferrer"&gt;Nextdoor&lt;/a&gt; to ask for contractor recommendations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.eunit.me/blog/how-to-scrape-nextdoor-for-hyper-local-demographics-and-community-sentiment" rel="noopener noreferrer"&gt;Collecting hyper-local data into a weekly CSV export&lt;/a&gt; is like brewing a fresh pot of coffee and leaving it on the counter for two days before serving it to your sales team. By the time an analyst manually exports the dataset, filters target zip codes, and emails the report to reps, those golden neighborhood opportunities are cold and competitors have already booked the jobs.&lt;/p&gt;

&lt;p&gt;However, extracting this hyper-local data from public sources has historically presented a severe operational challenge. Nextdoor is a rich ecosystem for local community context, but manually researching across hundreds of city pages is painfully slow. Worse still, traditional web scraping workflows often deposit extracted data into static CSV files or database tables that sit idle until someone manually reviews them days later. In high-intent lead generation, every hour of delay degrades lead momentum and lowers conversion rates.&lt;/p&gt;

&lt;p&gt;To solve this problem, I built two complementary lead routing workflows around &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; on the Apify platform. The first is a no-code Slack notification wired directly inside Apify Console. The second is an AI agent workflow that connects Nextdoor Scraper to Claude or Cursor via the &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt;, letting an AI agent run the scraper, filter results, and push alerts to Slack through a single conversational prompt.&lt;/p&gt;

&lt;p&gt;In this article, I will walk you through the extraction setup, the qualification filter logic, both Slack routing approaches, and exactly what each option looks like in practice.&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.amazonaws.com%2Fuploads%2Farticles%2Fk3jzl3ho7vzvn5pzdzxa.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.amazonaws.com%2Fuploads%2Farticles%2Fk3jzl3ho7vzvn5pzdzxa.png" alt="Nextdoor logo" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The gap between data collection and sales action
&lt;/h2&gt;

&lt;p&gt;For home service providers, such as &lt;a href="https://blog.apify.com/plumbing-leads/" rel="noopener noreferrer"&gt;plumbers&lt;/a&gt;, roofers, HVAC technicians, &lt;a href="https://blog.apify.com/scraping-real-estate-data/" rel="noopener noreferrer"&gt;landscapers&lt;/a&gt;, and solar installers, speed and geographic precision are everything. Identifying an ideal target neighborhood is only half the battle. If your sales team receives demographic reports 48 hours after a data crawl finishes, the opportunity to run timely local direct mail campaigns, localized digital ad pushes, or door-to-door outreach in high-demand pockets has already started to slip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------+      +--------------------+      +----------------------+
|  Public Nextdoor  | ---&amp;gt; |  Nextdoor Scraper  | ---&amp;gt; |  Data Dump (CSV/DB)  |
|    City Pages     |      |  (Manual Crawl)    |      |  (Sits Idle Days)    |
+-------------------+      +--------------------+      +----------------------+
                                                                  |
                                                                  v
                                                       [ Delayed Sales Action ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The primary friction point in modern web scraping is not extracting the data - it is the latency between data acquisition and business action. Traditional workflows decouple extraction from execution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A crawler runs and outputs thousands of raw records into key-value stores or CSV files.&lt;/li&gt;
&lt;li&gt;An analyst exports the CSV and manually applies spreadsheet filters to isolate target zip codes.&lt;/li&gt;
&lt;li&gt;The analyst formats a report and emails it to the sales lead.&lt;/li&gt;
&lt;li&gt;The sales lead assigns territories to reps who finally begin outreach.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This multi-step, manual chain creates massive operational friction. By the time reps receive the target list, days have elapsed.&lt;/p&gt;

&lt;p&gt;To eliminate this gap, I built two notification paths that both start from the same Nextdoor Scraper run, and both deliver results to Slack, but in different ways depending on your team's setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------+      +--------------------+      +---------------------+
|  Public Nextdoor  | ---&amp;gt; |  Nextdoor Scraper  | ---&amp;gt; |  Apify Dataset      |
|    City Pages     |      |  (Apify Platform)  |      |  (Structured JSON)  |
+-------------------+      +--------------------+      +---------------------+
                                                                  |
                          +--------------------------+------------+
                          |                          |
                          v                          v
             [ Path A: No-code ]        [ Path B: MCP server ]
             Slack notification         AI agent (Claude/Cursor)
             via Actor integration      calls Nextdoor Scraper,
             inside Apify Console       filters leads, posts to Slack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. The pipeline architecture
&lt;/h2&gt;

&lt;p&gt;The automated lead routing system operates across two stages: extraction and post-run notification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant ND as Public Nextdoor Pages
    participant NS as Nextdoor Scraper (Apify Platform)
    participant DS as Apify Dataset
    participant SL as Slack (#local-leads)
    participant AI as AI Agent (Claude / Cursor)

    ND-&amp;gt;&amp;gt;NS: Fetch public city &amp;amp; neighborhood page HTML
    NS-&amp;gt;&amp;gt;DS: Store structured place data (centroid, income, homeowners)
    Note over DS: Run completes. Dataset is ready.

    alt Path A: No-code Slack notification
        DS-&amp;gt;&amp;gt;SL: Actor integration fires on run finish
        SL--&amp;gt;&amp;gt;DS: Notification delivered
    else Path B: MCP server + AI agent
        AI-&amp;gt;&amp;gt;NS: call-actor via Apify MCP server (mcp.apify.com)
        NS-&amp;gt;&amp;gt;DS: Actor runs and stores results
        DS-&amp;gt;&amp;gt;AI: AI agent reads dataset items
        AI-&amp;gt;&amp;gt;AI: Filter by homeowners &amp;gt;= 50%, income &amp;gt;= $40k
        AI-&amp;gt;&amp;gt;SL: Post formatted alert via Slack bot or Slack MCP tool
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stage 1: Extraction
&lt;/h3&gt;

&lt;p&gt;The workflow begins by launching &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; on the &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify platform&lt;/a&gt;. The Actor &lt;a href="https://www.eunit.me/blog/how-to-scrape-nextdoor-for-hyper-local-demographics-and-community-sentiment" rel="noopener noreferrer"&gt;targets public Nextdoor city and neighborhood pages&lt;/a&gt;, bypassing the need for complex browser automation setups or raw HTML parsing scripts. It parses visible structured data and metadata, transforming disorganized web content into clean JSON records that include geographic coordinates, demographic metrics, resident counts, and references to community activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Notification routing
&lt;/h3&gt;

&lt;p&gt;Once the Actor run finishes and the dataset is populated, there are two ways to get that data into Slack. The first requires no code at all. The second turns the entire pipeline into something an AI agent like Claude or Cursor can trigger, filter, and broadcast with a single prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Step 1: Extracting targeted neighborhood demographics
&lt;/h2&gt;

&lt;p&gt;To get started, we configure &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; to target public city pages. Nextdoor public city pages display valuable summary metrics without requiring user logins or private authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input configuration
&lt;/h3&gt;

&lt;p&gt;In Apify Console, open the &lt;strong&gt;Input&lt;/strong&gt; tab for Nextdoor Scraper and supply your target city URLs along with crawl limits. Here is the &lt;code&gt;JSON&lt;/code&gt; input configuration used for this pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/north-miami--fl/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/bellaire--oh/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/bellbrook--oh/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/belle-center--oh/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"downloadStartUrlsToHtmlSources"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxRequestsPerCrawl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxConcurrency"&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;"maxItemsPerSection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"followNeighborhoodPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"followBusinessDirectoryPages"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"followAdditionalResourcePages"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"saveHtmlSnapshots"&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;&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%2Fsjclhz464tfcu05os530.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%2Fsjclhz464tfcu05os530.png" alt="Extracting targeted neighborhood demographics" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key demographic fields extracted
&lt;/h3&gt;

&lt;p&gt;When Nextdoor Scraper processes a city page, it returns normalized fields ready for immediate analysis:&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;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Sample Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cityName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Name of the target city or municipality&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"North Miami"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Two-letter state abbreviation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"FL"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;centroid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;Geographic center latitude and longitude&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"latitude": 40.016, "longitude": -80.747}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;residentsCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;Total estimated residents in the area&lt;/td&gt;
&lt;td&gt;&lt;code&gt;59854&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;averageAge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;Average resident age&lt;/td&gt;
&lt;td&gt;&lt;code&gt;35&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;averageIncome&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Public average household income indicator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"$41k"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;percentageHomeowners&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Share of residents who own their homes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"56%"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;affordabilityScore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;Calculated local affordability index&lt;/td&gt;
&lt;td&gt;&lt;code&gt;99&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;friendlinessScore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;Community friendliness score&lt;/td&gt;
&lt;td&gt;&lt;code&gt;86&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;faqs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;Structured FAQ question and answer pairs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[{"question": "...", "answer": "..."}]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;neighborhoods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;Discovered public neighborhood links&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[{"title": "128th St", "url": "..."}]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sample output JSON
&lt;/h3&gt;

&lt;p&gt;Here is a snippet of the structured dataset item generated by Nextdoor Scraper:&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/north-miami--fl/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"finalUrl"&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://nextdoor.com/city/north-miami--fl/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pageType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"North Miami, Florida | Nextdoor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Public city summary extracted from Nextdoor."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cityName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"North Miami"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"centroid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;25.8901&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-80.1867&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"residentsCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;59854&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"averageAge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"averageIncome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$48k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"percentageHomeowners"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"54%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"affordabilityScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"friendlinessScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"neighborhoodCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"businessDirectoryCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"faqCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accessStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public"&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;h3&gt;
  
  
  Cost efficiency with Pay-Per-Event pricing
&lt;/h3&gt;

&lt;p&gt;One major advantage of using &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; on the &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify platform&lt;/a&gt; is its pricing structure. &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; operates on a &lt;a href="https://help.apify.com/en/articles/10700066-what-is-pay-per-event" rel="noopener noreferrer"&gt;Pay-Per-Event (PPE)&lt;/a&gt; model.&lt;/p&gt;

&lt;p&gt;Unlike traditional compute-hour billing where you pay for raw server uptime regardless of success, &lt;a href="https://help.apify.com/en/articles/10700066-what-is-pay-per-event" rel="noopener noreferrer"&gt;PPE&lt;/a&gt; charging means you are billed strictly for successfully extracted place records. If a network fluctuation or access block occurs on a page, you are not billed for that failed attempt. This ensures predictable margins when executing large-scale, automated lead discovery campaigns across thousands of zip codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Step 2: Filtering for contractor accountability and intent
&lt;/h2&gt;

&lt;p&gt;To understand how conditional filtering works in practice, let us examine a real-world implementation executed during a regional contractor accountability platform pilot with Lake Rock Ventures.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lake Rock Ventures pilot scenario
&lt;/h3&gt;

&lt;p&gt;Lake Rock Ventures, a venture growth studio, was launching a pilot platform designed to connect vetted local roofing and HVAC contractors with high-intent homeowners. The platform needed to identify suburban communities with high enough homeownership rates to justify targeted direct-response marketing. Sending contractor representatives into renter-heavy neighborhoods resulted in wasted labor costs and low conversion rates.&lt;/p&gt;

&lt;p&gt;To solve this, we defined strict qualification parameters for acceptable lead routing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Homeowner ratio (&lt;code&gt;percentageHomeowners&lt;/code&gt;)&lt;/strong&gt;: Minimum of 50%. Homeowners make long-term property decisions; renters rarely purchase roof replacements or HVAC upgrades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community scale (&lt;code&gt;residentsCount&lt;/code&gt;)&lt;/strong&gt;: Minimum of 1,000 residents to guarantee adequate market volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Income indicator (&lt;code&gt;averageIncome&lt;/code&gt;)&lt;/strong&gt;: Must meet or exceed $40k to ensure purchasing power for home improvement projects.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Writing the mid-run evaluation logic
&lt;/h3&gt;

&lt;p&gt;We implemented a custom evaluation function in JavaScript that processes each dataset record emitted by Nextdoor Scraper. The script parses formatted strings (such as &lt;code&gt;"56%"&lt;/code&gt; or &lt;code&gt;"$48k"&lt;/code&gt;) into clean numerical values and tests them against our threshold rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Evaluates whether an extracted Nextdoor dataset item qualifies as a high-intent local lead.
 * @param {Object} item - Structured record from Nextdoor Scraper.
 * @returns {Object} Qualification result containing boolean status and parsed metrics.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;evaluateLeadQualification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Extract numerical value from percentage string e.g., "56%" -&amp;gt; 56&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;homeownerRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;percentageHomeowners&lt;/span&gt; 
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;percentageHomeowners&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// Extract numerical value from income string e.g., "$48k" -&amp;gt; 48000&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;incomeValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;averageIncome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawIncome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;averageIncome&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawIncome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;incomeValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawIncome&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;incomeValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawIncome&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;residentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;residentsCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Qualification thresholds&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_HOMEOWNER_PERCENT&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_INCOME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_RESIDENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isQualified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
        &lt;span class="nx"&gt;homeownerRate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MIN_HOMEOWNER_PERCENT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;incomeValue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MIN_INCOME&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;residentCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MIN_RESIDENTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;isQualified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;homeownerRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;incomeValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;residentCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;affordabilityScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;affordabilityScore&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;friendlinessScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;friendlinessScore&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By embedding this evaluation logic directly into our execution pipeline, every crawled page is screened within milliseconds of extraction. If &lt;code&gt;isQualified&lt;/code&gt; evaluates to &lt;code&gt;true&lt;/code&gt;, the pipeline proceeds to dispatch a real-time Slack notification.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Step 3 (Path A): Sending a Slack notification with no code
&lt;/h2&gt;

&lt;p&gt;Apify Console includes a built-in Slack notification integration for every Actor. It requires no custom code, no webhook endpoint, and no token management. You set it up once directly on the Nextdoor Scraper Actor page and it fires automatically after every run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the Slack notification in Apify Console
&lt;/h3&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%2Fmzarzbgkepufvx6hjv7t.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%2Fmzarzbgkepufvx6hjv7t.png" alt="Setting up the Slack notification in Apify Console" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; in Apify Console and click the &lt;strong&gt;Integrations&lt;/strong&gt; tab in the Actor's top navigation.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Search integrations&lt;/strong&gt; bar, type &lt;code&gt;slack&lt;/code&gt;. You will see a &lt;strong&gt;Send notification via Slack&lt;/strong&gt; option listed under &lt;strong&gt;Generic integrations&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Send notification via Slack&lt;/strong&gt;. A panel appears with a &lt;strong&gt;Connect workspace&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Connect workspace&lt;/strong&gt; and authorize the Apify app to access your Slack workspace. Select the target channel, such as &lt;code&gt;#local-leads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure when the notification fires. The most useful trigger is &lt;strong&gt;Run succeeded&lt;/strong&gt;, which posts a Slack message automatically each time a Nextdoor Scraper run completes successfully.&lt;/li&gt;
&lt;li&gt;Save the integration. From this point forward, every completed run posts a notification to your chosen Slack channel without any additional code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The notification includes the run status, a direct link to the dataset in Apify Console, and the Actor name. Your sales team gets an immediate signal that fresh neighborhood data is ready to review, without having to log in and check manually.&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%2Fov4mki0yv603wvfpp1at.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%2Fov4mki0yv603wvfpp1at.png" alt="Nextdoor Slack Integration" width="643" height="768"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------+      +---------------------+      +------------------------------+
| Nextdoor Scraper   | ---&amp;gt; | Run succeeds        | ---&amp;gt; | Slack: #local-leads          |
| (Apify Console)    |      | (Apify Platform)    |      | "Your run finished. View      |
|                    |      |                     |      |  results in Apify Console."  |
+--------------------+      +---------------------+      +------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fastest path to getting Slack alerts from Nextdoor Scraper. It takes under two minutes to configure and works on every run automatically, including scheduled runs and saved tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Step 4 (Path B): Using the Apify MCP server with an AI agent
&lt;/h2&gt;

&lt;p&gt;Path A sends a basic completion notification. Path B goes further: it lets an AI agent such as Claude or Cursor actually run Nextdoor Scraper, read and filter the extracted dataset, and post a formatted, actionable Slack alert with specific neighborhood metrics - all from a single conversational prompt.&lt;/p&gt;

&lt;p&gt;This is possible because Apify hosts a remote &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; server at &lt;code&gt;https://mcp.apify.com&lt;/code&gt;. Once you connect an MCP client to that server, your AI agent gains access to tools such as &lt;code&gt;call-actor&lt;/code&gt;, &lt;code&gt;search-actors&lt;/code&gt;, and &lt;code&gt;fetch-actor-details&lt;/code&gt;. It can run Nextdoor Scraper on demand and process the results directly inside the chat context.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Apify MCP server?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.apify.com/platform/integrations/mcp" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt; exposes Apify Actors as callable tools to external AI clients. It is a programmatic interface that works with any MCP-compatible client, including Claude for Desktop, Cursor, VS Code with MCP support, and others.&lt;/p&gt;

&lt;p&gt;This is distinct from MCP connectors, which work in the opposite direction (an Actor calling an external service). The Apify MCP server makes Actors available as tools your AI agent can invoke from outside Apify entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting the Apify MCP server to your AI client
&lt;/h3&gt;

&lt;p&gt;You authorize once via OAuth browser flow. After that, your AI client can call Nextdoor Scraper directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt; - create or update &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in your project:&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.apify.com/?tools=actors,docs,eunit/nextdoor-scraper"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt; - add to &lt;code&gt;.vscode/mcp.json&lt;/code&gt; or run &lt;strong&gt;MCP: Add Server&lt;/strong&gt; from the Command Palette:&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;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.apify.com/?tools=actors,docs,eunit/nextdoor-scraper"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude for Desktop&lt;/strong&gt; - edit &lt;code&gt;claude_desktop_config.json&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.apify.com/?tools=actors,docs,eunit/nextdoor-scraper"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;?tools=actors,docs,eunit/nextdoor-scraper&lt;/code&gt; query parameter scopes the tools list to the Nextdoor Scraper Actor specifically, alongside general Actor search and documentation tools. On first connection, a browser OAuth flow opens so you sign in to your Apify account and approve access. No API token is pasted into the chat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the pipeline from a prompt
&lt;/h3&gt;

&lt;p&gt;Once configured, the entire extraction and filtering workflow becomes a single AI prompt. Here is an example of what you would type in Claude or Cursor:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use Apify to run the Nextdoor Scraper on these four city pages: north-miami--fl, bellaire--oh, bellbrook--oh, and belle-center--oh. Once finished, read the dataset results. Filter for any city where percentageHomeowners is 50% or above and averageIncome is $40k or above. For each qualifying city, format a Slack message with the city name, homeowner share, average income, resident count, and affordability score, then post it to #local-leads.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI agent handles the full sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calls &lt;code&gt;call-actor&lt;/code&gt; with the Nextdoor Scraper Actor ID and input configuration.&lt;/li&gt;
&lt;li&gt;Waits for the run to complete and fetches the dataset items.&lt;/li&gt;
&lt;li&gt;Applies your qualification filter logic across every result.&lt;/li&gt;
&lt;li&gt;Formats a structured message for each qualifying neighborhood.&lt;/li&gt;
&lt;li&gt;Posts the alert to Slack using either a connected Slack MCP tool or a Slack bot token you provide in the chat context.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------------+      +----------------------+      +------------------+      +------------------+
| AI Agent         | ---&amp;gt; | Apify MCP server     | ---&amp;gt; | Nextdoor Scraper | ---&amp;gt; | Apify Dataset    |
| (Claude/Cursor)  |      | mcp.apify.com        |      | (Actor run)      |      | (JSON results)   |
| call-actor tool  |      | (OAuth, no token     |      |                  |      |                  |
|                  |      |  in chat)            |      |                  |      |                  |
+------------------+      +----------------------+      +------------------+      +------------------+
         ^                                                                                  |
         |                           reads &amp;amp; filters dataset                               |
         +-----------------------------------------------------------------------------------+
         |
         v
+------------------+
| Slack #local-leads|
| Formatted alert  |
| per qualifying   |
| neighborhood     |
+------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this approach works well for local lead routing
&lt;/h3&gt;

&lt;p&gt;The AI agent approach is especially effective because filtering logic does not need to be written in advance or maintained as code. You adjust the criteria conversationally. If the roofing company decides to lower the homeowner threshold to 45% for a quieter week, you change one line in the prompt. No code deployment required.&lt;/p&gt;

&lt;p&gt;The Apify MCP server also supports &lt;strong&gt;output schema inference&lt;/strong&gt;, meaning Claude or Cursor understands the structure of the Nextdoor Scraper dataset before the run completes. That makes it easier for the agent to write accurate filter logic against fields like &lt;code&gt;percentageHomeowners&lt;/code&gt;, &lt;code&gt;averageIncome&lt;/code&gt;, and &lt;code&gt;residentsCount&lt;/code&gt; without needing to inspect a raw JSON file first.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. What the end result looks like in both paths
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Path A: What the no-code Slack notification looks like
&lt;/h3&gt;

&lt;p&gt;When a Nextdoor Scraper run finishes, the built-in Slack integration posts a notification automatically to your chosen channel. The message includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Actor name and run status&lt;/li&gt;
&lt;li&gt;A direct link to the dataset in Apify Console&lt;/li&gt;
&lt;li&gt;The run duration and the number of items stored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your sales team opens Slack, sees the notification, and clicks through to the dataset to review results. No CSV downloads, no email chains, no delayed handoffs.&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%2Fhh3ajs23i8hwmnexods6.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%2Fhh3ajs23i8hwmnexods6.png" alt="Nextdoor Scraper Actor Succeeded" width="739" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For teams that run the scraper on a schedule (e.g. every Monday morning before the weekly sales meeting), this turns a manual check-in into a reliable, automated briefing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path B: What the AI agent prompt workflow looks like
&lt;/h3&gt;

&lt;p&gt;With the Apify MCP server connected to Claude or Cursor, the agent runs the full pipeline from one prompt. Here is what a real output message from Claude might look like after processing a Nextdoor Scraper dataset with a 50% homeowner filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------------------------+
|  #local-leads                                       [Slack]  |
+--------------------------------------------------------------+
|  🏡 High-intent lead: North Miami, FL                        |
|  Homeowner share: 54%  |  Avg income: $48k                   |
|  Residents: 59,854     |  Affordability: 82/100              |
|  Nextdoor page: https://nextdoor.com/city/north-miami--fl/   |
|                                                              |
|  🏡 High-intent lead: Bellbrook, OH                          |
|  Homeowner share: 61%  |  Avg income: $52k                   |
|  Residents: 7,009      |  Affordability: 91/100              |
|  Nextdoor page: https://nextdoor.com/city/bellbrook--oh/     |
+--------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Lake Rock Ventures pilot, switching from manual CSV review to the Apify MCP server + Claude workflow reduced the time from data collection to campaign briefing from &lt;strong&gt;48 hours to under 10 minutes&lt;/strong&gt;, while narrowing outreach to high-ownership areas only.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Wrapping up
&lt;/h2&gt;

&lt;p&gt;Static data collection is no longer sufficient for modern local marketing and lead generation operations. By replacing manual CSV exports with an automated pipeline powered by &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; and Apify MCP connectors, you can bridge the gap between web data extraction and real-time sales execution.&lt;/p&gt;

&lt;p&gt;With this architecture, you benefit from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hyper-local precision&lt;/strong&gt;: Extracting structured resident counts, homeowner ratios, and income signals from public Nextdoor pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable cost scaling&lt;/strong&gt;: Leveraging Pay-Per-Event pricing to pay strictly for successful record extractions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero glue-code overhead&lt;/strong&gt;: Eliminating third-party integration tools and AWS Lambda scripts by using Apify MCP connectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant lead velocity&lt;/strong&gt;: Routing high-intent local opportunities straight to your sales team's Slack channel in seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready to automate your local lead routing? Try &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; in the Apify Store, connect your Slack workspace via Apify MCP connectors, and start turning public neighborhood signals into real-time sales momentum today.&lt;/p&gt;

</description>
      <category>nexdoorscraping</category>
      <category>apify</category>
      <category>agents</category>
      <category>salesautomation</category>
    </item>
    <item>
      <title>How to download public YouTube videos at scale with an Apify Actor</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Thu, 18 Jun 2026 12:30:13 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-download-public-youtube-videos-at-scale-with-an-apify-actor-4i7b</link>
      <guid>https://dev.to/eunit/how-to-download-public-youtube-videos-at-scale-with-an-apify-actor-4i7b</guid>
      <description>&lt;p&gt;You open a YouTube page. The video is public. You need that video - or its transcript, or its metadata, or just a clean download link you can pass to the next step in your workflow.&lt;/p&gt;

&lt;p&gt;So you search for a YouTube video downloader. You find a browser extension, a sketchy website, maybe a command-line tool. You paste the URL. It works - once. But you have twenty more URLs to process. Or you need to run this same task every Monday morning. Or three people on your team need to do the same thing without using three different methods and getting three different outputs.&lt;/p&gt;

&lt;p&gt;That is where every basic downloader starts to fall apart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/youtube-video-downloader" rel="noopener noreferrer"&gt;YouTube Video Downloader&lt;/a&gt; is an Apify Actor built to solve exactly this problem. It processes public YouTube watch URLs, YouTube Shorts, and live replay links in a structured, repeatable, automatable way. You get download format URLs, video metadata, thumbnails, channel data, and optional transcript text - all in one clean dataset output per run.&lt;/p&gt;

&lt;p&gt;This guide will show you exactly how it works, how to configure it, and why it is a better fit than generic one-off tools if your workflow involves more than one video or one person.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the YouTube Video Downloader Actor does
&lt;/h2&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%2Ff74t6wemgoih8ztjdzk6.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%2Ff74t6wemgoih8ztjdzk6.png" alt="Youtube Video downloader" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most downloader tools treat the task as simple: give them a URL and they give you a file. That is fine if that is truly all you need.&lt;/p&gt;

&lt;p&gt;But most teams need a bit more. They need the title and description to track what they downloaded. They need the thumbnail to display in a content board. They need the transcript to feed into a summarization or search tool. And they need all of this in a format that works with the rest of their stack - not a browser download that lands in someone's desktop folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/youtube-video-downloader" rel="noopener noreferrer"&gt;YouTube Video Downloader&lt;/a&gt; handles all of that in one run. Here is what it can return for each URL you give it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct download URLs for available MP4 and WebM formats&lt;/li&gt;
&lt;li&gt;Progressive (video + audio combined) and adaptive (video-only or audio-only) stream metadata&lt;/li&gt;
&lt;li&gt;Video title, description, tags, and view count&lt;/li&gt;
&lt;li&gt;Channel name and channel ID&lt;/li&gt;
&lt;li&gt;Duration in seconds&lt;/li&gt;
&lt;li&gt;Thumbnail URL (including the high-resolution version when available)&lt;/li&gt;
&lt;li&gt;Media type - regular video, Short, or live replay&lt;/li&gt;
&lt;li&gt;Public transcript text when available and enabled&lt;/li&gt;
&lt;li&gt;A saved file in the run's key-value store when you use file export mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Actor runs on the Apify platform, which means you are not limited to a single manual session. You can run it from Apify Console, call it via API from your own application, schedule it on a recurring basis, or trigger it with a webhook. The output lands in a structured dataset you can export in JSON, CSV, or Excel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who this Actor is actually useful for
&lt;/h2&gt;

&lt;p&gt;Before you decide whether this tool is worth using, it helps to know whether your situation matches how it was designed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content and editorial teams&lt;/strong&gt; are a natural fit. If your team regularly works with public video assets - for clipping, repurposing, archiving, or editorial reference - you likely need more than the video file itself. You need the metadata alongside it. The Actor gives you both in a consistent format, run after run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agencies and operations teams&lt;/strong&gt; benefit most from the repeatability. When different people handle different requests manually, the output is inconsistent. One person uses one site, another uses a different tool. The Actor standardizes the intake: one input format, one output structure, every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers and product teams&lt;/strong&gt; often want a YouTube video downloader API - something they can call from inside their application or automation, not something they have to operate manually. The Actor exposes a full REST API and is also available through the JavaScript and Python client libraries. You can integrate it into any stack in a few lines of code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Researchers and analysts&lt;/strong&gt; who work with transcripts, topic metadata, or structured video data will find the Actor useful for building consistent datasets from public YouTube content. Instead of scraping each video manually, you can pass a list of URLs and get back a single structured export.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why browser-based downloaders are not enough
&lt;/h2&gt;

&lt;p&gt;It is worth being direct about this, because plenty of people reading this article have already tried the obvious alternatives.&lt;/p&gt;

&lt;p&gt;Browser-based YouTube downloader sites work for one-off tasks. You paste a link, download a file, and you are done. But they are built for individual, manual use. They do not give you metadata. They do not store transcripts. They do not support API access. They do not let you schedule a run or trigger one programmatically. And they definitely do not output a structured dataset you can hand to the next step in a pipeline.&lt;/p&gt;

&lt;p&gt;There is also the reliability issue. Many of these sites change frequently, break without warning, or quietly stop working after YouTube updates. If you build a team process around one of them, you are building on unstable ground.&lt;/p&gt;

&lt;p&gt;The YouTube Video Downloader Actor approaches the problem differently. Direct format URLs returned by YouTube are time-limited - they expire. The Actor solves this by giving you two options: use the expiring URL for immediate access, or switch to &lt;code&gt;save-best-progressive&lt;/code&gt; mode and have the Actor store a durable copy in Apify's key-value store. From there, you can fetch it via a stable URL for as long as you need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-step: how to download public YouTube videos with Apify
&lt;/h2&gt;

&lt;p&gt;Here is exactly how to run the Actor and get results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Open the Actor in Apify Console
&lt;/h3&gt;

&lt;p&gt;Go to the &lt;a href="https://apify.com/eunit/youtube-video-downloader" rel="noopener noreferrer"&gt;YouTube Video Downloader&lt;/a&gt; page in Apify Store. If you do not have an Apify account yet, you can sign up for free - no credit card required to start.&lt;/p&gt;

&lt;p&gt;Once you are in, click &lt;strong&gt;Try for free&lt;/strong&gt; to open the Actor in Apify Console. You will land on the &lt;strong&gt;Input&lt;/strong&gt; tab, which is where you configure everything before running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add your YouTube URLs
&lt;/h3&gt;

&lt;p&gt;In the &lt;strong&gt;YouTube video URLs&lt;/strong&gt; field, paste one or more public YouTube links. The Actor supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standard watch links: &lt;code&gt;https://www.youtube.com/watch?v=...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;YouTube Shorts: &lt;code&gt;https://www.youtube.com/shorts/...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Live replay links for completed live streams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can add up to 100 URLs per run (controlled by the &lt;strong&gt;Max videos&lt;/strong&gt; input field, which defaults to 10). If you have a longer list, you can run multiple jobs or call the Actor programmatically in batches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Choose your download mode
&lt;/h3&gt;

&lt;p&gt;This is where the Actor becomes more powerful than a generic downloader. Under &lt;strong&gt;Download mode&lt;/strong&gt;, you have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;metadata-only&lt;/strong&gt; - The Actor returns format URLs, metadata, and optional transcript data. No file is saved. This is the default mode and the fastest way to get structured output. Use this when you only need the download link or metadata to pass downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;save-best-progressive&lt;/strong&gt; - The Actor selects the best available progressive format (video + audio combined), downloads it, and stores the file in the run's default key-value store. You can then fetch it via a stable Apify URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set your preferred quality (&lt;code&gt;best&lt;/code&gt;, &lt;code&gt;720p&lt;/code&gt;, &lt;code&gt;1080p&lt;/code&gt;, etc.) and container (&lt;code&gt;mp4&lt;/code&gt;, &lt;code&gt;webm&lt;/code&gt;, or &lt;code&gt;any&lt;/code&gt;) to guide which format gets selected when saving.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure the rest of your input
&lt;/h3&gt;

&lt;p&gt;A few more fields worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Include transcript&lt;/strong&gt; - Toggle this on if you need the public transcript text. When available, it gets included in the dataset output for each video. Useful for search indexing, summarization, and AI enrichment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include progressive formats&lt;/strong&gt; - Returns combined video + audio formats in the output. On by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include adaptive formats&lt;/strong&gt; - Returns video-only and audio-only streams separately. On by default. Useful if you are working with adaptive streaming workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max formats per video&lt;/strong&gt; - Limits how many format entries appear in the output. Default is 8. Increase this if you want to see the full list of available streams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max saved file size (MB)&lt;/strong&gt; - A safety cap when using &lt;code&gt;save-best-progressive&lt;/code&gt; mode. Defaults to 150 MB. Increase if you are working with longer videos and have confirmed the file will fit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example input that covers most use cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.youtube.com/watch?v=dQw4w9WgXcQ"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.youtube.com/shorts/abc123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxVideos"&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;"preferredQuality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"720p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferredContainer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeProgressiveFormats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeAdaptiveFormats"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"downloadMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"metadata-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeTranscript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxDownloadSizeMb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;150&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;h3&gt;
  
  
  Step 5: Start the run
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Start&lt;/strong&gt; in Apify Console. The Actor processes each URL in sequence, resolves the available formats, collects the metadata, and pushes one item to the dataset for each URL it handles.&lt;/p&gt;

&lt;p&gt;You do not need to keep the browser open. Apify Console shows run status in real time, and you can come back when it is done. For large batches or scheduled runs, you will not be watching at all - the output will just be there when you need it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Review and use the output
&lt;/h3&gt;

&lt;p&gt;Once the run finishes, open the &lt;strong&gt;Dataset&lt;/strong&gt; tab. You will see one row per input URL. Each row has:&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;What it gives you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ready&lt;/code&gt;, &lt;code&gt;downloaded&lt;/code&gt;, or &lt;code&gt;error&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;videoId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The normalized YouTube video ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The video title from YouTube&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mediaType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Video, Short, or live replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;durationSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime in seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;viewCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Public view count at the time of the run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;thumbnailUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High-resolution thumbnail URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;formats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All returned format entries with stream URLs and quality labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transcript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Public transcript text (if enabled and available)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;selectedDownload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key-value store key and public URL of saved file (if applicable)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can export the dataset from the &lt;strong&gt;Export&lt;/strong&gt; button as JSON, CSV, or Excel. If you saved files using &lt;code&gt;save-best-progressive&lt;/code&gt; mode, they appear in the &lt;strong&gt;Storage&lt;/strong&gt; tab under the run's key-value store.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to integrate the Actor into your own code
&lt;/h2&gt;

&lt;p&gt;If you want to call the Actor programmatically - for example, from an internal tool, a script, or a larger automation - the easiest way is through the official Apify client libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript / TypeScript
&lt;/h3&gt;



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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eunit/youtube-video-downloader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;startUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.youtube.com/watch?v=dQw4w9WgXcQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;downloadMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata-only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;includeTranscript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;preferredQuality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;720p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python
&lt;/h3&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_API_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;eunit/youtube-video-downloader&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;startUrls&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;url&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;https://www.youtube.com/watch?v=dQw4w9WgXcQ&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;downloadMode&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;metadata-only&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;includeTranscript&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;preferredQuality&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;720p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the client with &lt;code&gt;pip install apify-client&lt;/code&gt; (Python) or &lt;code&gt;npm install apify-client&lt;/code&gt; (JavaScript/TypeScript). Your API token is in Apify Console under &lt;strong&gt;Settings &amp;gt; Integrations&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on legal and responsible use
&lt;/h2&gt;

&lt;p&gt;Any good guide on YouTube video downloading should address this clearly.&lt;/p&gt;

&lt;p&gt;The YouTube Video Downloader Actor is built for &lt;strong&gt;public YouTube content&lt;/strong&gt; - videos that are publicly visible without login. That does not automatically mean every public video is fair game to download, archive, or redistribute.&lt;/p&gt;

&lt;p&gt;YouTube's terms of service generally prohibit downloading videos without explicit permission from the platform or the content owner. There are exceptions for content you own, content licensed for download, and content in the public domain - but those are the exceptions, not the default.&lt;/p&gt;

&lt;p&gt;If you are asking &lt;strong&gt;how to download YouTube videos legally&lt;/strong&gt;, the clearest answer is: work with content you own, content you have been licensed to use, or content in the public domain. If you are unsure about a specific piece of content, check with a qualified legal advisor, not a downloader tool's FAQ.&lt;/p&gt;

&lt;p&gt;For teams, this matters even more. A repeatable workflow that processes video at scale creates a larger footprint. Good compliance means having clear answers to: what content are we allowed to process, where does the output go, and who approved it.&lt;/p&gt;

&lt;p&gt;The Actor helps you build a consistent, auditable workflow. The permissions question is something you need to answer before you run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common use cases where the Actor earns its cost
&lt;/h2&gt;

&lt;p&gt;If you are comparing this Actor against free browser-based tools, the question is not whether the free tool works for one video. It usually does. The question is what your workflow actually costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content repurposing pipelines.&lt;/strong&gt; If your team regularly turns public video into clips, summaries, or knowledge-base articles, you need the transcript and metadata alongside the media file. The Actor gives you all three in one run, in a structured format that fits your next tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approved asset management.&lt;/strong&gt; Agencies and media teams that manage approved public video assets across campaigns or clients need consistency. The Actor standardizes the output, so every URL processed the same way, by anyone on the team, gives the same fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI and research workflows.&lt;/strong&gt; Transcripts combined with metadata - title, tags, description, view count, duration - are a useful input for classification, topic modeling, or AI summarization pipelines. The Actor gives you a clean starting point for that kind of enrichment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product integration.&lt;/strong&gt; If you are building an internal tool or product feature that involves YouTube video data, the Actor is a faster path than building and maintaining your own downloader infrastructure. You call an API, you get structured output, and you handle the hard part (your product logic) rather than YouTube format resolution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why automation changes the value equation
&lt;/h2&gt;

&lt;p&gt;Here is a way to think about whether the Actor is worth paying for.&lt;/p&gt;

&lt;p&gt;If you download five YouTube videos a month manually, the cost of manual work is low. A free tool is probably fine.&lt;/p&gt;

&lt;p&gt;If you process fifty URLs every week across a team of three people, the math changes. Manual work is time, and time is money. If each URL takes five minutes to process by hand (finding the right tool, pasting the link, downloading the file, renaming it, noting the metadata, passing it to the next step), that is over four hours of manual work per week. At scale, or across a team, that adds up fast.&lt;/p&gt;

&lt;p&gt;The real cost of manual tools is not the tool's price. It is the time spent, the inconsistency in output, and the fragility when the tool breaks or the website changes.&lt;/p&gt;

&lt;p&gt;For teams that process video data regularly, the automation pays for itself quickly. And for teams that need scheduled or API-triggered runs - the kind of workflow that simply cannot be done with a manual tool - the comparison is not really about price at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can this Actor download YouTube Shorts?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The Actor supports public YouTube Shorts URLs. They are handled as a supported media type and included in the dataset output with the &lt;code&gt;mediaType&lt;/code&gt; field set to &lt;code&gt;Short&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it return direct download links?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. When available, the Actor returns direct format URLs in the &lt;code&gt;formats&lt;/code&gt; array. These URLs are time-limited, so use them promptly or switch to &lt;code&gt;save-best-progressive&lt;/code&gt; mode to get a durable stored copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can it save the video file, not just the URL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Set &lt;code&gt;downloadMode&lt;/code&gt; to &lt;code&gt;save-best-progressive&lt;/code&gt; and the Actor will select the best available progressive format, download it, and store it in the run's key-value store. You can then access it via a stable public URL from the &lt;code&gt;selectedDownload&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can it extract transcripts?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, when the video has a public transcript and you enable the &lt;code&gt;includeTranscript&lt;/code&gt; option. Not all videos have public transcripts, and in those cases the Actor returns a structured item without transcript data rather than failing the run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work for live replays?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Completed live streams that are publicly available on YouTube are supported as a media type. The Actor handles them the same way as regular videos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run this on a schedule or trigger it with a webhook?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Scheduled runs and webhook triggers are both available from Apify Console. You can also call the Actor via the REST API or the official client libraries to integrate it into any external system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why use this instead of a free downloader?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because free downloaders are built for one-off, manual, single-user tasks. They do not return structured metadata. They do not support transcripts. They do not give you API access, scheduled runs, or structured exports. And they break without warning. If your workflow involves more than one video, more than one person, or more than a one-time task, a structured Actor on a stable platform is a significantly better fit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;If you have a workflow that involves downloading public YouTube videos - or extracting metadata, transcripts, thumbnails, or stream format data from them - &lt;a href="https://apify.com/eunit/youtube-video-downloader" rel="noopener noreferrer"&gt;YouTube Video Downloader&lt;/a&gt; is built for exactly that.&lt;/p&gt;

&lt;p&gt;You can start with a single public URL, review the dataset output, and see in one run how much cleaner and more useful the output is compared to what you get from a browser-based tool.&lt;/p&gt;

&lt;p&gt;From there, you can scale up to batches, schedule recurring runs, or connect it to your own code using the Apify JavaScript or Python clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/eunit/youtube-video-downloader" rel="noopener noreferrer"&gt;Open YouTube Video Downloader on Apify →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>apify</category>
      <category>youtube</category>
      <category>download</category>
    </item>
    <item>
      <title>How to download Instagram videos and Reels at scale with Apify</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Wed, 17 Jun 2026 11:27:09 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-download-instagram-videos-and-reels-at-scale-with-apify-2ilg</link>
      <guid>https://dev.to/eunit/how-to-download-instagram-videos-and-reels-at-scale-with-apify-2ilg</guid>
      <description>&lt;p&gt;Picture this. Your marketing team just wrapped a three-month creator campaign. Thirty public Reels, a handful of IGTV videos, and dozens of short posts - all published, all public, and all living exclusively on Instagram. Now someone asks: "Can we archive these for the brand library?"&lt;/p&gt;

&lt;p&gt;You open your browser, paste a URL into a random download site, and... nothing. Or worse, you get an ad-laden page with a broken download button. You try four more sites. Two work. One gives you a 240p file. And you've just spent twenty minutes manually processing what should take seconds.&lt;/p&gt;

&lt;p&gt;Sound familiar? This is the reality for anyone managing Instagram video content at even a modest scale. The good news is there's a better way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; is an Apify Actor built specifically for this. It handles public Instagram Reels, &lt;code&gt;/p/&lt;/code&gt; video posts, and IGTV (&lt;code&gt;/tv/&lt;/code&gt;) links in a repeatable, automation-ready workflow - giving you direct video URLs, metadata, thumbnails, and optional stored files, all in clean structured output.&lt;/p&gt;

&lt;p&gt;This article walks you through exactly how it works, who it's for, and how to go from zero to a working download workflow in under ten minutes.&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.amazonaws.com%2Fuploads%2Farticles%2Furjd6jh8l91es82p6f8e.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.amazonaws.com%2Fuploads%2Farticles%2Furjd6jh8l91es82p6f8e.png" alt="Instagram Video and Reel Downloader" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why downloading Instagram videos is harder than it looks
&lt;/h2&gt;

&lt;p&gt;On the surface, saving an Instagram video sounds trivial. The content is public. The URL is right there. How hard can it be?&lt;/p&gt;

&lt;p&gt;Harder than expected, it turns out.&lt;/p&gt;

&lt;p&gt;Instagram doesn't expose direct video file URLs through any official public API. The platform serves video content dynamically from its content delivery network, and the URLs it generates are session-bound, expire quickly, and are deeply embedded in the page's internal data structures. What you see in your browser isn't what a simple HTTP request sees.&lt;/p&gt;

&lt;p&gt;On top of that, Reels in particular behave differently from regular posts. Some pages expose enough data in the static HTML for a fast scraper to pick up the video URL immediately. Others don't - they require a realistic browser session to load the required JavaScript and resolve the actual media delivery request before the video URL becomes visible.&lt;/p&gt;

&lt;p&gt;This is why so many free Instagram downloader websites are inconsistent. They work sometimes, fail silently other times, and give you no real explanation when something goes wrong. For a one-off personal download, that's annoying but acceptable. For a business workflow, it's a real problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; was designed around this reality. It uses a two-path extraction approach: try the fast static route first, and fall back to a browser-based session for harder Reel pages that don't expose the video URL in their initial HTML. The result is a more dependable tool for real public content workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Instagram Video and Reel Downloader actually does
&lt;/h2&gt;

&lt;p&gt;Let's be clear about what this Actor is and isn't.&lt;/p&gt;

&lt;p&gt;It is not a browser extension, not a mobile app, and not a one-click website. It's an &lt;a href="https://apify.com/store?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actor&lt;/a&gt; - a serverless cloud program that accepts structured input, performs a task, and returns structured output. You run it from &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify Console&lt;/a&gt;, call it through the API, or trigger it from an automation. It keeps a record of every run, and you can reuse the same configuration whenever you need it.&lt;/p&gt;

&lt;p&gt;More specifically, for each public Instagram URL you give it, the Actor can return:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a direct &lt;code&gt;videoUrl&lt;/code&gt; pointing to the resolved video file&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;thumbnailUrl&lt;/code&gt; for the post's preview image&lt;/li&gt;
&lt;li&gt;the post &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; when available&lt;/li&gt;
&lt;li&gt;the canonical URL, &lt;code&gt;shortcode&lt;/code&gt;, and &lt;code&gt;mediaType&lt;/code&gt; (&lt;code&gt;reel&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, or &lt;code&gt;tv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;optional file references if you choose to store the actual video or image in Apify's key-value store&lt;/li&gt;
&lt;li&gt;structured error data (with an &lt;code&gt;errorCode&lt;/code&gt; and &lt;code&gt;errorMessage&lt;/code&gt;) if a URL can't be resolved - so the run stays auditable even when something goes wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters more than it might seem. Most free downloader tools just... fail. No error message, no record, no way to know what happened. This Actor keeps a dataset item for every URL it processes, whether it succeeded or not. For teams tracking a batch of URLs, that's a meaningful difference.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ready to try it?&lt;/strong&gt; Open &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; in Apify Console and run it directly in your browser - no code required.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Who actually needs this?
&lt;/h2&gt;

&lt;p&gt;Before getting into the walkthrough, it's worth being honest about who this tool is and isn't for.&lt;/p&gt;

&lt;p&gt;If you occasionally want to save a single Instagram Reel for personal use, a free browser extension or a consumer downloader site will probably do the job. This Actor isn't aimed at that use case.&lt;/p&gt;

&lt;p&gt;Where it earns its place is in repeatable, team-level, or integration-heavy workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Social media managers and content teams&lt;/strong&gt; often need downloadable copies of published Reels - for brand libraries, cross-platform repurposing, or simply keeping a backup of content that lives only on Instagram. Running a batch of URLs through this Actor takes minutes instead of an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agencies&lt;/strong&gt; working with multiple clients or creator rosters regularly need to pull public video assets into approval workflows, reporting decks, or content management systems. A structured JSON output with direct video URLs and thumbnails is far easier to feed into those systems than manually downloading files and uploading them elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers and automation builders&lt;/strong&gt; can use the Actor as a building block. The Apify platform gives you API access, webhooks, and scheduling - so a workflow that starts with "download these public Instagram videos" can chain directly into "save to Google Drive," "send for review," or "push metadata to a spreadsheet."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Researchers and operations teams&lt;/strong&gt; sometimes need video URLs and metadata for tracking or reporting purposes - not necessarily the files themselves. The dataset output covers that without requiring extra storage overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-step: how to use the Actor from Apify Console
&lt;/h2&gt;

&lt;p&gt;You don't need to write a single line of code to get your first result. Here's the full walkthrough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Open the Actor
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;apify.com/eunit/instagram-video-and-reel-downloader&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don't already have an Apify account, you can create one for free. Once you're in, you'll land on the Actor's overview page. Read through the README - it's short and covers the key things to know before you run anything. Then click the &lt;strong&gt;Input&lt;/strong&gt; tab.&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.amazonaws.com%2Fuploads%2Farticles%2F996ppnzixmdblavna3cq.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.amazonaws.com%2Fuploads%2Farticles%2F996ppnzixmdblavna3cq.png" alt="Instagram Video and Reel Downloader on Apify" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Collect your public Instagram URLs
&lt;/h3&gt;

&lt;p&gt;The Actor supports three URL formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/reel/&lt;/code&gt; - standard Instagram Reels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/p/&lt;/code&gt; - regular photo and video posts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/tv/&lt;/code&gt; - IGTV-style long-form video&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copy the public URLs you want to process. The critical word there is &lt;em&gt;public&lt;/em&gt;. This tool doesn't attempt to access private accounts, private posts, or restricted content. If the post isn't publicly viewable without logging in, the Actor will return an error item for that URL rather than silently pretending it worked.&lt;/p&gt;

&lt;p&gt;A quick way to check: open the URL in a private/incognito browser window. If you can see the video without logging in, the Actor can process it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Configure the input
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Input&lt;/strong&gt; tab in Apify Console gives you a form to fill in. Here's what each field does:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;startUrls&lt;/code&gt;&lt;/strong&gt; - This is the only required field. It's the list of Instagram URLs you want the Actor to process. You can add one or fifty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;storeVideoFiles&lt;/code&gt;&lt;/strong&gt; - When this is enabled, the Actor doesn't just resolve the video URL - it also downloads the actual video file and stores it in Apify's key-value store, accessible via a public URL. Useful when you need the binary file, not just a link. Leave it off if the &lt;code&gt;videoUrl&lt;/code&gt; in the dataset is sufficient for your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;storeThumbnailFiles&lt;/code&gt;&lt;/strong&gt; - Same idea, but for the post's thumbnail image. Enable it if you need the actual image file stored alongside the video reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;includePageHtml&lt;/code&gt;&lt;/strong&gt; - Saves the raw Instagram HTML from each fetched page to key-value store. You'll rarely need this, but it's a useful debugging tool if Instagram changes its page structure and the Actor stops resolving video URLs correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;filenamePrefix&lt;/code&gt;&lt;/strong&gt; - If you're storing files, this prefix gets prepended to every stored filename. Something like &lt;code&gt;campaign-june-2026&lt;/code&gt; makes it easy to find related files later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;maxConcurrency&lt;/code&gt;&lt;/strong&gt; - Controls how many pages the Actor processes in parallel. The default of 3 is a reasonable starting point. Keep this lower (1 or 2) if Instagram is rate-limiting your requests; only push it higher if speed is a priority and you're willing to accept some request failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;maxRequestRetries&lt;/code&gt;&lt;/strong&gt; - How many times the Actor retries a failed page request before marking it as an error. The default of 2 is fine for most use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;requestTimeoutSecs&lt;/code&gt;&lt;/strong&gt; - The timeout for individual page fetches and optional file downloads. 30 seconds works well under normal conditions; increase it if you're on a slow connection or downloading large video files into storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;proxyConfiguration&lt;/code&gt;&lt;/strong&gt; - Optional proxy settings. Most users don't need this to start. If you run into consistent request failures or geo-blocking behavior, enabling Apify Proxy here can help distribute requests more effectively.&lt;/p&gt;

&lt;p&gt;A realistic input configuration for a small batch might look like 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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/ABC123xyz/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/p/DEF456uvw/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/GHI789rst/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storeVideoFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storeThumbnailFiles"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includePageHtml"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filenamePrefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"campaign-june-2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxConcurrency"&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;"maxRequestRetries"&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;"requestTimeoutSecs"&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;"proxyConfiguration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"useApifyProxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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.amazonaws.com%2Fuploads%2Farticles%2Frd6wgovr8hnu4rk03jel.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.amazonaws.com%2Fuploads%2Farticles%2Frd6wgovr8hnu4rk03jel.png" alt="Sample Input on Apify Console" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Run the Actor
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Run&lt;/strong&gt; in the top-right corner.&lt;/p&gt;

&lt;p&gt;The Actor starts processing your URLs immediately. For straightforward public posts and video pages, it extracts the video URL and metadata from the static page response - fast, efficient, no browser overhead. For trickier Reel pages that don't expose the video delivery URL in their initial HTML, it falls back to a browser-based path that can capture the actual media requests.&lt;/p&gt;

&lt;p&gt;You don't need to manage any of this yourself. The Actor handles the routing internally.&lt;/p&gt;

&lt;p&gt;You can watch the logs in real time as the Actor runs. Each URL gets processed, and any failures surface immediately with an error code and message rather than a silent skip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Review the dataset output
&lt;/h3&gt;

&lt;p&gt;Once the run completes, click the &lt;strong&gt;Dataset&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;Each URL you submitted produces one dataset item. A successful result looks like 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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/ABC123xyz/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"canonicalUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/ABC123xyz/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shortcode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ABC123xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Instagram video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Caption text from the post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"videoUrl"&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://instagram.fcdn.example/v/t16/f1/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"thumbnailUrl"&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://instagram.fcdn.example/v/t51/thumb.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"videoStoreKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video-campaign-june-2026-abc123xyz.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"videoPublicUrl"&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://api.apify.com/v2/key-value-stores/STORE_ID/records/video-campaign-june-2026-abc123xyz.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uploadDate"&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-05-15T14:30:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fetchedAt"&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-06-17T10:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errorCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errorMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;A failed result is just as structured:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/reel/PRIVATE123/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errorCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NO_VIDEO_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errorMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Could not resolve a public video URL from this page."&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;You can export the dataset in JSON, CSV, Excel, or HTML format - whichever fits your downstream workflow. If you enabled file storage, you can access all stored video and image files directly from the run's &lt;strong&gt;Storage&lt;/strong&gt; tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Put the output to work
&lt;/h3&gt;

&lt;p&gt;This is where the Actor starts earning its value beyond a simple file download.&lt;/p&gt;

&lt;p&gt;If your workflow is "download these URLs occasionally and review them manually," exporting the dataset as CSV and opening it in a spreadsheet is enough.&lt;/p&gt;

&lt;p&gt;If your workflow is more continuous - weekly campaign archives, automated content pipelines, or team-level review processes - the Apify platform gives you several paths to build on this run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API calls:&lt;/strong&gt; Trigger the Actor from any script using the Apify JavaScript or Python client, or a direct HTTP request. The output is the same structured dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedules:&lt;/strong&gt; Run the Actor on a weekly or daily cadence with a fresh list of URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks:&lt;/strong&gt; Fire a webhook when the run completes to trigger downstream steps in Make, Zapier, n8n, or your own backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server:&lt;/strong&gt; If you're building AI workflows, the Actor is available as a tool through Apify's Model Context Protocol (MCP) server.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Integrating the Actor with code
&lt;/h2&gt;

&lt;p&gt;If you're a developer or want to trigger the Actor programmatically, the setup is straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript / TypeScript:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;startUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.instagram.com/reel/ABC123xyz/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;storeVideoFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;filenamePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-archive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eunit/instagram-video-and-reel-downloader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Video URL:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python:&lt;/strong&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;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_API_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_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;startUrls&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;url&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;https://www.instagram.com/reel/ABC123xyz/&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;storeVideoFiles&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;filenamePrefix&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;my-archive&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;eunit/instagram-video-and-reel-downloader&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ready&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;Video URL:&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;videoUrl&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;Install the client with &lt;code&gt;npm install apify-client&lt;/code&gt; (JavaScript) or &lt;code&gt;pip install apify-client&lt;/code&gt; (Python), then drop in your Apify API token from Apify Console under &lt;strong&gt;Settings &amp;gt; Integrations&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When it makes sense to pay for a reliable Actor
&lt;/h2&gt;

&lt;p&gt;Let's address the obvious question: why pay for this when free downloader sites exist?&lt;/p&gt;

&lt;p&gt;The honest answer is that free downloader sites are built for individual, one-off use. They're not designed to process batches, return structured data, integrate with APIs, or recover gracefully when Instagram changes something. They also come with ad loads, pop-ups, and opaque data practices that you probably don't want in a business workflow.&lt;/p&gt;

&lt;p&gt;This Actor uses a pay-per-event model at $90.00 per 1,000 processed videos. That pricing structure means you pay for actual completed work - not a flat subscription that bills you whether you use it or not.&lt;/p&gt;

&lt;p&gt;For context: if a marketing manager spends 30 minutes manually downloading and organizing 10 Instagram videos per week, that's roughly 26 hours of manual work per year. The same batch run through this Actor costs under $1 and completes in a few seconds.&lt;/p&gt;

&lt;p&gt;The economics shift further when you factor in reliability. A workflow that fails halfway through a batch, returns corrupted files, or gives you no error visibility isn't really "free" - it's just deferring the cost into your time and frustration budget.&lt;/p&gt;

&lt;p&gt;Paying customers here are buying dependability, structured output, and the ability to build workflows that run the same way every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few things to keep in mind
&lt;/h2&gt;

&lt;p&gt;This Actor is built for public Instagram content, and it's worth being clear about what that means in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private content won't work.&lt;/strong&gt; If a post requires you to be logged in to view it, the Actor can't access it, and it will return an error item for that URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instagram can and does change things.&lt;/strong&gt; The page structure, video delivery logic, and anti-bot behavior can shift with Instagram updates. When that happens, the extraction may stop working for some URL types until the Actor is updated. The structured error output means you'll know which URLs failed rather than getting a silent empty result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're responsible for compliance.&lt;/strong&gt; Only download content you own, have explicit permission to use, or have a legitimate business reason to process. Respect copyright, platform terms, and local laws. The tool does what it's designed to do - how you use it is your responsibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Most Instagram video download tools are consumer utilities. They're built for "save this one Reel right now" - not for "process this batch of public videos, give me structured output, and let me automate it next week too."&lt;/p&gt;

&lt;p&gt;If your work touches Instagram video content at any real scale - campaign archives, content pipelines, creator workflows, or automated reporting - that distinction matters a lot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Instagram Video and Reel Downloader&lt;/a&gt; is built for the second use case. It gives you direct video URLs, structured metadata, optional file storage, a reliable fallback for difficult Reel pages, and a platform underneath it that supports APIs, scheduling, webhooks, and integrations.&lt;/p&gt;

&lt;p&gt;Try it from Apify Console with a few public URLs. The first run takes about few seconds to set up, and you'll see immediately whether it fits the workflow you're building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/instagram-video-and-reel-downloader" rel="noopener noreferrer"&gt;Get started with Instagram Video and Reel Downloader on Apify →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>instagram</category>
      <category>api</category>
      <category>apify</category>
      <category>video</category>
    </item>
    <item>
      <title>How to scrape Nextdoor for hyper-local demographics and community sentiment</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Sat, 30 May 2026 07:05:14 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-scrape-nextdoor-for-hyper-local-demographics-and-community-sentiment-2cbg</link>
      <guid>https://dev.to/eunit/how-to-scrape-nextdoor-for-hyper-local-demographics-and-community-sentiment-2cbg</guid>
      <description>&lt;p&gt;Neighborhood-level data is where many high-value business decisions start. Citywide averages are useful, but they often hide the details that matter when you are choosing where to advertise, where to invest, or how to understand what local communities care about. If you want to find areas with strong homeownership, identify places where residents discuss common service needs, or analyze how people describe a neighborhood in public community pages, you need hyper-local signals.&lt;/p&gt;

&lt;p&gt;That is exactly why so many teams look for a reliable way to scrape Nextdoor. The challenge is that &lt;a href="https://ctexaminer.com/2026/03/27/nextdoors-closed-door-policy/" rel="noopener noreferrer"&gt;Nextdoor is a relatively closed ecosystem&lt;/a&gt;. It does not offer an easy, public, developer-friendly path for extracting the kind of structured data marketers, analysts, founders, and researchers want. Manual collection is slow, inconsistent, and impossible to scale across hundreds of locations.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;&lt;strong&gt;Nextdoor Scraper&lt;/strong&gt;&lt;/a&gt; helps. Built as one of the &lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actors&lt;/a&gt; for the Apify platform, it turns public Nextdoor city-page data into structured output you can use. Instead of copying information by hand, you can collect geographic coordinates, resident counts, income indicators, homeowner ratios, affordability signals, FAQ content, and local community references in a repeatable workflow. If you need a practical Nextdoor API alternative for public pages, this is the faster path.&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.amazonaws.com%2Fuploads%2Farticles%2Fk3jzl3ho7vzvn5pzdzxa.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.amazonaws.com%2Fuploads%2Farticles%2Fk3jzl3ho7vzvn5pzdzxa.png" alt="Nextdoor logo" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Nextdoor Scraper?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/nextdoor-scraper?fpr=eunit" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; is a scraping and data extraction Actor for collecting structured data from public Nextdoor pages, especially city pages. It is designed for people who want usable output, not raw HTML. That means you get cleaned fields that can go straight into a spreadsheet, dashboard, CRM, enrichment pipeline, or reporting workflow.&lt;/p&gt;

&lt;p&gt;In simple terms, the &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; visits public Nextdoor pages, extracts the visible and structured page data, and returns it in machine-readable formats such as JSON, XLSX, and CSV. That makes it easier to analyze neighborhood-level trends at scale without building your own browser automation stack from scratch.&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%2Fi.postimg.cc%2FdtTwsSDL%2Fscreenshot-2026-05-29-at-15-36-11.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%2Fi.postimg.cc%2FdtTwsSDL%2Fscreenshot-2026-05-29-at-15-36-11.png" alt="Aberdeen, Ohio - Nextdoor page" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The extracted fields are especially useful for local market analysis. Depending on the public page and the available structured data, the Actor can capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Geographic coordinates, such as a centroid with latitude and longitude&lt;/li&gt;
&lt;li&gt;City and state details&lt;/li&gt;
&lt;li&gt;Resident counts&lt;/li&gt;
&lt;li&gt;Average age&lt;/li&gt;
&lt;li&gt;Average income&lt;/li&gt;
&lt;li&gt;Percentage of homeowners&lt;/li&gt;
&lt;li&gt;Affordability and friendliness scores&lt;/li&gt;
&lt;li&gt;FAQ entries&lt;/li&gt;
&lt;li&gt;Neighborhood references&lt;/li&gt;
&lt;li&gt;Business directory references&lt;/li&gt;
&lt;li&gt;Local groups and additional community resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a public city page can expose useful signals such as a centroid of &lt;code&gt;40.016, -80.747&lt;/code&gt;, a resident count of &lt;code&gt;3,878&lt;/code&gt;, an average age of &lt;code&gt;35&lt;/code&gt;, an average income of &lt;code&gt;$41k&lt;/code&gt;, and a homeowner share of &lt;code&gt;56%&lt;/code&gt;. It can also include livability indicators such as an affordability score of &lt;code&gt;99&lt;/code&gt; and a friendliness score of &lt;code&gt;86&lt;/code&gt;. Those values are much more actionable than a plain list of URLs because they help you compare locations quickly and prioritize where to spend time and budget.&lt;/p&gt;

&lt;p&gt;The result is a practical way to scrape Nextdoor data for research, local service lead generation, geographic analysis, and community sentiment discovery, while staying focused on public information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Top 4 profitable use cases for Nextdoor data
&lt;/h2&gt;

&lt;p&gt;The biggest reason to use a Nextdoor scraper is not the scraping itself. It is what the data helps you decide, launch, test, or sell afterward. Here are four high-value ways to use hyper-local demographics and community sentiment from public Nextdoor pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Building regional contractor and reputation platforms
&lt;/h3&gt;

&lt;p&gt;If you are building a &lt;a href="https://demandium.app/blog/best-home-service-marketplaces-platform/" rel="noopener noreferrer"&gt;regional directory, a home services marketplace, or a contractor reputation platform&lt;/a&gt;, you need more than a list of businesses. You need neighborhood context.&lt;/p&gt;

&lt;p&gt;Public Nextdoor data helps you map cities and surrounding local structures in a way that feels more grounded in how residents actually organize their communities. The FAQs, neighborhood references, and local groups can help you understand how people describe an area, what kinds of issues come up repeatedly, and which categories of services matter most.&lt;/p&gt;

&lt;p&gt;That is valuable when you are building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A local contractor comparison site&lt;/li&gt;
&lt;li&gt;A regional reputation database&lt;/li&gt;
&lt;li&gt;A neighborhood services recommendation engine&lt;/li&gt;
&lt;li&gt;A local SEO content network for service categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of launching broad, generic pages, you can prioritize neighborhoods with meaningful public signals and create location-aware service experiences. That makes your product more relevant, and it can improve both engagement and conversion.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lead generation for home services
&lt;/h3&gt;

&lt;p&gt;This is one of the clearest commercial use cases. Home service businesses such as roofers, landscapers, &lt;a href="https://blog.apify.com/plumbing-leads/" rel="noopener noreferrer"&gt;plumbers&lt;/a&gt;, painters, pest control companies, and HVAC providers need to know where homeowners live, where disposable income is high, and where local demand is likely to justify the cost of outreach. &lt;a href="https://apify.com/eunit/nextdoor-scraper/api/python" rel="noopener noreferrer"&gt;Nextdoor city-page data&lt;/a&gt; can help you build exactly that picture.&lt;/p&gt;

&lt;p&gt;For example, if a location shows &lt;code&gt;3,878&lt;/code&gt; residents, an average income of &lt;code&gt;$41k&lt;/code&gt;, and &lt;code&gt;56%&lt;/code&gt; homeowners, that is immediately more useful than running a generic campaign across an entire metro area. It gives you a better basis for deciding where to target direct mail, paid social, local landing pages, sales outreach, and neighborhood-specific offers.&lt;/p&gt;

&lt;p&gt;You can use the data to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find areas with stronger homeowner density&lt;/li&gt;
&lt;li&gt;Compare income signals across nearby cities&lt;/li&gt;
&lt;li&gt;Prioritize communities for seasonal campaigns&lt;/li&gt;
&lt;li&gt;Match marketing messages to neighborhood concerns surfaced in FAQs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A landscaping company may care about homeowner-heavy areas. A roofing business may want to focus on suburban communities with stable ownership and strong local trust signals. A cleaning or handyman service may use local groups and neighborhood references to decide where to pilot a new offer. That is the real advantage of scraping Nextdoor for lead generation. You are getting closer to the local context behind the customer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real estate investment and market research
&lt;/h3&gt;

&lt;p&gt;Real estate teams already use public data from many sources, but the hyper-local context is often fragmented. That is where public Nextdoor data becomes interesting.&lt;/p&gt;

&lt;p&gt;When a city page includes livability indicators such as an affordability score of &lt;code&gt;99&lt;/code&gt; and a friendliness score of &lt;code&gt;86&lt;/code&gt;, you gain another layer of context for evaluating a market. These values do not replace formal due diligence, but they can help analysts screen locations faster before moving into deeper research.&lt;/p&gt;

&lt;p&gt;A real estate investor, brokerage, or proptech company can use the data to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compare neighborhoods and cities at a glance&lt;/li&gt;
&lt;li&gt;Identify areas that look affordable relative to nearby markets&lt;/li&gt;
&lt;li&gt;Spot locations with strong community-oriented signals&lt;/li&gt;
&lt;li&gt;Enrich internal market reports with public local context&lt;/li&gt;
&lt;li&gt;Create lead magnets and market snapshots for clients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Geographic coordinates also matter. A centroid such as &lt;code&gt;40.016, -80.747&lt;/code&gt; makes it easier to map results, visualize city clusters, and combine the output with other location-based datasets. That is useful for market scoring, territory planning, and neighborhood-level dashboards.&lt;/p&gt;

&lt;p&gt;If you are producing local real estate data products, public Nextdoor scraping can work as a lightweight enrichment layer that adds community context to your pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Community sentiment and trend analysis
&lt;/h3&gt;

&lt;p&gt;Community sentiment is often hard to quantify because it spans many channels. Nextdoor is valuable because it is built around neighborhood-level discussion, recommendations, local concerns, and community interaction.&lt;/p&gt;

&lt;p&gt;Even when you focus only on public pages, FAQ sections, local groups, business directory references, and related resources can still reveal what a place emphasizes. That helps you understand whether a community leans toward family life, local commerce, safety, neighborhood organization, pet activity, home improvement, or other recurring themes.&lt;/p&gt;

&lt;p&gt;This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local market researchers&lt;/li&gt;
&lt;li&gt;Urban policy teams&lt;/li&gt;
&lt;li&gt;Media analysts&lt;/li&gt;
&lt;li&gt;Economic development groups&lt;/li&gt;
&lt;li&gt;Brands planning geo-targeted campaigns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if you see repeated references to neighborhood groups, practical local services, and community FAQs, that can point to a highly engaged local audience. If business directories are prominent, it may suggest strong local commercial activity. If certain topic clusters appear across multiple nearby pages, you may have found a regional trend worth tracking over time. In other words, the value is not just in scraping Nextdoor data once. It is in building a repeatable view of how communities present themselves publicly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features of the Nextdoor Scraper
&lt;/h2&gt;

&lt;p&gt;The appeal of &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor scraper&lt;/a&gt; is that it is not just a one-off script. It is packaged as one of the &lt;a href="https://apify.com/store?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actors&lt;/a&gt;, so it fits into a repeatable workflow on the Apify platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed and scale
&lt;/h3&gt;

&lt;p&gt;Instead of manually opening pages and copying details into a spreadsheet, you can run automated extraction across many public URLs in one job. That saves hours of repetitive work and makes side-by-side comparison possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured output for analysis
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor scraper&lt;/a&gt; returns normalized data fields, not just raw page content. That makes it easier to filter, group, sort, enrich, and export your results for business use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser-based extraction on the Apify platform
&lt;/h3&gt;

&lt;p&gt;Nextdoor pages are modern web pages, so browser automation matters. The &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor scraper&lt;/a&gt; handles the browser-driven extraction flow for you on the &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify platform&lt;/a&gt;, which is much easier than maintaining your own extraction stack and local tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost-effective automation
&lt;/h3&gt;

&lt;p&gt;Building and maintaining your own pipeline for public-page collection can get expensive fast. Using &lt;a href="https://apify.com/store?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actors&lt;/a&gt; reduces engineering overhead, shortens time to value, and gives you a practical way to pay for execution instead of building everything yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export formats that fit real workflows
&lt;/h3&gt;

&lt;p&gt;Once the data is collected, you can work with it in formats your team already uses. JSON is ideal for developers and downstream automation. CSV is ideal for analysts, operations teams, and quick spreadsheet reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step tutorial: how to scrape Nextdoor data
&lt;/h2&gt;

&lt;p&gt;Using Nextdoor Scraper on the Apify platform is straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Access the Actor
&lt;/h3&gt;

&lt;p&gt;Open &lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;Nextdoor Scraper&lt;/strong&gt;&lt;/a&gt; in Apify Store. On the Actor page, click &lt;strong&gt;Try for free&lt;/strong&gt; or &lt;strong&gt;Start&lt;/strong&gt; to open the input form in Apify Console.&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%2Fi.postimg.cc%2FT3X14Ycs%2FNextdoor-Apify.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%2Fi.postimg.cc%2FT3X14Ycs%2FNextdoor-Apify.png" alt="Nextdoor Scraper" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Configure the target URLs
&lt;/h3&gt;

&lt;p&gt;In the &lt;strong&gt;Input&lt;/strong&gt; tab, add one or more public Nextdoor URLs. A common starting point is a city page, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/north-miami--fl/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxRequestsPerCrawl"&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;"maxConcurrency"&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;"maxItemsPerSection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.postimg.cc%2F9FNHnWjh%2Fscreenshot-2026-05-30-at-07-34-21.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%2Fi.postimg.cc%2F9FNHnWjh%2Fscreenshot-2026-05-30-at-07-34-21.png" alt="Nextdoor Scraper - Input configuration" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Run the Actor
&lt;/h3&gt;

&lt;p&gt;Click the &lt;strong&gt;Start&lt;/strong&gt; button at the bottom of the page. The Actor initializes, processes the target pages, extracts structured data fields, and stores the results in a dataset.&lt;/p&gt;

&lt;p&gt;Depending on the page, the output can include fields such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cityName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;state&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;centroid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;residentsCount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;averageAge&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;averageIncome&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;percentageHomeowners&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;affordabilityScore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;friendlinessScore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;faqs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localGroups&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Export the dataset
&lt;/h3&gt;

&lt;p&gt;Once the run finishes, open the &lt;strong&gt;Storage&lt;/strong&gt; tab to inspect the extracted records. You can then export the dataset in the format that best fits your workflow.&lt;/p&gt;

&lt;p&gt;Here is a simplified example of the kind of output you can expect:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nextdoor.com/city/example-city/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cityName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example City"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"centroid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.016&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-80.747&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"residentsCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3878&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"averageAge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"averageIncome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$41k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"percentageHomeowners"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"affordabilityScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"friendlinessScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"faqCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accessStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can download the results as JSON or CSV and move directly into analysis, enrichment, reporting, or outreach planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Nextdoor data into your workflows
&lt;/h2&gt;

&lt;p&gt;The value of scraped Nextdoor data increases when it flows into the tools your team already uses.&lt;/p&gt;

&lt;p&gt;On the Apify platform, you can connect dataset outputs to automations and downstream systems. That includes sending results to Google Sheets for review, using Make for no-code workflows, or routing data into Zapier for notifications and follow-up actions. You can also plug the JSON output into your own analytics stack, internal dashboards, CRMs, or lead scoring systems.&lt;/p&gt;

&lt;p&gt;This is especially helpful when you are monitoring multiple cities over time. Instead of exporting data manually after each run, you can make Nextdoor scraping into a repeatable process that supports lead generation, market tracking, and local intelligence workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is scraping Nextdoor legal?
&lt;/h2&gt;

&lt;p&gt;Scraping should always be done responsibly. The safest approach is to focus on publicly available data, respect applicable rules and terms, and avoid collecting private or personal information behind login walls.&lt;/p&gt;

&lt;p&gt;Nextdoor Scraper is designed around public page extraction. That means the workflow is intended for public city-page data and other publicly accessible content, not private profiles, direct messages, or restricted user data. You should also review your own legal obligations, compliance needs, and usage policies before running large-scale data collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you want to scrape Nextdoor for hyper-local demographics, local service lead generation, neighborhood research, or community sentiment analysis, structured public-page extraction gives you a strong starting point. Instead of relying on manual research, you can collect repeatable location data such as resident counts, income signals, homeowner ratios, affordability indicators, friendliness scores, FAQs, and local group references in a format your team can actually use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/nextdoor-scraper" rel="noopener noreferrer"&gt;Nextdoor Scraper&lt;/a&gt; turns that process into a scalable workflow on the Apify platform. Open the Actor in Apify Store, run your first extraction, and start building better local market insights today.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>leadgen</category>
      <category>realestate</category>
    </item>
    <item>
      <title>How to get YouTube data for a growth strategy: video and channel analysis</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Thu, 09 Apr 2026 17:02:04 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-get-youtube-data-for-a-growth-strategy-video-and-channel-analysis-7a1</link>
      <guid>https://dev.to/eunit/how-to-get-youtube-data-for-a-growth-strategy-video-and-channel-analysis-7a1</guid>
      <description>&lt;p&gt;YouTube has more than 2 billion logged-in users every month and &lt;a href="https://www.statista.com/statistics/259477/hours-of-video-uploaded-to-youtube-every-minute/?srsltid=AfmBOopPcf0RFGQdL-oJ74szr6mWMInPT0Y3cQK4pdSRPbcfAxq1B_Wj" rel="noopener noreferrer"&gt;processes over 500 hours of uploaded video every minute&lt;/a&gt;. It is the second-largest search engine in the world. Yet the majority of creators and brands still build their content strategy the old-fashioned way: watching what competitors post, guessing which topics will resonate, and hoping the algorithm cooperates.&lt;/p&gt;

&lt;p&gt;That approach works early on. But as your niche becomes more competitive and your publishing schedule more demanding, manual research stops scaling. You can't audit 20 competitor channels by hand every week, track which video formats are trending across five countries simultaneously, and still have time to actually make content.&lt;/p&gt;

&lt;p&gt;What changes everything is structured data. When you can collect trending video metrics, popular channel signals, and keyword data programmatically - across countries, categories, and time - YouTube growth becomes a repeatable system rather than a creative gamble.&lt;/p&gt;

&lt;p&gt;This article shows you how to obtain that data using two purpose-built &lt;a href="https://apify.com/store?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actors&lt;/a&gt;, and how to turn it into a growth strategy grounded in what the platform is actually rewarding right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why YouTube data matters for growth strategy
&lt;/h2&gt;

&lt;p&gt;YouTube operates on two distinct engines: search and recommendations. Search surfaces content in response to specific queries. Recommendations push content to viewers who weren't actively searching - but whose watch history signals a relevant interest. A strong growth strategy needs both, and both require different types of data.&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.amazonaws.com%2Fuploads%2Farticles%2Focnns11gey0acp3fqy03.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.amazonaws.com%2Fuploads%2Farticles%2Focnns11gey0acp3fqy03.png" alt="YouTube data for a growth strategy: video and channel analysis" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data that matters most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trending videos by category and country&lt;/strong&gt; - what the recommendation algorithm is actively amplifying, which formats and topics are getting pushed to new audiences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popular channels and their recent content&lt;/strong&gt; - which creators the platform is currently favoring, and what they're publishing right now&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popular keywords&lt;/strong&gt; - what viewers are actively searching for at this moment, not six months ago&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engagement metrics (views, likes, comments)&lt;/strong&gt; - signals of content-audience fit that go deeper than raw view counts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official YouTube Data API was not designed for this kind of research. Its quota limits cap how much data you can pull per day, it has no trending channels endpoint, and accessing granular trend data by country and category requires complex workarounds that hit limits quickly. For recurring competitive research, the API becomes an obstacle rather than a tool.&lt;/p&gt;

&lt;p&gt;Purpose-built scraping Actors solve this. The &lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt; and &lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt; extract exactly what you need - structured, export-ready data - without API keys, quota management, or engineering overhead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want a step-by-step scraping walkthrough before diving into strategy, read &lt;a href="https://www.eunit.me/blog/how-to-scrape-youtube-trends-and-popular-channels" rel="noopener noreferrer"&gt;How to scrape YouTube trends and popular channels&lt;/a&gt; first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The growth data you can collect and why it matters
&lt;/h2&gt;

&lt;p&gt;Before looking at how to use the data, it helps to understand exactly what each Actor returns and which growth question each data point answers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data point&lt;/th&gt;
&lt;th&gt;Source Actor&lt;/th&gt;
&lt;th&gt;Growth use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trending video rank, title, views, likes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Spot viral formats and topics before your competitors do&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;published_time_text&lt;/code&gt; (e.g., "Yesterday", "3 hours ago")&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Identify how quickly content trends and optimal publishing windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video thumbnail URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Analyze thumbnail patterns that correlate with high click-through rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel name, recent videos, video URLs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Identify which channels are dominating your niche right now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Popular keywords&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Discover high-intent search terms for SEO and content ideation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Category breakdown per country&lt;/td&gt;
&lt;td&gt;Both Actors&lt;/td&gt;
&lt;td&gt;Understand regional audience preferences for localized content strategies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engagement ratio (likes / views)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Filter for content that converts viewers, not just attracts them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together, these two Actors give you a full picture view of what YouTube is amplifying in your space - the videos rising to the top and the channels being pushed to new audiences.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using trending video data for content strategy
&lt;/h2&gt;

&lt;p&gt;Trending videos are the algorithm's endorsement. They represent the exact combination of topic, format, title structure, and production style that YouTube is choosing to amplify in a given country and category at a specific moment. Analyzing these patterns systematically - rather than browsing trending pages manually - reveals what's working before it becomes obvious.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to collect trending video data
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt; on Apify. Create a free account if you don't have one.&lt;/li&gt;
&lt;li&gt;Select your target &lt;strong&gt;Country&lt;/strong&gt; (e.g., &lt;code&gt;united-states&lt;/code&gt;, &lt;code&gt;united-kingdom&lt;/code&gt;, &lt;code&gt;india&lt;/code&gt;, &lt;code&gt;brazil&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Select your &lt;strong&gt;Category&lt;/strong&gt; (e.g., &lt;code&gt;gaming&lt;/code&gt;, &lt;code&gt;music&lt;/code&gt;, &lt;code&gt;howto-and-style&lt;/code&gt;, &lt;code&gt;science-and-technology&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save &amp;amp; Start&lt;/strong&gt;. The run completes in seconds.&lt;/li&gt;
&lt;li&gt;Preview results in the table view or export as JSON, CSV, or Excel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each result includes: rank, video title, video URL, thumbnail URL, author (channel name), &lt;code&gt;published_time_text&lt;/code&gt;, views, likes, and comments.&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="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rank"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"video_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Marvel Television's Wonder Man | Official Trailer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"video_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://youtube.com/watch?v=wHuWmjXsReU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Marvel Entertainment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"published_time_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yesterday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"views"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5.2M"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"likes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"113K"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.9K"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&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;"united-kingdom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"All"&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;Run this Actor across multiple countries and categories, and you have a competitive intelligence layer that updates every time you trigger it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Competitor channel analysis: who's winning and why
&lt;/h2&gt;

&lt;p&gt;Channel analysis is where most creators focus - but most do it wrong. They look at subscriber counts. A channel with 800k subscribers might not have posted in two months and could be algorithmically invisible. The more useful signal is which channels are &lt;em&gt;currently appearing as popular&lt;/em&gt; in trending data - because that reflects what YouTube is actively recommending to new viewers right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why popular channel data beats subscriber counts
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt; returns the channels YouTube is currently surfacing as "buzzing" in a given country and category, along with their most recent videos. This is fundamentally different from a ranked list of the biggest channels. A channel with 80k subscribers appearing here is actively growing. A channel with 2 million subscribers that doesn't appear has lost algorithmic favor.&lt;/p&gt;

&lt;p&gt;This distinction matters enormously for competitor research. Your real YouTube competitors are not necessarily your biggest industry rivals - they're the channels competing for the same viewer attention and winning right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to analyze in popular channel data
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Publishing frequency and recency.&lt;/strong&gt; How many videos has each trending channel posted in the last 30 days? Channels maintaining a consistent 2-3 videos per week in trending data signal that the algorithm rewards their cadence. That's a publishing frequency benchmark, not just an aspiration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Topic and title patterns.&lt;/strong&gt; Across a channel's recent trending videos, which topics repeat? If a competitor's recent 6 trending videos are all about personal finance for under-30s, that sub-niche is clearly working for their audience. Look for the intersection between their most successful topics and gaps in your own content calendar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-country channel overlap.&lt;/strong&gt; Run the Actor for the same category across the US, UK, and Australia. Channels appearing in all three are building genuine algorithmic momentum across English-speaking markets. These are the channels worth monitoring most closely.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to collect popular channel data
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fp4i5zqnj7knkrr0jux4m.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.amazonaws.com%2Fuploads%2Farticles%2Fp4i5zqnj7knkrr0jux4m.png" alt="YouTube Popular Channels Scraper" width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt; on Apify.&lt;/li&gt;
&lt;li&gt;Select your &lt;strong&gt;Country&lt;/strong&gt; and &lt;strong&gt;Category&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save &amp;amp; Start&lt;/strong&gt;. Results include channel names, recent video URLs, and popular keywords for the selected market.&lt;/li&gt;
&lt;li&gt;Export and repeat for 3-5 target countries.&lt;/li&gt;
&lt;li&gt;Build a simple tracking sheet: channel name, countries trending in, recent topic themes, and estimated posting frequency. Update it weekly.
&lt;/li&gt;
&lt;/ol&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="err"&gt;&amp;nbsp;&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;"united-kingdom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&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;h2&gt;
  
  
  Building a multi-country growth intelligence system
&lt;/h2&gt;

&lt;p&gt;A one-off data pull gives you a snapshot. A repeatable system gives you a competitive advantage that compounds over time. Here's how to build one without a development team.&lt;/p&gt;

&lt;h3&gt;
  
  
  The architecture
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Collect&lt;/strong&gt; - run both Actors weekly across your target countries (3-5) and category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store&lt;/strong&gt; - export results to Google Sheets or Airtable with a date column so you can track changes over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyze&lt;/strong&gt; - compare week-over-week: are the same channels trending, or new ones emerging? Are keywords shifting? Are engagement ratios moving up or down?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt; - translate the top 3 insights into your next 2 weeks of content: one trending topic, one underserved format, one keyword to target&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scheduling automatic runs
&lt;/h3&gt;

&lt;p&gt;From Apify Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure your Actor run (country + category)&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save as a new task&lt;/strong&gt; and give it a name (e.g., "US Gaming Trends - Weekly")&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Schedules&lt;/strong&gt; in the left navigation and click &lt;strong&gt;Create new schedule&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add your task and set the frequency - weekly is a good starting cadence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No code required. The Actor runs automatically, and new results are appended to the dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  For developers: automate with Python
&lt;/h3&gt;

&lt;p&gt;If you're building a dashboard, feeding a machine learning model, or integrating this data into a larger pipeline, the &lt;code&gt;apify-client&lt;/code&gt; makes automation straightforward.&lt;/p&gt;

&lt;p&gt;Install the client:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Run the YouTube Popular Channels Scraper programmatically:&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_API_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_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&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;united-states&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&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;gaming&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;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;eunit/youtube-popular-channels-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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;eunit/youtube-popular-channels-scraper&lt;/code&gt; for &lt;code&gt;eunit/youtube-trending-videos-by-categories&lt;/code&gt; to pull trending video data instead. The input schema is identical. You can run both Actors in sequence and merge the outputs into a single structured dataset for a complete weekly intelligence snapshot.&lt;/p&gt;

&lt;p&gt;Your API token is in &lt;strong&gt;Settings &amp;gt; API &amp;amp; Integrations&lt;/strong&gt; in Apify Console.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pricing: what this costs in practice
&lt;/h2&gt;

&lt;p&gt;Both Actors use a Pay-Per-Event pricing model. You are charged a fixed amount per result successfully scraped - not for platform compute time or server usage.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Actor&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;from $1.00 / 1,000 results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;from $1.90 / 1,000 results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice, running a weekly trend scan across 5 countries with 1 category each returns approximately 200-300 data points - costing under $0.30 per run, or roughly $1.20/month.&lt;/p&gt;

&lt;p&gt;Traditional SaaS YouTube analytics platforms typically charge between $49 and $299/month for dashboards with less granular, less current data than you get from a direct scrape. The Pay-Per-Event model means you pay exactly in proportion to how much data you need. Scale up for a product launch. Scale down during quiet weeks. No subscriptions, no idle spend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Legal and ethical considerations
&lt;/h2&gt;

&lt;p&gt;Both Actors collect publicly available data - information that any user can see by visiting YouTube without logging in. They do not access private videos, user passwords, private messages, or any content that requires authentication.&lt;/p&gt;

&lt;p&gt;Scraping public data for research, competitive intelligence, and informational purposes is generally considered acceptable in most jurisdictions. Operate within YouTube's Terms of Service, use the data for your own analysis, and avoid any use cases that involve republishing scraped content without appropriate attribution.&lt;/p&gt;

&lt;p&gt;For Apify's broader guidance on ethical web scraping, see the &lt;a href="https://docs.apify.com" rel="noopener noreferrer"&gt;Apify documentation&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;YouTube growth stops being a guessing game when you treat it like a data problem. Trending videos tell you what the algorithm is amplifying right now. Popular channel data shows you who it's amplifying and why. Engagement ratios reveal which content is building real audience relationships versus passive viewership. And popular keywords hand you the exact search terms your potential viewers are using today.&lt;/p&gt;

&lt;p&gt;The growth loop becomes repeatable: collect data weekly, identify the 2-3 highest-signal patterns, build your next content batch around them, and measure whether your own engagement metrics are converging with trending benchmarks.&lt;/p&gt;

&lt;p&gt;The best channels in every niche are already doing this. The ones that aren't are still guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start collecting:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt; - from $1.00 / 1,000 results&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apify.com/eunit/youtube-popular-channels-scraper" rel="noopener noreferrer"&gt;YouTube Popular Channels Scraper&lt;/a&gt; - from $1.90 / 1,000 results&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I get YouTube trending data without the official API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The &lt;a href="https://apify.com/eunit/youtube-trending-videos-by-categories" rel="noopener noreferrer"&gt;YouTube Trending Videos by Categories Scraper&lt;/a&gt; extracts real-time trending data across 25+ countries and 13 categories with no API key required and no quota limits to manage. You get the same data you'd see by browsing YouTube's trending pages, in a structured, export-ready format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is "popular channel" data different from subscriber count?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Popular channel data reflects which channels YouTube is actively recommending to new viewers right now. A channel with 80k subscribers can appear here because it's publishing consistently and gaining algorithmic momentum. A channel with 2 million subscribers that hasn't posted recently won't. For competitive intelligence, current algorithmic favor matters far more than historical growth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What categories can I track?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both Actors support: All, Music, Gaming, Film &amp;amp; Animation, Autos &amp;amp; Vehicles, Sports, Pets &amp;amp; Animals, Comedy, Entertainment, News &amp;amp; Politics, How-to &amp;amp; Style, and Science &amp;amp; Technology - across 25+ countries including the US, UK, India, Brazil, Japan, Germany, Australia, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How often should I run the scrapers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Weekly is sufficient for most content strategies. Daily runs are useful during product launches, campaigns, or for fast-moving niches like Gaming or News &amp;amp; Politics where trends shift within hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use this data for sentiment analysis?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Engagement ratios (likes-to-views, comment volume relative to views) are a reliable sentiment proxy at scale and require no comment scraping. For comment-level sentiment - what viewers are actually saying - pair these Actors with a YouTube Comments Scraper and feed the output to an LLM for summarization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is YouTube scraping legal?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scraping publicly available YouTube data for research and informational purposes is generally legal in most jurisdictions. These Actors only collect data visible to any visitor without logging in. Always use scraped data in accordance with YouTube's Terms of Service and applicable privacy laws.&lt;/p&gt;

</description>
      <category>youtube</category>
      <category>seo</category>
      <category>contentstrategy</category>
      <category>datascraping</category>
    </item>
    <item>
      <title>How to get Twitter (X) data for sentiment analysis</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Fri, 03 Apr 2026 14:40:48 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-get-twitter-x-data-for-sentiment-analysis-4c2h</link>
      <guid>https://dev.to/eunit/how-to-get-twitter-x-data-for-sentiment-analysis-4c2h</guid>
      <description>&lt;p&gt;When a product fails publicly, Twitter (X) is usually the first place it shows up. Not the press. Not review sites. Twitter. Within hours, a single product flaw can spawn thousands of tweets, drive a hashtag into the top ten trends in three countries, and permanently shift how a brand is perceived.&lt;/p&gt;

&lt;p&gt;That's why researchers, brand managers, journalists, and quantitative analysts all want the same thing: reliable Twitter data, fast, and at scale.&lt;/p&gt;

&lt;p&gt;The problem is that getting that data has never been harder or more expensive. Twitter's own Application Programming Interface (API) now costs anywhere from $100 to $5,000 per month, and even then, it doesn't give you clean, structured trend data. Do-it-yourself scrapers require proxies, constant maintenance, and a tolerance for early-morning debugging sessions when Twitter changes its UI without warning.&lt;/p&gt;

&lt;p&gt;This article is about a smarter path. You'll learn what kinds of Twitter data are most useful for sentiment analysis, how the main collection methods compare, and how to get structured, real-time Twitter trend data using the &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; on Apify - in under five minutes, with no setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Twitter sentiment analysis?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/blog/sentiment-analysis-twitter" rel="noopener noreferrer"&gt;Sentiment analysis&lt;/a&gt; is the process of using natural language processing (NLP) or large language models (LLMs) to automatically classify the emotional tone behind a piece of text - typically as positive, negative, or neutral. When applied to Twitter, it turns raw social data into measurable public opinion signals.&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.amazonaws.com%2Fuploads%2Farticles%2Faty7738uyf9my2llqnpd.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.amazonaws.com%2Fuploads%2Farticles%2Faty7738uyf9my2llqnpd.png" alt="Twitter (X) data for sentiment analysis" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twitter (X) is uniquely valuable for this because people say what they actually think there. Unlike LinkedIn, where professional image management dominates, or product review sites, where feedback is structured and often incentivized, Twitter is where unfiltered reactions occur in real-time. A product launch, a policy announcement, a celebrity controversy - they all land on Twitter first.&lt;/p&gt;

&lt;p&gt;Two distinct types of Twitter data matter for sentiment analysis:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tweet content&lt;/strong&gt; - the actual text of individual tweets, replies, quote-tweets, and threads posted about a topic, keyword, or brand. This is what most sentiment analysis tutorials focus on. You scrape tweets, run them through a classifier, and obtain a sentiment label for each tweet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trending topic data&lt;/strong&gt; - the ranked list of hashtags and topics that are dominating conversation in a specific location at a specific point in time. This includes the trend rank, name, tweet count, and hourly history. I wrote an article on how to get &lt;a href="https://www.eunit.me/blog/how-to-scrape-twitter-x-trends-without-breaking-the-bank" rel="noopener noreferrer"&gt;Twitter (X) Trends Without Breaking the Bank&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Why trend data is an underrated input for sentiment analysis
&lt;/h2&gt;

&lt;p&gt;Here's a question most sentiment analysis tutorials skip: what if you don't know which topic to analyze yet?&lt;/p&gt;

&lt;p&gt;Tweet-level sentiment analysis works well when you already have a clear target - a brand name, a product, a specific hashtag. But trend data answers a different and often more valuable question: &lt;em&gt;What are people talking about right now, and how intense is that conversation?&lt;/em&gt; That intensity is itself a sentiment signal.&lt;/p&gt;

&lt;p&gt;Consider this: &lt;code&gt;#ProductX&lt;/code&gt; is trending in São Paulo with 45k tweets and rising. You don't need to read a single tweet to know something significant is happening. The trend data alone indicate that a significant number of people are discussing this topic simultaneously, which almost always reflects a strong emotional reaction - positive or negative.&lt;/p&gt;

&lt;p&gt;Here's what trend data actually reveals:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tweet count as a proxy for emotional intensity.&lt;/strong&gt; High tweet counts on a trending topic consistently correlate with strong emotional reactions. A hashtag with 80k tweets is generating far more sentiment "energy" than one with 2k. That count becomes your first filter for what's worth analyzing in depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hourly trend history reveals sentiment momentum.&lt;/strong&gt; The &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; captures trends across hourly snapshots, not just the current moment. A trend that appears at rank 40 with 5k tweets and climbs to rank 3 with 50k tweets in two hours signals something going viral. A trend that hits rank 1 and then rapidly falls often signals a controversy that burned hot and fast. These patterns are distinct and actionable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Location-based trends reveal geographic sentiment clusters.&lt;/strong&gt; A consumer product campaign trending in New York but not in London, or a political hashtag dominating Lagos but absent in Nairobi, gives you geographic precision that tweet-level analysis rarely provides without significant pre-processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical use cases where trend data drives sentiment decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brand monitoring:&lt;/strong&gt; Catch a PR crisis developing before it peaks, while you still have time to respond.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product launch tracking:&lt;/strong&gt; See whether your campaign is gaining traction geographically, not just globally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor intelligence:&lt;/strong&gt; Know the moment your competitor's brand starts trending - and with what intensity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Academic research:&lt;/strong&gt; Track regional public opinion on political events, elections, or policy announcements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content strategy:&lt;/strong&gt; Identify topics with proven high engagement before committing creative resources to them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what does an effective data collection strategy actually look like?&lt;/p&gt;




&lt;h2&gt;
  
  
  Methods for getting Twitter (X) data
&lt;/h2&gt;

&lt;p&gt;There are three realistic options for collecting Twitter data at scale. Here's an honest comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  The official X API
&lt;/h3&gt;

&lt;p&gt;The X API is Twitter's own data access layer. It gives you structured, compliant access to tweets, user profiles, and some trend data via REST and streaming endpoints.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.eunit.me/blog/how-to-scrape-twitter-x-trends-without-breaking-the-bank" rel="noopener noreferrer"&gt;The problem is the pricing model.&lt;/a&gt; The free tier is limited to 1,500 tweets per month for write-only access - virtually useless for sentiment analysis. The Basic plan costs &lt;code&gt;$100.00&lt;/code&gt; per month and gives you limited read access. The Pro plan, which provides meaningful volume, costs &lt;code&gt;$5,000.00&lt;/code&gt; per month. Enterprise pricing isn't even listed publicly.&lt;/p&gt;

&lt;p&gt;For trend data specifically, the API provides access to the &lt;code&gt;trends/place&lt;/code&gt; endpoint, but it's been progressively restricted alongside the broader API changes since 2023.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Enterprise teams with compliance requirements who need guaranteed terms-of-service alignment and have the budget to justify it.&lt;/p&gt;

&lt;h3&gt;
  
  
  DIY web scraping
&lt;/h3&gt;

&lt;p&gt;You could write your own scraper using tools like Selenium or Playwright to automate a browser, log in to Twitter, and extract trend data from the web interface.&lt;/p&gt;

&lt;p&gt;The appeal is obvious: potentially free and fully flexible. The reality is less appealing. &lt;a href="https://www.eunit.me/blog/how-to-scrape-twitter-x-trends-without-breaking-the-bank#:~:text=Obfuscated%20and%20Dynamic%20DOM:%20X,flagged%20and%20throttled%20into%20oblivion." rel="noopener noreferrer"&gt;Twitter's front-end changes frequently, and every UI update risks breaking your scraper&lt;/a&gt;. To avoid IP blocks, you'll need rotating residential proxies, which can cost hundreds of dollars per month on their own. And all of that assumes you have the time and technical skill to build and maintain the scraper indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers who treat scraper maintenance as a learning exercise rather than a cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ready-made Apify Actors
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://apify.com/store?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actors&lt;/a&gt; are pre-built, cloud-hosted scrapers maintained by their developers and hosted on the &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify platform&lt;/a&gt;. They handle proxy management, authentication avoidance, output formatting, and cloud execution - so you don't have to.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; is exactly this kind of tool. It was built specifically to extract real-time trending topic data from Twitter, supports granular location targeting across hundreds of countries and cities, and requires zero configuration beyond selecting your location.&lt;/p&gt;

&lt;p&gt;Operating on a &lt;strong&gt;pay-per-event&lt;/strong&gt; model (starting from $1.00 per 1,000 results), it is a fraction of the cost of the Pro API plan - and delivers trend data the API doesn't even expose cleanly. You only pay for the successful scrapes you actually perform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Researchers, marketers, and developers who need reliable data without infrastructure overhead or expensive monthly subscriptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  At a glance: method comparison
&lt;/h3&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;Official X API&lt;/th&gt;
&lt;th&gt;DIY scraping&lt;/th&gt;
&lt;th&gt;Apify Actor (PPE)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$100–$5,000+/month&lt;/td&gt;
&lt;td&gt;"Free" + proxy costs&lt;/td&gt;
&lt;td&gt;From $1.00 per 1,000 results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trend data quality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited, restricted since 2023&lt;/td&gt;
&lt;td&gt;Variable (breaks on UI changes)&lt;/td&gt;
&lt;td&gt;Structured, real-time, hourly history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (API keys, OAuth, rate limits)&lt;/td&gt;
&lt;td&gt;Very high (proxies, parsing, maintenance)&lt;/td&gt;
&lt;td&gt;None (click to run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance burden&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (API is stable until deprecated)&lt;/td&gt;
&lt;td&gt;High (breaks on any UI update)&lt;/td&gt;
&lt;td&gt;None (maintained by developer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Location targeting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Country-level only&lt;/td&gt;
&lt;td&gt;Depends on implementation&lt;/td&gt;
&lt;td&gt;300+ countries and cities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structured output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (JSON)&lt;/td&gt;
&lt;td&gt;Manual (requires parsing)&lt;/td&gt;
&lt;td&gt;Yes (JSON, CSV, Excel, Sheets)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ToS compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Fully compliant&lt;/td&gt;
&lt;td&gt;⚠️ Gray area&lt;/td&gt;
&lt;td&gt;✅ Public data only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise compliance-first teams&lt;/td&gt;
&lt;td&gt;Developers who enjoy scraper maintenance&lt;/td&gt;
&lt;td&gt;Researchers, marketers, and developers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How to use the Twitter (X) Trends Scraper (PPE) to get data for sentiment analysis
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; extracts real-time trending topics from Twitter (X) for any country or city in its location list. It captures trend rank, hashtag names, tweet counts, direct search links, and a rolling hourly history - making it directly useful as a sentiment intensity tracker rather than just a static snapshot tool.&lt;/p&gt;

&lt;p&gt;Here's how to get your first dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Access the Actor
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://apify.com/store" rel="noopener noreferrer"&gt;Apify Store&lt;/a&gt; and search for "Twitter (X) Trends Scraper (PPE)", or navigate directly to &lt;code&gt;apify.com/eunit/x-twitter-trends-ppe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you don't already have an Apify account, sign up for free. No credit card is required to start the trial. Once on the Actor page, click &lt;strong&gt;Try for free&lt;/strong&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2Fwfjh59yg8j3t3gi4wiqi.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.amazonaws.com%2Fuploads%2Farticles%2Fwfjh59yg8j3t3gi4wiqi.png" alt="Twitter (X) Trends Scraper (PPE) on Apify" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Configure your input
&lt;/h3&gt;

&lt;p&gt;The input form has a single field: &lt;strong&gt;Country / City&lt;/strong&gt;. This simplicity is intentional - the Actor is designed to return complete trend data for your selected location in one run.&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.amazonaws.com%2Fuploads%2Farticles%2F9nev13l7tl04wzq3v1y8.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.amazonaws.com%2Fuploads%2Farticles%2F9nev13l7tl04wzq3v1y8.png" alt="Configure input for Twitter (X) Trends Scraper (PPE)" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dropdown supports a wide range of locations, from top-level country selections like &lt;code&gt;Worldwide&lt;/code&gt; or &lt;code&gt;united-kingdom&lt;/code&gt;, to city-level targeting such as &lt;code&gt;united-states/new-york&lt;/code&gt;, &lt;code&gt;nigeria/lagos&lt;/code&gt;, or &lt;code&gt;india/mumbai&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For sentiment analysis purposes, location selection strategy matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're monitoring a brand or product, start with &lt;code&gt;Worldwide&lt;/code&gt; to establish whether the topic is trending globally.&lt;/li&gt;
&lt;li&gt;Then re-run with specific market locations (e.g., &lt;code&gt;united-states&lt;/code&gt;, &lt;code&gt;united-kingdom&lt;/code&gt;, &lt;code&gt;australia&lt;/code&gt;) to understand where the conversation is concentrated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The equivalent JSON input looks like 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="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;"united-states/new-york"&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;You can also pass this input directly via the API or Apify client libraries, which is covered in the automation section below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Run and inspect the output
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Run&lt;/strong&gt;. The Actor typically completes in seconds and returns a structured JSON dataset. Here's what each key field contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;scraped_at&lt;/code&gt;&lt;/strong&gt; - the UTC timestamp of the run. Critical for tracking data freshness and building time-series datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;country_input&lt;/code&gt;&lt;/strong&gt; - the location string you submitted, useful for multi-location runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timeline&lt;/code&gt;&lt;/strong&gt; - an array of hourly snapshots, each containing up to 50 ranked trends. Each trend includes its &lt;code&gt;rank&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; (the hashtag or topic), &lt;code&gt;tweet_count&lt;/code&gt;, and a direct search &lt;code&gt;link&lt;/code&gt;. This is the richest output for sentiment analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tag_cloud&lt;/code&gt;&lt;/strong&gt; - a broader set of trending terms for that location, useful for topic discovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;table_data&lt;/code&gt;&lt;/strong&gt; - the current top 50 ranked trends in a flat table format, ideal for quick dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A sample of the &lt;code&gt;timeline&lt;/code&gt; output:&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;"scraped_at"&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-04-03T10:30:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country_input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"united-states/new-york"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"datetime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2 minutes ago"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1743672600"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"trends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"rank"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#TechLayoffs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://twitter.com/search?q=%23TechLayoffs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tweet_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"82K"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"rank"&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;"name"&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 Giants"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"link"&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://twitter.com/search?q=%22New+York+Giants%22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tweet_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14K"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Export and use the data
&lt;/h3&gt;

&lt;p&gt;Once the run completes, Apify Console gives you several export options directly from the dataset view: JSON, CSV, Excel, XML, and a direct Google Sheets integration.&lt;/p&gt;

&lt;p&gt;For sentiment analysis pipelines, JSON or CSV are the most practical formats. JSON preserves the nested &lt;code&gt;timeline&lt;/code&gt; structure (essential if you're tracking momentum across hourly snapshots), while CSV flattens the data for easy import into tools like Excel, Google Sheets, or pandas.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For a fully automated pipeline - where you scrape trends, then instantly analyze the sentiment of trending topics without writing any code - connect this Actor with &lt;a href="https://apify.com/dusan.vystrcil/llm-dataset-processor" rel="noopener noreferrer"&gt;LLM Dataset Processor&lt;/a&gt; directly in Apify Console. Point the LLM processor at your trend dataset, define a prompt like &lt;code&gt;"Classify the likely sentiment associated with this trending hashtag as Positive, Negative, or Neutral based on context"&lt;/code&gt;, and export the enriched results to Google Sheets automatically.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  For developers: automating the pipeline
&lt;/h2&gt;

&lt;p&gt;If you want to integrate live Twitter trend data into your own application or run it on a schedule, Apify provides client libraries for Python and JavaScript that make this straightforward.&lt;/p&gt;

&lt;p&gt;First, install the Python client:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then run the Actor programmatically:&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="c1"&gt;# Initialize the client with your Apify API token
&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;&amp;lt;YOUR_API_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define your input
&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;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;united-states/new-york&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="c1"&gt;# Run the Actor and wait for completion
&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;eunit/x-twitter-trends-ppe&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="c1"&gt;# Fetch and process the results
&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&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="s"&gt;Trends for: &lt;/span&gt;&lt;span class="si"&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;country_input&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;trend&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;timeline&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;trends&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="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;trend&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="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;trend&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;trend&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tweet_count&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; tweets&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;For JavaScript/Node.js projects, the pattern is identical using the &lt;code&gt;apify-client&lt;/code&gt; npm package:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR_API_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eunit/x-twitter-trends-ppe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;united-states/new-york&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeline&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="nx"&gt;trends&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trend&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tweet_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scheduling for longitudinal datasets.&lt;/strong&gt; The real power for sentiment analysis is in running the Actor on a regular cadence - say, every hour - and storing the results in a database or data warehouse. This builds a time-series dataset of trend rank and tweet count, which lets you track sentiment momentum over hours, days, or weeks. Apify's built-in scheduler handles this without external cron jobs or infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent integration.&lt;/strong&gt; If you're building an AI agent that needs to be aware of current social trends, the Actor supports Model Context Protocol (MCP) server integration. Your agent can query live trend data and use it as context for downstream reasoning tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is it legal to scrape Twitter (X) trends?
&lt;/h2&gt;

&lt;p&gt;This is a fair question, and it deserves a direct answer.&lt;/p&gt;

&lt;p&gt;The Twitter (X) Trends Scraper (PPE) collects publicly available, factual data - specifically the list of trending hashtags and topics that Twitter displays to any unauthenticated visitor. It does not scrape private profiles, direct messages, content behind a login wall, or any personally identifiable information.&lt;/p&gt;

&lt;p&gt;Scraping publicly available factual data is generally considered legal in most jurisdictions. The landmark &lt;em&gt;hiQ Labs v. LinkedIn&lt;/em&gt; case in the United States established that scraping publicly available data does not constitute a violation of the Computer Fraud and Abuse Act (CFAA). Trend data - hashtag names, rankings, and aggregate tweet counts - is about as factual and public as data gets.&lt;/p&gt;

&lt;p&gt;That said, you should always review the Terms of Service of any platform you interact with, and ensure your use case complies with applicable regulations. The General Data Protection Regulation (GDPR) and California Consumer Privacy Act (CCPA) govern &lt;em&gt;personal data&lt;/em&gt;, not aggregated metadata like trend rankings and hashtag names. As long as you're collecting and using trend data responsibly, you're on solid ground.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The hardest part of any Twitter sentiment analysis project isn't the analysis. It's getting clean, reliable, structured data to analyze. The official API is prohibitively expensive for most teams. DIY scrapers are fragile and require time-consuming maintenance. And most sentiment analysis tutorials skip past the data collection problem entirely.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; fills that gap directly. It gives you real-time Twitter trend data - ranked hashtags, tweet counts, hourly history, and location-level granularity - in a structured format that plugs directly into your sentiment pipeline, whether that's a Python NLP script, an LLM-powered classifier, or a no-code Apify workflow.&lt;/p&gt;

&lt;p&gt;The free trial requires no credit card. A single run takes about ten seconds. And the data you get back can be the difference between catching a PR crisis before it peaks and reading about it in the Monday morning report.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Try the Twitter (X) Trends Scraper (PPE) on Apify&lt;/a&gt; and start turning Twitter's real-time noise into a clear, actionable signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you get data from Twitter (X) for sentiment analysis?
&lt;/h3&gt;

&lt;p&gt;The three main options are the official X API (expensive, limited trend data), custom web scrapers (fragile, requires proxy infrastructure), and ready-made tools like Apify Actors. The &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; is the most practical option for sentiment-focused use cases, as it returns structured trend data, including tweet counts and hourly history with zero configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best tool for scraping Twitter trends in 2026?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/x-twitter-trends-ppe" rel="noopener noreferrer"&gt;Twitter (X) Trends Scraper (PPE)&lt;/a&gt; on Apify is the most practical option for most use cases. It supports granular location data for hundreds of countries and cities, requires no proxy setup, and uses a fair &lt;strong&gt;pay-per-event&lt;/strong&gt; pricing model (from $1.00 / 1,000 results) - a fraction of the cost of the official API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it legal to scrape Twitter (X) for sentiment analysis data?
&lt;/h3&gt;

&lt;p&gt;Scraping publicly available trend data is generally legal. Trend rankings and hashtag names are factual, public-facing data, not personal information subject to GDPR or CCPA. However, you should always review the platform's Terms of Service and ensure your use case complies with local regulations.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost to get Twitter data for sentiment analysis?
&lt;/h3&gt;

&lt;p&gt;The official X API starts at $100 per month for basic access and $5,000 per month for professional volume. The Twitter (X) Trends Scraper (PPE) on Apify operates on a pay-per-event basis, meaning you only pay for the data you successfully scrape. If you need tweet-level data in addition to trends, additional Actors on the Apify Store are available, each with their own pricing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I get historical Twitter trend data?
&lt;/h3&gt;

&lt;p&gt;Twitter does not provide historical trend data through its official API. The Twitter (X) Trends Scraper (PPE) captures trends at the time of each run, including the hourly timeline snapshot that Twitter maintains for the current period. To build a historical dataset, schedule the Actor to run at regular intervals (e.g., hourly) and store each run's output in your own database. Apify Console also retains your run history and datasets for later access.&lt;/p&gt;

</description>
      <category>sentimentanalysis</category>
      <category>twitter</category>
      <category>datascaper</category>
    </item>
    <item>
      <title>How to scrape TikTok search results: A complete guide for 2026</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Sat, 21 Mar 2026 14:58:52 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-scrape-tiktok-search-results-a-complete-guide-for-2026-17nm</link>
      <guid>https://dev.to/eunit/how-to-scrape-tiktok-search-results-a-complete-guide-for-2026-17nm</guid>
      <description>&lt;h1&gt;
  
  
  How to scrape TikTok search results: A complete guide for 2026
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.tiktok.com/" rel="noopener noreferrer"&gt;TikTok&lt;/a&gt; &lt;a href="https://blog.alandotchin.com/tiktoks-global-influence-redefining-social-media-and-culture/#:~:text=virality%20and%20discoverability.-,Cultural%20and%20Generational%20Influence,find%20the%20same%20platform%20elsewhere." rel="noopener noreferrer"&gt;has transformed from a simple video-sharing app into a global cultural engine&lt;/a&gt;. With over 1 billion active users, the platform dictates what we listen to, what we buy, and how we communicate. For businesses, marketers, and researchers, TikTok is not just entertainment; it is a repository of real-time consumer sentiment and trend data.&lt;/p&gt;

&lt;p&gt;However, getting that data is a top challenge as &lt;a href="https://www.tiktok.com/business/en/blog/how-tiktok-protects-users-from-spam-and-inauthentic-behavior" rel="noopener noreferrer"&gt;TikTok employs some of the most sophisticated anti-scraping technologies in the world&lt;/a&gt;. In this article, we will explore why TikTok data is essential, the technical barriers to getting it, and how you can use the &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; to automate your data collection at scale.&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.amazonaws.com%2Fuploads%2Farticles%2Fq6ns2jq8uivj6cg69prk.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.amazonaws.com%2Fuploads%2Farticles%2Fq6ns2jq8uivj6cg69prk.png" alt="How to scrape TikTok search results: A complete guide for 2026 " width="600" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The importance of TikTok data in modern marketing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.qualtrics.com/articles/strategy-research/market-research-guide/" rel="noopener noreferrer"&gt;In the past, brands relied on slow-moving market research surveys&lt;/a&gt;. Today, they rely on &lt;a href="https://oxgital.com/how-nigerian-brands-can-use-tiktok-for-viral-growth/" rel="noopener noreferrer"&gt;TikTok in 2026 as a primary growth engine&lt;/a&gt;, utilizing authentic, low-polish content, behind-the-scenes storytelling, and trending sounds to drive viral engagement. With over 5 billion daily users, it is crucial for building brand loyalty and reaching audiences who spend a significant amount of time on social media. The speed at which a trend moves from a single video to a global phenomenon is unprecedented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trend tracking and cultural shifts
&lt;/h3&gt;

&lt;p&gt;By scraping TikTok search results, you can identify emerging trends before they hit the mainstream. For example, a search for "sustainable fashion" might reveal specific local hashtags or challenges that are gaining momentum. Tracking these shifts allows you to adjust your content strategy in real-time, ensuring your brand remains relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audience behavior and sentiment analysis
&lt;/h3&gt;

&lt;p&gt;Comments, view counts, and video descriptions provide insight into how users feel about specific topics. Analyzing these data points helps you understand the language your target audience uses, their pain points, and what kind of content they find engaging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Competitor and influencer benchmarking
&lt;/h3&gt;

&lt;p&gt;Who are the top creators in your niche? What music are they using? How often do they post? You can answer all these questions by scraping search results for relevant keywords. This data helps you benchmark your performance against competitors and identify potential influencers for future collaborations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key aspects of brand reliance on TikTok
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authenticity over production&lt;/strong&gt;: Brands, particularly in fashion and beauty, succeed by using smartphone cameras to show raw "before and after" transformations rather than polished ads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Driving growth&lt;/strong&gt;: Small businesses in sectors like fashion, fintech, and beauty use TikTok as their primary marketing channel to reach new audiences.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Immersion &amp;amp; trends&lt;/strong&gt;: Successful brands like Sandro Paris and Yahoo create immersive, trend-driven content to stay relevant in pop culture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Massive reach &amp;amp; user-generated content (UGC)&lt;/strong&gt;: Major brands like ESPN hold millions of followers, while companies increasingly rely on UGC to build trust.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strategic outsourcing&lt;/strong&gt;: To keep up with the pace of trends, many companies outsource their TikTok creative to specialists&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why traditional scraping methods fail
&lt;/h2&gt;

&lt;p&gt;If you have tried to build a simple Python script to scrape TikTok, you have likely encountered immediate blocks or endless captchas. TikTok's security infrastructure is designed to distinguish between automated bots and real human users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic content and JavaScript heavy lifting
&lt;/h3&gt;

&lt;p&gt;TikTok is a single-page application (SPA) that heavily relies on JavaScript to load content. A basic HTTP request will return an empty shell of a page. To get actual video data, you need a browser-based scraper that can render the DOM and handle infinite scrolling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced bot detection
&lt;/h3&gt;

&lt;p&gt;TikTok &lt;a href="https://www.tiktok.com/privacy/blog/how-we-combat-scraping/en" rel="noopener noreferrer"&gt;checks for several technical signals&lt;/a&gt; to identify automation, among which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Navigator.webdriver&lt;/strong&gt;: Many automated browsers leave this flag set to &lt;code&gt;true&lt;/code&gt;, making them easy to spot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fingerprinting&lt;/strong&gt;: Systems analyze your browser's font list, canvas rendering, and hardware identifiers to see if they match a standard device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Hints&lt;/strong&gt;: Modern anti-bot systems check &lt;strong&gt;sec-ch-ua&lt;/strong&gt; headers. If your User-Agent indicates that you are on Chrome 123 but your headers say something else, you will be blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter IP blocking&lt;/strong&gt;: TikTok blocks entire IP ranges belonging to cloud providers (AWS, GCP, Apify, etc.) at the CDN level before your browser fingerprint is even evaluated. A request arriving from a datacenter IP receives an empty page shell regardless of how convincing the browser looks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The solution: TikTok Search Scraper
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; is a professional-grade &lt;a href="https://www.apify.com?fpr=eunit" rel="noopener noreferrer"&gt;Apify Actor&lt;/a&gt; designed to bypass these barriers. It does not just "read" the page; it interacts with it as a human would.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key features for maximum resilience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human-like behavior&lt;/strong&gt;: The scraper mimics real user interaction. It does not just jump to the bottom of the page; it performs smooth, variable-speed scrolling. It even "pauses" to simulate a user watching a video, which significantly reduces the risk of triggering behavioral blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser profile rotation&lt;/strong&gt;: Every run can use a different browser profile. These profiles include curated User-Agents, realistic viewports, and synchronized Client Hints. This makes each request appear to be coming from a unique, legitimate device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stealth technology&lt;/strong&gt;: By leveraging the &lt;strong&gt;stealth plugin&lt;/strong&gt;, &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;the Actor&lt;/a&gt; masks its automated nature, overriding the &lt;code&gt;navigator.webdriver&lt;/code&gt; property and other common detection vectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep fingerprint evasion&lt;/strong&gt;: Beyond the stealth plugin, each session generates a Bayesian browser fingerprint (canvas noise, WebGL vendor/renderer, AudioContext noise, WebRTC IP leak prevention) that is unique per run and consistent with the selected browser profile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session cookie injection&lt;/strong&gt;: You can supply real cookies exported from your own browser directly into the actor's input. TikTok's scoring system grants significantly higher trust to sessions that arrive with authentic tokens like &lt;code&gt;msToken&lt;/code&gt; and &lt;code&gt;ttwid&lt;/code&gt; — the same ones a real browser accumulates after visiting TikTok normally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom proxy URL&lt;/strong&gt;: Instead of purchasing residential proxies, you can route the actor's traffic through your own machine. This makes TikTok see your home IP address rather than a cloud datacenter IP, which is often the single most effective unblock technique.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic cookie persistence&lt;/strong&gt;: After every successful run, the actor saves the live TikTok session cookies to Apify's storage. The next run automatically reloads them, so the session trust level builds up over time without any manual action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rehydration script fallback&lt;/strong&gt;: TikTok stores its initial data in a JSON blob called a rehydration script. If the DOM structure changes or is temporarily hidden, &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;the Actor&lt;/a&gt; can pull data directly from this script, ensuring high reliability even during platform updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scraping TikTok without residential proxies
&lt;/h2&gt;

&lt;p&gt;Residential proxies work reliably, but they come at a cost. The &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; now includes two built-in techniques that let you bypass TikTok's datacenter block without purchasing a residential proxy plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technique 1: Session cookie injection
&lt;/h3&gt;

&lt;p&gt;When you browse TikTok in your regular Chrome or Firefox browser, TikTok sets a collection of session cookies on your machine — most importantly &lt;code&gt;msToken&lt;/code&gt;, &lt;code&gt;ttwid&lt;/code&gt;, and &lt;code&gt;s_v_web_id&lt;/code&gt;. These tokens tell TikTok that the request is coming from a real, established browser session. When you inject them into the actor, TikTok extends that same trust to the automated run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to export your cookies:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the free &lt;strong&gt;&lt;a href="https://cookie-editor.com/" rel="noopener noreferrer"&gt;Cookie-Editor&lt;/a&gt;&lt;/strong&gt; extension for Chrome or Firefox.&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://www.tiktok.com" rel="noopener noreferrer"&gt;https://www.tiktok.com&lt;/a&gt; and browse normally for about 30 seconds — scroll the feed, watch a video.&lt;/li&gt;
&lt;li&gt;Click the Cookie-Editor icon in your browser toolbar.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Export&lt;/strong&gt; → &lt;strong&gt;Export as JSON&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Copy the entire JSON array that appears.&lt;/li&gt;
&lt;li&gt;Paste it into the &lt;strong&gt;Session Cookies&lt;/strong&gt; field in the actor's input on Apify.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The actor normalizes the cookie format automatically, strips any extension-specific metadata, and injects the cookies before any request is sent. It also skips the homepage warm-up visit so that the higher-trust tokens you provided are not overwritten by datacenter-seeded ones.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Cookies expire — typically within a few hours to a few days. If scraping stops returning results, re-export and update the field. The actor's automatic cookie persistence feature saves live cookies from each successful run, so subsequent runs start with increasingly fresh tokens even without a manual re-export.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Technique 2: Route traffic through your own machine
&lt;/h3&gt;

&lt;p&gt;If you want every actor run to use your home IP address, you can run a lightweight SOCKS5 proxy on your local machine and expose it to the internet using a free tunneling service. The actor sends all its traffic through your tunnel, so TikTok sees your residential IP — the same one that works when you run the scraper locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup with microsocks and ngrok (free tier):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1 — Install and start a local SOCKS5 proxy&lt;/span&gt;
&lt;span class="c"&gt;# macOS/Linux:&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;microsocks   &lt;span class="c"&gt;# or: sudo apt install microsocks&lt;/span&gt;
microsocks &lt;span class="nt"&gt;-p&lt;/span&gt; 1080

&lt;span class="c"&gt;# Step 2 — Create a public TCP tunnel to your proxy&lt;/span&gt;
&lt;span class="c"&gt;# (requires a free ngrok account at https://ngrok.com)&lt;/span&gt;
ngrok tcp 1080
&lt;span class="c"&gt;# ngrok will print something like: tcp://0.tcp.ngrok.io:12345 → localhost:1080&lt;/span&gt;

&lt;span class="c"&gt;# Step 3 — Paste the address into the actor's "Custom Proxy URL" field:&lt;/span&gt;
&lt;span class="c"&gt;# socks5://0.tcp.ngrok.io:12345&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your machine must stay running and connected while the actor is active. The &lt;code&gt;customProxyUrl&lt;/code&gt; field takes priority over the &lt;strong&gt;Proxy Configuration&lt;/strong&gt; panel, so you do not need to configure anything else.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Combining both techniques — injecting real session cookies &lt;em&gt;and&lt;/em&gt; routing through your home IP — gives the actor the best possible chance of success. TikTok sees a request from a known residential IP carrying an established session, which is indistinguishable from a real user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What data can you extract?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; provides a comprehensive set of data points for every video result:&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;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Video ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The unique identifier for the video (&lt;code&gt;id&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Video URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The direct link to the TikTok video (&lt;code&gt;url&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The full caption, including hashtags and mentions (&lt;code&gt;description&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;View Count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The number of times the video has been played (&lt;code&gt;views&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Likes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The number of likes the video has received (&lt;code&gt;likes&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Comments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The number of comments on the video (&lt;code&gt;comments&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shares&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The number of times the video has been shared (&lt;code&gt;shares&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Author Info&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Username (&lt;code&gt;uniqueId&lt;/code&gt;), nickname, avatar URL, verification status (&lt;code&gt;verified&lt;/code&gt;), and &lt;code&gt;followers&lt;/code&gt; count (&lt;code&gt;author&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Music Title&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The song or sound used in the video (&lt;code&gt;music&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Music Author&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The creator of the music or sound used in the video (&lt;code&gt;musicAuthor&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hashtags&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A list of hashtags included in the video (&lt;code&gt;hashtags&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Duration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The length or duration of the video (&lt;code&gt;duration&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timestamp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The date and time the video was published (&lt;code&gt;timestamp&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scraped At&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The exact date and time the data was extracted (&lt;code&gt;scrapedAt&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The method or source used to extract the data (&lt;code&gt;source&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to use the scraper
&lt;/h2&gt;

&lt;p&gt;Here is a sample of the extracted JSON output:&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="w"&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;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7580436439.........."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.tiktok.com/@techuser112/video/7580436439.........."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Comment &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tech&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; if you want a road map on how to get into any of these careers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"views"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"11K"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"likes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"shares"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"uniqueId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"techuser112"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nickname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tech with Lesedi’s profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"avatar"&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://p16-sign-sg.tiktokcdn.com/tos-alisg-avt-0068/9f813384bac2a......."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"verified"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"followers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&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;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"music"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lesedi with Adrian’s 7AM - Slowed + Reverb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"musicAuthor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hashtags"&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;"technology"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"techjobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"techreview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"techroadmap"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-12-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"scrapedAt"&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-03-21T10:47:10.773Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dom"&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;h2&gt;
  
  
  How to use the scraper on the Apify platform
&lt;/h2&gt;

&lt;p&gt;Running the &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; on the &lt;a href="https://console.apify.com/sign-up?fpr=eunit" rel="noopener noreferrer"&gt;Apify platform&lt;/a&gt; is a simple four-step process. You do not need any coding knowledge to get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Set your queries
&lt;/h3&gt;

&lt;p&gt;Once you are on the &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;TikTok Search Scraper&lt;/strong&gt;&lt;/a&gt; page, navigate to the &lt;strong&gt;Input&lt;/strong&gt; tab. In the &lt;strong&gt;Search Queries&lt;/strong&gt; field, enter an array of search terms you want to analyze. For example: &lt;code&gt;["tech review", "laptop unboxing", "coding tips"]&lt;/code&gt;. This screenshot below shows what you need to get started:&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.amazonaws.com%2Fuploads%2Farticles%2Fukvdqozoe4gd0dgztujr.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.amazonaws.com%2Fuploads%2Farticles%2Fukvdqozoe4gd0dgztujr.png" alt="Tiktok Search Scraper" width="800" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Define your limits
&lt;/h3&gt;

&lt;p&gt;Use the &lt;strong&gt;maxItems&lt;/strong&gt; field to specify how many videos you want to collect per query. If you are conducting a deep dive, consider setting this to 500. For a quick pulse check, 20 results might be enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Configure proxy settings
&lt;/h3&gt;

&lt;p&gt;TikTok has strict regional and IP-based limits. To avoid being blocked, always use &lt;strong&gt;Apify Proxy&lt;/strong&gt;. We highly recommend using &lt;strong&gt;Residential&lt;/strong&gt; proxies, as they use IP addresses assigned to real households, making your scraping activity indistinguishable from regular traffic. Learn more about ApifyProxy from the &lt;a href="https://docs.apify.com/proxy" rel="noopener noreferrer"&gt;Apify documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running without residential proxies:&lt;/strong&gt; If you prefer not to purchase a proxy plan right now, the actor provides two alternative bypass options under the &lt;strong&gt;Advanced — Datacenter IP Bypass&lt;/strong&gt; section of the input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session Cookies&lt;/strong&gt; — Paste a JSON cookie array exported from your real browser (see Technique 1 above). The actor injects these before any request, causing TikTok to treat the session as a real, trusted browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Proxy URL&lt;/strong&gt; — Enter a &lt;code&gt;socks5://&lt;/code&gt; or &lt;code&gt;http://&lt;/code&gt; URL pointing to a proxy running on your own machine (see Technique 2 above). This makes TikTok see your residential IP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using both options together offers the highest success rate without any proxy subscription.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Run and export
&lt;/h3&gt;

&lt;p&gt;Click the &lt;strong&gt;Start&lt;/strong&gt; button at the bottom of the screen. You can monitor the progress in the &lt;strong&gt;Log&lt;/strong&gt; tab. Once the run is finished, go to the &lt;strong&gt;Storage&lt;/strong&gt; tab to export your data. You can choose from several formats, e.g., JSON, CSV, Excel, or XML.&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.amazonaws.com%2Fuploads%2Farticles%2Foz1icv2jyftytqaj77me.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.amazonaws.com%2Fuploads%2Farticles%2Foz1icv2jyftytqaj77me.png" alt="Save and Start the TickTok Search Scraper" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization and cost efficiency with Pay-Per-Event
&lt;/h2&gt;

&lt;p&gt;Many scrapers charge based on the computing power they consume, which can be unpredictable and vary significantly. The &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; uses a more transparent model: &lt;a href="https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event" rel="noopener noreferrer"&gt;Pay-Per-Event (PPE)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With PPE, you are charged a fixed price of $0.29 for every 1,000 video results. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You only pay for the data you actually get.&lt;/li&gt;
&lt;li&gt;Your costs are perfectly predictable.&lt;/li&gt;
&lt;li&gt;You do not pay for the platform's overhead or idle time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use cases: Putting the data to work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Influencer marketing
&lt;/h3&gt;

&lt;p&gt;Agencies use the scraper to find rising stars. By searching for niche keywords, they can identify creators who have high engagement but have not yet been "discovered" by major brands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Market research
&lt;/h3&gt;

&lt;p&gt;Pharma and retail companies scrape TikTok to see how people are using their products. Are they finding new use cases? Are they complaining about a specific feature? This qualitative data is often more honest than what you get in a focus group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trend capitalization
&lt;/h3&gt;

&lt;p&gt;Media companies track trending hashtags to decide what topics to cover next. By seeing which videos are gaining the most views in a short period, they can jump on trends while they are still hot.&lt;/p&gt;

&lt;h2&gt;
  
  
  For Developer: Automate with JavaScript
&lt;/h2&gt;

&lt;p&gt;If you are a developer looking to integrate the &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; directly into your projects, you can use the official &lt;a href="https://docs.apify.com/api/client/js.md" rel="noopener noreferrer"&gt;Apify API JavaScript/TypeScript client&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First, install the &lt;code&gt;apify-client&lt;/code&gt; package in your project:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage Example
&lt;/h3&gt;

&lt;p&gt;To get started, initialize the client with your Apify API token and pass your search queries. Here is an example script showing how to scrape data for the "AI coding" query:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// Initialize the ApifyClient with your Apify API token&lt;/span&gt;
&lt;span class="c1"&gt;// Replace the '&amp;lt;YOUR_API_TOKEN&amp;gt;' with your token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR_API_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Prepare Actor input&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;queries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI coding&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maxItems&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;proxyConfiguration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;useApifyProxy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apifyProxyGroups&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RESIDENTIAL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Optional: paste your browser cookies to bypass datacenter IP blocks for free.&lt;/span&gt;
    &lt;span class="c1"&gt;// Export from Cookie-Editor extension (Export as JSON), then paste the array here.&lt;/span&gt;
    &lt;span class="c1"&gt;// "sessionCookies": "[{\"name\":\"msToken\",\"value\":\"...\",\"domain\":\".tiktok.com\",...}]",&lt;/span&gt;

    &lt;span class="c1"&gt;// Optional: route traffic through your own machine instead of Apify's datacenter.&lt;/span&gt;
    &lt;span class="c1"&gt;// Run a local SOCKS5 proxy (e.g. microsocks -p 1080) and expose it with: ngrok tcp 1080&lt;/span&gt;
    &lt;span class="c1"&gt;// "customProxyUrl": "socks5://0.tcp.ngrok.io:12345",&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Run the Actor and wait for it to finish&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eunit/tiktok-search-scraper&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch and print Actor results from the run's dataset (if any)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Results from dataset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`💾 Check your data here: https://console.apify.com/storage/datasets/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Legal and ethical considerations
&lt;/h2&gt;

&lt;p&gt;When scraping TikTok, it is important to act responsibly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Respect privacy&lt;/strong&gt;: Only scrape publicly available data. Never attempt to access private accounts or unauthorized sections of the platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid overloading&lt;/strong&gt;: Use the scraper's built-in delays and human-like interactions to avoid putting unnecessary strain on TikTok's servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comply with regulations&lt;/strong&gt;: Ensure your use of the data complies with local regulations, such as GDPR in the EU or CCPA in California.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;TikTok is a powerful source of real-time intelligence, but accessing it requires a professional approach. The &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;TikTok Search Scraper&lt;/a&gt; on the Apify platform provides the stealth, speed, and reliability you need to turn raw social media activity into actionable business insights.&lt;/p&gt;

&lt;p&gt;Start &lt;a href="https://apify.com/eunit/tiktok-search-scraper" rel="noopener noreferrer"&gt;your first run today&lt;/a&gt; and stop guessing about the latest trends.&lt;/p&gt;

&lt;p&gt;Happy automating!&lt;/p&gt;

</description>
      <category>tiktok</category>
      <category>datascraping</category>
      <category>trendanalysis</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>How to actually start your fitness journey and stick to it (with the FitJourney.app platform)</title>
      <dc:creator>Emmanuel Uchenna</dc:creator>
      <pubDate>Sat, 07 Mar 2026 12:46:36 +0000</pubDate>
      <link>https://dev.to/eunit/how-to-actually-start-your-fitness-journey-and-stick-to-it-with-the-fitjourney-platform-1oj4</link>
      <guid>https://dev.to/eunit/how-to-actually-start-your-fitness-journey-and-stick-to-it-with-the-fitjourney-platform-1oj4</guid>
      <description>&lt;p&gt;You have been thinking about it for months. Maybe even years. That expensive gym membership is gathering dust, your workout clothes still have the tags on them, and you have a growing, endless list of reasons why “tomorrow” will be the perfect day to begin. Here is the honest, unfiltered truth about health and wellness: starting a fitness journey is absolutely the hardest part, but it is also the most transformative decision you will ever make for your body and your mind.&lt;/p&gt;

&lt;p&gt;The first workout is always harder mentally than it is physically. We often build up incredible, insurmountable barriers in our minds. When you finally take that first step, you are not just beginning a workout routine - you are rewiring decades of self-doubt and breaking down established negative habits. However, taking that step can feel incredibly daunting because the modern fitness landscape is undeniably confusing. You have fitness influencers screaming conflicting advice, terrifying big box gyms full of confusing machines, and a dozen different apps you supposedly need to track your life.&lt;/p&gt;

&lt;p&gt;This article is designed to cut through the noise. We are going to deeply explore why starting your health and fitness journey feels so overwhelming, the science-backed benefits of committing to a routine, and how to find the lasting fitness motivation you need. Most importantly, we will introduce the &lt;a href="https://www.fitjourney.app/" rel="noopener noreferrer"&gt;FitJourney platform&lt;/a&gt;, an all-in-one ecosystem that drastically simplifies progress tracking, fitness challenges, and educational content so you can finally stop planning and start doing.&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.amazonaws.com%2Fuploads%2Farticles%2F7jw59kkk7xr5zw5acp83.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.amazonaws.com%2Fuploads%2Farticles%2F7jw59kkk7xr5zw5acp83.png" alt="FitJourney.app platform on various devices" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why starting your fitness journey feels so overwhelming
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Obstacle 1: Analysis paralysis
&lt;/h3&gt;

&lt;p&gt;You have probably researched every workout program, compared various gym memberships in your local area, and read countless articles about the ”best” way to get started. You spend hours watching videos on the optimal grip for a barbell deadlift, the exact macronutrient ratio for fat loss, or the perfect supplements to buy before you have even picked up a pair of dumbbells. Meanwhile, weeks turn into months of intense planning rather than taking any real action.&lt;/p&gt;

&lt;p&gt;The truth about how to start a fitness journey is simple: the best program is the one you will actually follow consistently. Analysis paralysis keeps you stuck on the starting line, waiting for perfect conditions that will never arrive. You don't need to know everything to begin. You need to take the first step and learn as you go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obstacle 2: Fragmentation
&lt;/h3&gt;

&lt;p&gt;Relying on scattered, disconnected tools completely kills your momentum. Right now, you might be using a basic notes app for &lt;a href="https://www.fitjourney.app/tracking-science" rel="noopener noreferrer"&gt;tracking your sets and reps&lt;/a&gt;, a separate application for logging your meals and counting calories, another website for learning how to perform exercises safely, and a group chat for accountability. This fragmentation makes the simple act of working out feel like a stressful, overwhelming part-time job.&lt;/p&gt;

&lt;p&gt;When your fitness tools are scattered across the wind, it is incredibly easy to drop the ball on one of them, which often leads to dropping the whole routine out of pure frustration. &lt;a href="https://www.fitjourney.app/" rel="noopener noreferrer"&gt;A unified, dedicated fitness tracking platform is essential&lt;/a&gt; for keeping everything organized and keeping you focused on the actual physical work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obstacle 3: Fear of judgment and unrealistic expectations
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.fitjourney.app/articles/overcoming-gym-anxiety" rel="noopener noreferrer"&gt;Walking into a new gym feels intimidating&lt;/a&gt;, especially when you are convinced that everyone else there has it perfectly figured out. Big box gyms can dramatically amplify this fear of judgment. You might worry that people are watching you struggle with a machine, or you might feel deeply embarrassed because you do not look like the fitness models prominently featured on your social media feed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is vital to remember that social media only shows the highlight reel, not the messy, sweaty middle of the journey&lt;/strong&gt;. Furthermore, unrealistic expectations set you up for immediate failure. You might expect to drop 10 pounds in your first week and get discouraged when the scale barely moves. Sustainable fitness is about showing up consistently, embracing the learning curve, and rejecting the pressure to be perfect from day one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is vital to remember that social media only shows the highlight reel, not the messy, sweaty middle of the journey&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The core benefits of committing to your fitness
&lt;/h2&gt;

&lt;p&gt;Drawing inspiration from real science and established medical research, it becomes clear that &lt;a href="https://www.mayoclinic.org/healthy-lifestyle/fitness/in-depth/exercise/art-20048389" rel="noopener noreferrer"&gt;committing to regular exercise is the ultimate game-changer for your entire life&lt;/a&gt;, far beyond just looking good in the mirror.&lt;/p&gt;

&lt;h3&gt;
  
  
  Physical health
&lt;/h3&gt;

&lt;p&gt;The most obvious benefits of a health and fitness journey are physical. Exercise helps control weight by helping you burn calories and build active, lean muscle mass. But beyond the aesthetics, &lt;a href="https://www.cdc.gov/physical-activity-basics/benefits/index.html" rel="noopener noreferrer"&gt;regular physical activity is a powerhouse for your internal, functional health&lt;/a&gt;. It vigorously combats chronic health conditions and diseases. Whether you are worried about hereditary heart disease or hoping to naturally prevent high blood pressure, being active boosts your high-density lipoprotein (HDL) cholesterol—often known as the “good” cholesterol—and decreases unhealthy triglycerides.&lt;/p&gt;

&lt;p&gt;This one-two punch keeps your blood flowing smoothly, which significantly lowers your risk of cardiovascular diseases, type 2 diabetes, metabolic syndrome, and even certain types of cancer. Additionally, as you build muscle strength and greatly boost your endurance, you will find you have significantly more physical energy to tackle your daily chores, play with your children, and live life without getting easily winded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mental well-being
&lt;/h3&gt;

&lt;p&gt;If you currently need a significant emotional lift or a reliable way to lower stress after a difficult, demanding day at work, a solid workout session can help tremendously. Physical activity stimulates various powerful brain chemicals, including endorphins and dopamine, that leave you feeling happier, significantly more relaxed, and noticeably less anxious. Exercise is a potent, natural antidepressant that helps you regulate cortisol levels in your body.&lt;/p&gt;

&lt;h3&gt;
  
  
  Longevity
&lt;/h3&gt;

&lt;p&gt;Fitness is a lifelong marathon, not a frantic 30-day sprint. It is the single best investment you can make in your future self and your long-term independence. By maintaining your muscle mass and increasing your bone density through dedicated resistance training, you actively prevent the risk of debilitating falls and arthritis as you age. Getting fit is not just about extending the total years of your lifespan; it is about drastically improving your health span. This ensures that your later years are vibrant, active, and fulfilling, free from the extreme physical limitations that plague so many older adults.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to define your "why" and set realistic goals
&lt;/h2&gt;

&lt;p&gt;Before nervously lacing up your running shoes or paying for an expensive gym membership, you absolutely need to understand the psychology of goal setting and intrinsic motivation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding your motivation
&lt;/h3&gt;

&lt;p&gt;To continually sustain your health and fitness journey through the tough days, you must clearly define your underlying "why". Why do you genuinely want to get fit? Your motivation must be unique to you. If your initial goal is "to look good in a swimsuit," your motivation will likely plummet to zero the moment you put on a cozy sweater in the winter.&lt;/p&gt;

&lt;p&gt;You need to look beyond surface-level aesthetics to understand your deepest, most personal reasons for getting fit. Do you deeply want to have the boundless energy to play with your children without losing your breath? Do you desperately want to reverse a frightening pre-diabetes diagnosis so you can live a long, full life? Do you want to build the physical confidence to hike a steep mountain on your next vacation? Write down your deeply rooted reasons on paper, and revisit them whenever your motivation wavers. Knowing your true "why" gives your fitness journey unshakeable, relentless purpose.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To continually sustain your health and fitness journey through the tough days, you must clearly define your underlying "why". Why do you genuinely want to get fit?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Goal setting
&lt;/h3&gt;

&lt;p&gt;Establishing clear, intelligent, and achievable goals is crucial for maintaining constant motivation. However, there is a massive conceptual difference between outcome goals and behavioral goals.&lt;/p&gt;

&lt;p&gt;An outcome goal might be something like "I want to lose 20 pounds by summer." While this is a perfectly fine desire, many complex biological factors influence the actual, literal outcome, which is outside of your direct control, such as your shifting metabolism, hormonal water weight fluctuations, and daily stress levels.&lt;/p&gt;

&lt;p&gt;A behavioral goal is an actionable step that is 100% within your complete control, such as "I will work out three times a week for exactly 45 minutes" or "I will eat a serving of green vegetables with every single dinner." Focus your mental energy entirely on mastering behavioral goals. Celebrate each small behavioral victory, because these repeatable actions serve as tangible evidence of your commitment and will inevitably lead to the desired long-term outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency over perfection
&lt;/h3&gt;

&lt;p&gt;Consistency beats perfection every single time. It is the golden, unbreakable rule of fitness. You don't need to execute perfectly to start a fitness journey, and you absolutely do not need to have a flawless diet to see amazing results. Life happens to all of us. Work gets crazy, the kids get sick, and natural motivation fluctuates from day to day.&lt;/p&gt;

&lt;p&gt;If you miss a scheduled workout, it does not mean you have failed. If you eat a piece of cake at a birthday party, your progress is not magically ruined forever. Remind yourself constantly that taking a brisk 15-minute walk on a bad, stressful day is infinitely better than doing nothing at all. The people who ultimately succeed in fitness are not those who never make mistakes; they are the ones who refuse ever to quit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the FitJourney platform: your all-in-one fitness ecosystem
&lt;/h2&gt;

&lt;p&gt;Now that you have a comprehensive understanding of the mental challenges, the profound medical benefits, and the resilient mindset explicitly required, you desperately need the right tools to execute your plan flawlessly. This is where the &lt;a href="https://www.fitjourney.app/" rel="noopener noreferrer"&gt;FitJourney platform&lt;/a&gt; comes in to revolutionize your approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing the FitJourney.app platform
&lt;/h3&gt;

&lt;p&gt;We built the &lt;a href="https://www.fitjourney.app/" rel="noopener noreferrer"&gt;FitJourney platform&lt;/a&gt; to permanently eliminate the confusing guesswork from your health and wellness goals. Remember the detrimental obstacle of fragmentation we discussed earlier? The FitJourney platform solves this critical issue by providing a comprehensive, all-in-one ecosystem. You no longer need five different, annoying applications to manage your health. &lt;a href="https://www.fitjourney.app/about" rel="noopener noreferrer"&gt;Our integrated fitness tracking platform&lt;/a&gt; brings your scheduled workouts, &lt;a href="https://www.fitjourney.app/articles" rel="noopener noreferrer"&gt;your ongoing education&lt;/a&gt;, &lt;a href="https://www.fitjourney.app/tracking-science" rel="noopener noreferrer"&gt;your nutrition guidelines&lt;/a&gt;, and your vital progress data into a single, beautiful, and highly intuitive interface.&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.amazonaws.com%2Fuploads%2Farticles%2Fchhv3py34mirvsq9daje.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.amazonaws.com%2Fuploads%2Farticles%2Fchhv3py34mirvsq9daje.png" alt="FitJourney.app dashboard" width="609" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured challenges
&lt;/h3&gt;

&lt;p&gt;One of the biggest hurdles for nervous beginners is bravely walking into a gym and thinking, "What in the world do I do now?" The FitJourney platform removes this paralyzing anxiety entirely through our structured, expertly designed programs. By navigating to the &lt;a href="https://www.fitjourney.app/app/challenges" rel="noopener noreferrer"&gt;&lt;strong&gt;Challenges&lt;/strong&gt;&lt;/a&gt; section of the platform, you can quickly enroll in fitness challenges purposefully tailored to your specific experience level.&lt;/p&gt;

&lt;p&gt;Whether you are tentatively stepping into a gym for the very first time in your life or you are an intermediate lifter looking to break through a plateau aggressively, our comprehensive challenges provide step-by-step guidance. You will know exactly which exercises to do, how many sets and reps to complete properly, and how to progress safely week after week.&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.amazonaws.com%2Fuploads%2Farticles%2F0b98mhqcbntlftcfxk2m.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.amazonaws.com%2Fuploads%2Farticles%2F0b98mhqcbntlftcfxk2m.png" alt="Challenges screen" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Education and practicals
&lt;/h3&gt;

&lt;p&gt;We firmly believe that education is the ultimate form of empowerment. We do not just want you to follow a generic workout routine; we desperately want you to understand exactly how your body works fully. The FitJourney platform features built-in, highly detailed educational articles and interactive practical exercises that teach you exactly how to train effectively and eat perfectly right for your dynamic goals.&lt;/p&gt;

&lt;p&gt;A practical is a hands-on learning module that rigorously tests your knowledge in a real-world setting. You will thoroughly learn about the scientific principles of progressive overload, how to properly fuel your precious body before a grueling workout, and how to proactively prevent common injuries. This deep, expanding well of knowledge strongly ensures that you are building sustainable, intelligent habits that will truly last a lifetime, rather than just seeking an impossible 30-day quick fix.&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.amazonaws.com%2Fuploads%2Farticles%2Fz3vm8j8q8e66wp3y2z6s.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.amazonaws.com%2Fuploads%2Farticles%2Fz3vm8j8q8e66wp3y2z6s.png" alt="Education and practicals on FitJourney.app" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  All-in-one tracking
&lt;/h3&gt;

&lt;p&gt;Seeing clear, tangible data is unequivocally the most reliable source of endless fitness motivation. When you can objectively see on a chart that you are getting stronger, running faster, and becoming healthier, everything falls into place wonderfully.&lt;/p&gt;

&lt;p&gt;The FitJourney platform makes tracking completely effortless and highly rewarding. Your personalized &lt;strong&gt;Dashboard&lt;/strong&gt; provides a clear, bird's-eye view of your current active streaks, your upcoming workouts, and your recent achievements. Meanwhile, &lt;strong&gt;Your Progress Tracker&lt;/strong&gt; allows you to intimately log the specific, granular details of your journey, securely storing your daily workout data, body measurements, and athletic performance milestones over time. Seeing your personal line graph steadily trending in the right direction provides the incredibly powerful psychological reinforcement you need to keep showing up every single week.&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.amazonaws.com%2Fuploads%2Farticles%2Feg2gl9lu9ugjqaamgvd9.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.amazonaws.com%2Fuploads%2Farticles%2Feg2gl9lu9ugjqaamgvd9.png" alt="Progress tracking on FitJourney.app" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3 simple steps to kickstart your journey today
&lt;/h2&gt;

&lt;p&gt;Your fitness journey is quietly waiting for you. Not next Monday. Not after the busy holidays. Not when life miraculously gets less busy. Right today. &lt;a href="https://www.fitjourney.app/articles/motivation-vs-discipline" rel="noopener noreferrer"&gt;The objectively hardest part is truly just making the bold decision that you are worth the time and physical investment&lt;/a&gt;. To help you aggressively overcome analysis paralysis, here are three simple, actionable steps you can confidently take right now to begin successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Get set up
&lt;/h3&gt;

&lt;p&gt;Your very first action is to swiftly &lt;a href="https://www.fitjourney.app/sign-up" rel="noopener noreferrer"&gt;create your account on the FitJourney platform&lt;/a&gt;. The entire sign-up process is remarkably quick, intentionally. Once you are successfully logged in, take a few dedicated minutes to explore your newly personalized &lt;strong&gt;Dashboard&lt;/strong&gt;. Familiarize yourself with the clean interface, set your initial user preferences, and complete your basic profile. Setting up your digital environment is a crucially important psychological cue to your brain that you are officially beginning a completely new chapter of your life.&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.amazonaws.com%2Fuploads%2Farticles%2F227zme3t16jwywndhkj8.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.amazonaws.com%2Fuploads%2Farticles%2F227zme3t16jwywndhkj8.png" alt="FitJourney Dashboard" width="777" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Join a challenge
&lt;/h3&gt;

&lt;p&gt;Once your account is fully set up, purposefully navigate to the &lt;a href="https://www.fitjourney.app/app/challenges" rel="noopener noreferrer"&gt;&lt;strong&gt;Challenges&lt;/strong&gt;&lt;/a&gt; tab located in the main menu. Enthusiastically browse through the available, structured programs and select a beginner-friendly challenge that perfectly aligns with your current fitness level and your personal goals. Do not overthink it; the primary goal right now is to build the consistent, daily habit of working out.&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.amazonaws.com%2Fuploads%2Farticles%2F0b98mhqcbntlftcfxk2m.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.amazonaws.com%2Fuploads%2Farticles%2F0b98mhqcbntlftcfxk2m.png" alt="FitJourney Challenges" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Track and learn
&lt;/h3&gt;

&lt;p&gt;When it is finally time for your critical first workout, open up the platform, diligently follow the detailed instructions safely provided in your chosen challenge, and give it your absolute best effort. Immediately after finishing, triumphantly jump into &lt;strong&gt;Your Progress Tracker&lt;/strong&gt; to carefully log your completed workout. Openly celebrate this massive, foundational win! Then, take 10 minutes to read your first educational article properly or complete an engaging practical on the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Actively starting your health and fitness journey is undoubtedly the hardest part of the entire process. It demands that you aggressively overcome deep-seated self-doubt, push decisively past paralyzing analysis paralysis, and bravely step into the vast unknown. But the staggering rewards—boundless daily energy, profound mental clarity, and exceptional long-term health—are ultimately worth infinitely more than the brief initial discomfort.&lt;/p&gt;

&lt;p&gt;To predictably succeed, you must define your deeply personal "why", prioritize relentless consistency over impossible perfection, and set behavioral goals that you can easily and directly control. Furthermore, strategically leveraging the right tools is paramount. By actively using the FitJourney platform, you permanently eliminate the distracting confusion of fragmented apps and systematically replace it with a focused, all-in-one fitness ecosystem that tracks your progress and educates your mind.&lt;/p&gt;

&lt;p&gt;Ready to definitively stop planning and start doing? &lt;a href="https://www.fitjourney.app/sign-up" rel="noopener noreferrer"&gt;Create your free account on the FitJourney platform&lt;/a&gt; and join your very first challenge today. Your future self will profoundly thank you for the incredible courage you showed right now.&lt;/p&gt;

</description>
      <category>fitness</category>
      <category>motivation</category>
      <category>digital</category>
      <category>health</category>
    </item>
  </channel>
</rss>
