<?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: Crawler Bros</title>
    <description>The latest articles on DEV Community by Crawler Bros (@crawlerbros).</description>
    <link>https://dev.to/crawlerbros</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%2F4020706%2F25e54aa8-4850-4807-8a4b-2f43f977ff70.jpeg</url>
      <title>DEV Community: Crawler Bros</title>
      <link>https://dev.to/crawlerbros</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crawlerbros"/>
    <language>en</language>
    <item>
      <title>Overcoming Cloudflare Blocks on Preply to Analyze Tutor Pricing Trends</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:01:37 +0000</pubDate>
      <link>https://dev.to/crawlerbros/overcoming-cloudflare-blocks-on-preply-to-analyze-tutor-pricing-trends-1799</link>
      <guid>https://dev.to/crawlerbros/overcoming-cloudflare-blocks-on-preply-to-analyze-tutor-pricing-trends-1799</guid>
      <description>&lt;p&gt;Building a localized pricing index for online language tutoring requires structured, fresh market data. Analysts comparing tutoring marketplaces often need to evaluate average hourly rates, native speaker availability, and professional certifications across different languages and regions. However, extracting this data at scale from Preply presents immediate technical challenges. The platform uses Cloudflare protection to prevent automated collection, which frequently results in HTTP 503 errors or CAPTCHA challenges when using standard scraping libraries.&lt;/p&gt;

&lt;p&gt;To bypass these blocks and gather clean structured datasets, developers can use the &lt;a href="https://apify.com/crawlerbros/preply-scraper" rel="noopener noreferrer"&gt;preply-scraper&lt;/a&gt; on the Apify platform. This tool is built specifically to navigate Preply's anti-scraping defenses while exposing clean parameters for filtering by subject, language, country, and price.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Query the Preply Public Directory
&lt;/h2&gt;

&lt;p&gt;The scraper operates in two distinct execution paths defined by the &lt;code&gt;mode&lt;/code&gt; parameter: directory search (&lt;code&gt;searchTutors&lt;/code&gt;) and direct profile extraction (&lt;code&gt;getTutor&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;searchTutors&lt;/code&gt; mode, the tool paginates through Preply's public index. It handles the underlying pagination logic automatically, returning up to 10 tutors per page. You can narrow your query using specific schema parameters to target exact market segments. For instance, if you need to analyze the premium market for certified German teachers, you can target specific sub-segments rather than pulling the entire directory.&lt;/p&gt;

&lt;p&gt;Here is an example input payload for a targeted search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"searchTutors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"german"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proOnly"&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;"minRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.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;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"useProxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;useProxy&lt;/code&gt; boolean is set to &lt;code&gt;true&lt;/code&gt; by default. When enabled, the execution automatically falls back to Playwright combined with residential proxy rotation if direct HTTP requests are blocked by Cloudflare. This ensures that the scraper mimics legitimate user traffic and maintains a high success rate over large runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extracting Detailed Profiles with IDs
&lt;/h2&gt;

&lt;p&gt;The directory search returns a high-level summary of each tutor, including their &lt;code&gt;hourlyRate&lt;/code&gt;, &lt;code&gt;rating&lt;/code&gt;, &lt;code&gt;totalLessons&lt;/code&gt;, and &lt;code&gt;responseRate&lt;/code&gt;. However, if your analysis requires deep academic or credential verification, you need the tutor's historical background. &lt;/p&gt;

&lt;p&gt;By switching the &lt;code&gt;mode&lt;/code&gt; parameter to &lt;code&gt;getTutor&lt;/code&gt;, you can feed the scraper specific identifiers. This mode accepts either full profile links via the &lt;code&gt;startUrls&lt;/code&gt; array or numeric IDs via the &lt;code&gt;tutorIds&lt;/code&gt; array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getTutor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tutorIds"&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;"12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"67890"&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;When running in &lt;code&gt;getTutor&lt;/code&gt; mode, the scraper extracts detailed nested structures that are omitted from the main search results page, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;education&lt;/code&gt;: Academic degrees, institutions, and graduation years.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;certificates&lt;/code&gt;: Certified teaching credentials and issuing bodies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output from this run is returned as a clean JSON object containing the &lt;code&gt;scrapedAt&lt;/code&gt; timestamp, ensuring you can track data freshness for historical analysis.&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;"tutorId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345"&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;"Maria S."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Spain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nativeLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Spanish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"languagesTaught"&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;"Spanish"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hourlyRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewCount"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalLessons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"responseRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"98%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isPro"&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;"profileUrl"&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://preply.com/en/tutor/12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"avatarUrl"&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://cdn.preply.com/avatar/12345.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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tutor"&lt;/span&gt;&lt;span class="p"&gt;,&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-05-17T10:00:00+00:00"&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;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;To run this scraper programmatically, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure your Apify environment&lt;/strong&gt;: Set up an account on the Apify platform to access the API and retrieve your API token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define your search parameters&lt;/strong&gt;: Construct your JSON input. Specify the target &lt;code&gt;language&lt;/code&gt; (such as "spanish" or "japanese") and apply filters like &lt;code&gt;tutorCountry&lt;/code&gt; using ISO country codes (e.g., &lt;code&gt;ES&lt;/code&gt; or &lt;code&gt;MX,CO&lt;/code&gt;) to segment your search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute the Actor&lt;/strong&gt;: Run the &lt;code&gt;preply-scraper&lt;/code&gt; with your payload. The platform handles the infrastructure, proxy rotation, and retries on rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch the Dataset&lt;/strong&gt;: Once the run completes, download the resulting data from the default dataset.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Predictable Event-Based Costs
&lt;/h2&gt;

&lt;p&gt;Estimating the cost of data pipeline operations is straightforward because this scraper uses a transparent PAY_PER_EVENT pricing model. Unlike traditional scrapers that bill based on unpredictable server execution time, this tool charges strictly for successful data delivery and execution starts.&lt;/p&gt;

&lt;p&gt;The cost structure consists of exactly two event types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start (&lt;code&gt;apify-actor-start&lt;/code&gt;)&lt;/strong&gt;: Charged at a flat rate of $0.005 per GB of memory allocated to the run. This event occurs once per run execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;)&lt;/strong&gt;: Charged at $0.005 per single tutor profile successfully returned in your dataset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As your data requirements scale, the pricing for results decreases according to specific volume-tier prices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FREE&lt;/strong&gt;: $0.005 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BRONZE&lt;/strong&gt;: $0.00433 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SILVER&lt;/strong&gt;: $0.00367 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOLD&lt;/strong&gt;: $0.003 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PLATINUM&lt;/strong&gt;: $0.003 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIAMOND&lt;/strong&gt;: $0.003 per result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a run configured with &lt;code&gt;maxItems: 1000&lt;/code&gt; allocated with 1 GB of memory will cost exactly $0.005 for the run initiation, plus the cost of the results. Under the FREE tier rate, 1,000 extracted tutor profiles will cost $5.00, bringing the total run cost to $5.005.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of the Scraping Approach
&lt;/h2&gt;

&lt;p&gt;This scraper is optimized for public directory extraction and individual profile retrieval, but it is not designed for real-time chat monitoring or scheduling updates. It cannot access private tutor-student messaging feeds, internal lesson bookings, or account-specific dashboards, as these require active user authentication. Additionally, while the scraper can filter by general availability using the &lt;code&gt;availabilityDay&lt;/code&gt; parameter, it does not fetch the granular, real-time booking calendar slots for individual tutors.&lt;/p&gt;




&lt;p&gt;The examples here were produced with &lt;a href="https://apify.com/crawlerbros/preply-scraper" rel="noopener noreferrer"&gt;Preply Tutor Profiles Scraper&lt;/a&gt;. Its README lists the output fields, so you can check a response against the schema before you build on it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Extracting Track BPM and Key Data Past Beatport Search Limits</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Wed, 23 Sep 2026 08:12:09 +0000</pubDate>
      <link>https://dev.to/crawlerbros/extracting-track-bpm-and-key-data-past-beatport-search-limits-2pho</link>
      <guid>https://dev.to/crawlerbros/extracting-track-bpm-and-key-data-past-beatport-search-limits-2pho</guid>
      <description>&lt;p&gt;Building an automated ingestion pipeline for electronic music metadata often leads to Beatport. Music analytics platforms, DJ curation tools, and record labels rely on its catalog for critical track attributes: BPM, Camelot keys, catalog numbers, ISRC codes, and release dates.&lt;/p&gt;

&lt;p&gt;However, querying Beatport directly presents engineering obstacles. The search endpoint behaves inconsistently, capping results at 10 items per page and looping result sets after one or two pages. Furthermore, the storefront utilizes anonymous token rotation that can reject requests with HTTP 401 status codes across certain IP ranges. &lt;/p&gt;

&lt;p&gt;Using the &lt;a href="https://apify.com/crawlerbros/beatport-scraper" rel="noopener noreferrer"&gt;Beatport Scraper&lt;/a&gt; actor bypasses these storefront quirks by wrapping the internal &lt;code&gt;api.beatport.com/v4/catalog&lt;/code&gt; API, managing visitor session tokens, and offering targeted ingestion modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Search Pagination Bottleneck
&lt;/h2&gt;

&lt;p&gt;When sending free-text queries to Beatport's search endpoint via &lt;code&gt;mode: "search"&lt;/code&gt;, Beatport's API caps track results to 10 items per page regardless of request parameters. After the first one or two pages, the underlying API recycles results, making deep pagination via keyword search impossible. A free-text search typically tops out at 10 to 20 unique tracks before deduplication terminates the run.&lt;/p&gt;

&lt;p&gt;If your pipeline requires systematic catalog coverage across genres or labels, keyword searching is the wrong ingestion path.&lt;/p&gt;

&lt;p&gt;Instead, the catalog endpoints for genres, labels, releases, and charts do not suffer from this 10-item cap. Running a genre or release extraction allows consistent retrieval of full pages of tracks. For structured pipelines, shifting from &lt;code&gt;mode: "search"&lt;/code&gt; to &lt;code&gt;mode: "browseByGenre"&lt;/code&gt;, &lt;code&gt;mode: "browseReleases"&lt;/code&gt;, or &lt;code&gt;mode: "topCharts"&lt;/code&gt; provides stable, high-volume ingestion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browseByGenre"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"genre"&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-house"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minBpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxBpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"A Minor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&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;
  
  
  Structure of the Extracted Track Dataset
&lt;/h2&gt;

&lt;p&gt;The actor emits clean records directly to the dataset, omitting empty fields rather than passing null values. A single track record includes identity, musical analysis, and media URLs:&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3731110&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;"The Kid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mixName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Original Mix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"artists"&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;"Example Artist"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"primaryArtist"&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 Artist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remixers"&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;"label"&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 Records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"releaseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;54321&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"releaseName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Kid EP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"genre"&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="mi"&gt;11&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;"Tech House"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slug"&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-house"&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;"bpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"8A"&lt;/span&gt;&lt;span class="p"&gt;,&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;"6:42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"durationMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;402000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display"&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.49"&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;"isrc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GB-AAA-24-00001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"catalogNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EX001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"releaseDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previewUrl"&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://geo-samples.beatport.com/track/preview/3731110.mp3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sampleStartMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sampleEndMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"artworkUrl"&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://geo-media.beatport.com/image_size/500x500/12345.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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"track"&lt;/span&gt;&lt;span class="p"&gt;,&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;"2025-02-23T12:00:00.000Z"&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 payload delivers both standard musical keys and Camelot notations (such as &lt;code&gt;8A&lt;/code&gt;), making the output immediately usable in harmonic mixing engines or catalog tagging systems without custom mapping tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering Parameters for Targeted Ingestion
&lt;/h2&gt;

&lt;p&gt;To prevent downstream systems from discarding irrelevant records, the actor processes filter parameters before writing items to the dataset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;minBpm&lt;/code&gt; and &lt;code&gt;maxBpm&lt;/code&gt;: Constrains the tempo range (valid between 40 and 200 BPM).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;key&lt;/code&gt;: Restricts results to any of the 24 standard musical keys.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;releasedAfter&lt;/code&gt;: Accepts an ISO date string (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;) to exclude historical back-catalogs during recurring monitoring runs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;containsKeyword&lt;/code&gt;: Performs case-insensitive matching on track titles.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includeReleaseTracks&lt;/code&gt; / &lt;code&gt;includeChartTracks&lt;/code&gt;: When fetching parent objects in &lt;code&gt;topCharts&lt;/code&gt; or &lt;code&gt;browseReleases&lt;/code&gt; modes, setting these booleans to &lt;code&gt;true&lt;/code&gt; extracts the nested individual track objects into the dataset alongside the parent record.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running an Ingestion Workflow via Python
&lt;/h2&gt;

&lt;p&gt;The Apify API client allows straightforward integration of the scraper into an ETL pipeline. The following script configures a release-monitoring extraction for a specific electronic music label.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;run_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;mode&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;browseReleases&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;labelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20670&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;releaseSort&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;newest&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;includeReleaseTracks&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;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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;crawlerbros/beatport-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="c1"&gt;# Fetch emitted dataset items
&lt;/span&gt;&lt;span class="n"&gt;dataset_items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="n"&gt;tracks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&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;dataset_items&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;recordType&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;track&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="s"&gt;Extracted &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;tracks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tracks from label catalog.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step-by-Step Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locate Target IDs&lt;/strong&gt;: If targeting a specific artist or label, open their page on &lt;code&gt;beatport.com&lt;/code&gt;. The ID is the integer at the end of the URL (e.g., &lt;code&gt;beatport.com/artist/amy-dabbs/406088&lt;/code&gt; yields &lt;code&gt;artistId: 406088&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select Ingestion Mode&lt;/strong&gt;: Choose &lt;code&gt;browseByGenre&lt;/code&gt;, &lt;code&gt;browseByLabel&lt;/code&gt;, &lt;code&gt;browseReleases&lt;/code&gt;, or &lt;code&gt;topCharts&lt;/code&gt; for bulk ingestion. Reserve &lt;code&gt;search&lt;/code&gt; strictly for targeted track name lookups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Filters&lt;/strong&gt;: Define &lt;code&gt;releasedAfter&lt;/code&gt; to pull only new tracks since your last ingestion timestamp, and configure &lt;code&gt;minBpm&lt;/code&gt;/&lt;code&gt;maxBpm&lt;/code&gt; if your target taxonomy requires strict tempo boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Proxy&lt;/strong&gt;: If your runs encounter environment-level 401 blocks from Beatport's API, enable &lt;code&gt;proxyConfiguration&lt;/code&gt; in the input payload to route catalog and token-minting requests through datacenter proxies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consume Dataset&lt;/strong&gt;: Ingest the dataset items by streaming records from the default dataset into your analytical warehouse or music library database.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  System Boundaries
&lt;/h2&gt;

&lt;p&gt;This tool does not provide full-length audio downloads; it extracts only metadata and the public 30- to 60-second MP3 preview URLs hosted on Beatport's CDN. Additionally, because Beatport's API occasionally omits specific metadata fields (such as missing ISRC values on older releases), downstream schemas must handle missing fields rather than assuming every track record contains identical keys.&lt;/p&gt;




&lt;p&gt;If you want to reproduce this, the Actor is &lt;a href="https://apify.com/crawlerbros/beatport-scraper" rel="noopener noreferrer"&gt;Beatport Scraper&lt;/a&gt;. Read its input schema before the first run -- most failed runs are a missing required field, not a block.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>devops</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Calculating N M Distance Matrices Without API Key Quotas</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Wed, 23 Sep 2026 04:04:12 +0000</pubDate>
      <link>https://dev.to/crawlerbros/calculating-nxm-distance-matrices-without-api-key-quotas-48el</link>
      <guid>https://dev.to/crawlerbros/calculating-nxm-distance-matrices-without-api-key-quotas-48el</guid>
      <description>&lt;p&gt;Logistics optimization and vehicle routing algorithms require matrix inputs. When building a Travelling Salesman Problem (TSP) solver or evaluating warehouse distribution centers, sending thousands of coordinate pairs to commercial mapping APIs gets expensive fast. Most commercial routing engines enforce strict daily quotas and charge per request cell, making large-scale distance calculation cost-prohibitive during initial analysis phases.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/crawlerbros/osrm-route-planner" rel="noopener noreferrer"&gt;OSRM Route Planner Scraper&lt;/a&gt; solves this by tapping into the open Open Source Routing Machine engine backed by OpenStreetMap data. It converts latitude and longitude inputs into routing geometry, distance tables, and road network snapping records without requiring developer keys or dedicated proxy configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Coordinate Formatting and Direct API Queries
&lt;/h2&gt;

&lt;p&gt;A common source of errors in routing pipelines is coordinate order. Standard mapping applications accept inputs as latitude followed by longitude (e.g., &lt;code&gt;"51.5074,-0.1278"&lt;/code&gt; for London). However, underlying GeoJSON specs and the native OSRM API expect longitude followed by latitude (&lt;code&gt;lon,lat&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;When passing coordinates into this task, you supply standard &lt;code&gt;"lat,lon"&lt;/code&gt; strings. The system normalizes the coordinate strings automatically before executing queries against the OpenStreetMap network.&lt;/p&gt;

&lt;p&gt;The platform provides three core operational workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"driving"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startCoordinate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51.5074,-0.1278"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endCoordinate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"48.8566,2.3522"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"waypoints"&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;"50.1109,8.6821"&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;If you already have pre-formatted routing URLs targeting an existing OSRM deployment, you can bypass input parameters using &lt;code&gt;startUrls&lt;/code&gt;. Providing an array of direct endpoint URLs overrides &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;profile&lt;/code&gt;, and coordinate inputs entirely, passing those HTTP requests straight to the network handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating Matrix Distances and Snapping Coordinates
&lt;/h2&gt;

&lt;p&gt;Routing engines fail when coordinates fall on unroutable terrain, such as a building interior or water body. To prevent calculation errors, you can run a nearest-road lookup to snap points to the closest routable segment prior to route execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nearest Road Lookup (&lt;code&gt;mode: "nearest"&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;By passing a single point to &lt;code&gt;startCoordinate&lt;/code&gt;, the module retrieves the closest physical road name, precise snapped coordinates, and the radial offset in meters:&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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nearest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceUrl"&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://router.project-osrm.org/nearest/v1/driving/51.5074,-0.1278"&lt;/span&gt;&lt;span class="p"&gt;,&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-01-01T00:00:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"driving"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queryCoordinate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51.5074,-0.1278"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nearestCoordinate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51.507478,-0.127965"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nearestLatitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;51.507478&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nearestLongitude"&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.127965&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nearestRoadName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"King Charles I Island"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distanceToRoadMeters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;14.37&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;
  
  
  Distance Matrix Generation (&lt;code&gt;mode: "matrix"&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;For fleet routing, calculating individual point-to-point routes scales exponentially ($O(N \times M)$). Matrix mode requests the entire cross-table in a single execution payload. You provide arrays to the &lt;code&gt;origins&lt;/code&gt; and &lt;code&gt;destinations&lt;/code&gt; parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"matrix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"driving"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origins"&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;"51.5074,-0.1278"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"51.4545,-2.5879"&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;"destinations"&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;"48.8566,2.3522"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"50.6292,3.0573"&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 output yields an array of &lt;code&gt;matrixCells&lt;/code&gt;, containing &lt;code&gt;durationSeconds&lt;/code&gt; and &lt;code&gt;distanceMeters&lt;/code&gt; for every origin-destination pair combination:&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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"matrix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceUrl"&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://router.project-osrm.org/table/v1/driving/..."&lt;/span&gt;&lt;span class="p"&gt;,&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-01-01T00:00:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"driving"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origins"&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;"51.5074,-0.1278"&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;"destinations"&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;"48.8566,2.3522"&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;"matrixCells"&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;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51.5074,-0.1278"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"48.8566,2.3522"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"durationSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43265.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"distanceMeters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;620363.4&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;h2&gt;
  
  
  How to Set Up an OSRM Pipeline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define the operation mode&lt;/strong&gt;: Select &lt;code&gt;route&lt;/code&gt; for turn-by-turn directions, &lt;code&gt;matrix&lt;/code&gt; for distance tables, or &lt;code&gt;nearest&lt;/code&gt; for geofence snapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select a transport profile&lt;/strong&gt;: Set &lt;code&gt;profile&lt;/code&gt; to &lt;code&gt;driving&lt;/code&gt;, &lt;code&gt;cycling&lt;/code&gt;, or &lt;code&gt;walking&lt;/code&gt; to adjust speed assumptions and access rules (e.g., pedestrian-only zones or highway restrictions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set coordinate inputs&lt;/strong&gt;: Supply standard &lt;code&gt;"lat,lon"&lt;/code&gt; values into &lt;code&gt;startCoordinate&lt;/code&gt; and &lt;code&gt;endCoordinate&lt;/code&gt; for simple paths, or populate &lt;code&gt;origins&lt;/code&gt; and &lt;code&gt;destinations&lt;/code&gt; arrays for matrix calculations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure execution boundaries&lt;/strong&gt;: Optionally set &lt;code&gt;maxItems&lt;/code&gt; to restrict the total record output emitted to the default dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the Task&lt;/strong&gt;: Execute the module to generate output data directly into your dataset storage.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cost Structure and Execution Mechanics
&lt;/h2&gt;

&lt;p&gt;This module runs on a pure PAY_PER_EVENT pricing model. This means you are charged strictly for specific execution events rather than variable compute runtimes or subscription tiers.&lt;/p&gt;

&lt;p&gt;The event charges are defined as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start (&lt;code&gt;apify-actor-start&lt;/code&gt;)&lt;/strong&gt;: $0.005 per GB of memory allocated to the run. This is a one-time start event fee per run execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset Result (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;)&lt;/strong&gt;: $0.005 per event output emitted to the default dataset (with volume tiers dropping to $0.00433 on BRONZE, $0.00367 on SILVER, and $0.003 on GOLD, PLATINUM, and DIAMOND tiers).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, running a matrix calculation that yields 100 output dataset records using a 1 GB memory allocation incurs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 × &lt;code&gt;Actor Start&lt;/code&gt; event at 1 GB = $0.005&lt;/li&gt;
&lt;li&gt;100 × &lt;code&gt;result&lt;/code&gt; events at base price ($0.005) = $0.50&lt;/li&gt;
&lt;li&gt;Total run cost = $0.505&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the routing compute uses the public demo server, you incur no extra third-party subscription charges or proxy bandwidth fees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Pipeline Limitations
&lt;/h2&gt;

&lt;p&gt;While this approach works well for batch processing and network analysis, it does not account for real-time traffic congestion or live road closures. The underlying public OSRM demonstration server relies on static OpenStreetMap data dumps, making it unsuitable for live navigation applications that require live rerouting around temporary incidents.&lt;/p&gt;

&lt;p&gt;The system adds controlled request spacing to respect public server resource limits. If your pipeline demands real-time dynamic re-routing or millions of sub-second matrix operations, you will need to point &lt;code&gt;startUrls&lt;/code&gt; toward a privately self-hosted OSRM instance.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://apify.com/crawlerbros/osrm-route-planner" rel="noopener noreferrer"&gt;OSRM Route Planner Scraper&lt;/a&gt; is the Actor behind these examples. If a selector in your own version breaks, compare your output against the fields listed in its README first.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>webdev</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Why Exposing Facebook Comments to MCP Agents Times Out on 300 Second Runs</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Wed, 23 Sep 2026 02:52:18 +0000</pubDate>
      <link>https://dev.to/crawlerbros/why-exposing-facebook-comments-to-mcp-agents-times-out-on-300-second-runs-15oe</link>
      <guid>https://dev.to/crawlerbros/why-exposing-facebook-comments-to-mcp-agents-times-out-on-300-second-runs-15oe</guid>
      <description>&lt;h2&gt;
  
  
  Exposing Facebook Comments to AI Agents via Model Context Protocol
&lt;/h2&gt;

&lt;p&gt;When you expose a data extraction tool to an AI agent using the Model Context Protocol (MCP), the agent does not see a web interface or a simplified SDK wrapper. It sees a raw JSON Schema. If you expose &lt;a href="https://apify.com/crawlerbros/facebook-comments-scraper" rel="noopener noreferrer"&gt;facebook-comments-scraper&lt;/a&gt; (which extracts public comments from Facebook posts, Watch videos, and photo stories) directly to an LLM via the Apify MCP server, the agent must parse, understand, and accurately populate a highly nested input structure.&lt;/p&gt;

&lt;p&gt;The integration relies on the Apify MCP server at &lt;code&gt;https://mcp.apify.com&lt;/code&gt;, scoped using the &lt;code&gt;?tools=facebook-comments-scraper&lt;/code&gt; query parameter. This scoping isolates the toolset so the agent is not overwhelmed by thousands of unrelated utilities. However, once isolated, the agent is solely responsible for generating valid payloads that conform to the scraper's exact technical specifications.&lt;/p&gt;

&lt;p&gt;When an agent processes a user request like "find out what people think about this post," it has to translate that intent into a tool call. If the input schema is too permissive or if the agent misunderstands the default properties, the execution fails before a single container starts. The most common point of failure is how the agent handles the transition from natural language requests to structured API payloads, particularly around URL formats and proxy configurations.&lt;/p&gt;

&lt;p&gt;To expose &lt;code&gt;facebook-comments-scraper&lt;/code&gt; to an AI agent, you can configure your MCP client to connect to the Apify MCP host with the specific actor tool scoped in the connection URL. This exposes only the target actor as a callable tool, reducing context window bloat and preventing the model from hallucinating input parameters.&lt;/p&gt;

&lt;p&gt;Here is an example configuration for a Claude Desktop developer setup or any standard MCP host file that registers the scoped tool:&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-facebook-comments"&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;"@modelcontextprotocol/server-env"&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=facebook-comments-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="nl"&gt;"env"&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_TOKEN"&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_APIFY_API_TOKEN"&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;When this configuration loads, the MCP server queries the actor's metadata and translates its input schema into a tool definition. The agent receives a tool called &lt;code&gt;facebook-comments-scraper&lt;/code&gt; along with a structured JSON Schema describing what arguments it must generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What tool signature does the agent interpret?
&lt;/h2&gt;

&lt;p&gt;The tool signature is an OpenAPI-like schema that maps the Actor's input fields into JSON Schema definitions for function calling. The AI model inspects field types, descriptions, and defaults to determine required properties and optional operational flags.&lt;/p&gt;

&lt;p&gt;Checked against the Actor's input schema and Apify docs on 2026-09-23.&lt;/p&gt;

&lt;p&gt;Here is the exact schema signature that the AI agent interprets when deciding how to invoke the tool:&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;"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;"facebook-comments-scraper"&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;"Scrape public comments from Facebook posts, Watch videos, and photo stories. Extract comment text, author info, reactions, timestamps, and nested replies via GraphQL pagination."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&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;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"startUrls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"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;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"items"&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;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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="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;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uri"&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;"required"&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;"url"&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;"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;"Start URLs | Facebook post, Reel, Watch, or photo URLs to scrape comments from. Supports /posts/, /reel/, /watch?v=, /photo?fbid=, /groups/.../permalink/, and /share/p/ URLs."&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;"maxItems"&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;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"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;"Max items per URL | Maximum number of comment rows to collect per start URL (not global across all URLs)."&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;"onlyCommentsNewerThan"&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;"string"&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;"Only comments newer than | Limit results to comments posted after this date. Accepts YYYY-MM-DD, a full ISO 8601 timestamp (e.g. 2024-06-15T12:00:00Z), or a relative value like '1 day', '2 weeks', '3 months'"&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;"commentsMode"&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;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enum"&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;"ALL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEWEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MOST_RELEVANT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&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="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;"Comments mode | Which comment view to scrape: ALL (Facebook's unfiltered 'All comments' view, in Facebook's own ranked ordering), NEWEST (newest first), or MOST_RELEVANT (Facebook's ranked 'Most relevant' view)."&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;"includeNestedComments"&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;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&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;"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;"Include nested comments | If true, also scrape reply rows under top-level comments (with parentComment/parentReply metadata). If false, only top-level comments are collected (faster)."&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;"maxReplyDepth"&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;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&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;"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;"Max reply nesting depth | How many levels of nested replies to follow when 'Include nested comments' is enabled. 1 = only direct replies to top-level comments (Facebook's most reliably available level)."&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;"proxy"&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;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"apifyProxyGroups"&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;"RESIDENTIAL"&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;"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;"Proxy Configuration | Residential proxy recommended for reliable results, especially at higher volume or concurrency."&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;"required"&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;"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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent uses this system description to construct its payload. If the user provides a raw URL string, the agent must wrap it inside the &lt;code&gt;startUrls&lt;/code&gt; array of objects. Any failure to build this specific structure results in a validation error before the execution reaches the scraper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does relying on UI prefill fields break programmatic agent calls?
&lt;/h2&gt;

&lt;p&gt;The Apify API completely ignores schema prefill fields because they are console UI helpers rather than backend defaults. Programmatic calls and MCP drivers only apply schema default values, so relying on prefill hints causes runs to execute without intended configurations.&lt;/p&gt;

&lt;p&gt;On the Apify platform, there is a distinct separation between how the Console UI is rendered and how the underlying API processes inputs. The &lt;code&gt;prefill&lt;/code&gt; field in an actor's input schema controls what a human operator sees when they open the scraper inside their web browser. It is a user-experience design pattern to help humans get started quickly.&lt;/p&gt;

&lt;p&gt;However, the Apify API does not apply &lt;code&gt;prefill&lt;/code&gt; fields when executing runs via API endpoints or tool calls. Only true &lt;code&gt;default&lt;/code&gt; values are merged into the payload if a key is omitted. This means if you program your agent to rely on UI-level hints or if the agent assumes certain complex objects (like proxy parameters) will automatically populate with human-centric prefilled data, the API will receive an incomplete structure.&lt;/p&gt;

&lt;p&gt;To guarantee that your agent runs do not fail due to missing network routing parameters, the agent must be instructed to always construct and pass an explicit input payload containing all required keys. You cannot count on the platform to merge UI-level prefills behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you handle the 300 second synchronous run cap?
&lt;/h2&gt;

&lt;p&gt;You handle the cap by initiating the Actor run asynchronously and polling its status before fetching dataset items. Synchronous endpoints cut connections after 300 seconds with an HTTP 408 error, making asynchronous polling mandatory for large thread extraction.&lt;/p&gt;

&lt;p&gt;The synchronous run endpoint on the Apify platform has a strict physical cap of 300 seconds. If an Actor run exceeds this limit during a synchronous HTTP POST request, the platform terminates the HTTP connection and returns an HTTP 408 (Request Timeout) status.&lt;/p&gt;

&lt;p&gt;When an AI agent triggers a run synchronously via an MCP tool call, it waits for the HTTP response. For threads with thousands of comments, deep nesting levels, or complex anti-scraping challenges, scraping can easily push past the 5-minute barrier.&lt;/p&gt;

&lt;p&gt;To avoid this failure mode, any agent integration that handles large data sets must run the Actor asynchronously. Instead of waiting for the results in a single blocking request, the agent must POST to the asynchronous runs endpoint, receive a run ID, poll the run status, and then fetch the items from the default dataset once the run completes successfully.&lt;/p&gt;

&lt;p&gt;Here is a Python implementation that demonstrates how to execute this asynchronous execution flow and handle potential timeouts gracefully:&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;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_facebook_scraper_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;actor_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;facebook-comments-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;initiate_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://api.apify.com/v2/acts/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actor_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/runs?token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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="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="n"&gt;start_url&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;max_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commentsMode&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;ALL&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;includeNestedComments&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;maxReplyDepth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apifyProxyGroups&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;RESIDENTIAL&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="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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initiate_url&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="k"&gt;if&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="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;201&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;RuntimeError&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;Failed to start Actor run: &lt;/span&gt;&lt;span class="si"&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="n"&gt;run_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="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;data&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_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_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;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;dataset_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_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;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;status_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://api.apify.com/v2/actor-runs/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;while&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;status_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;status_url&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;status_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="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&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;RuntimeError&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;Failed to fetch run status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status_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="n"&gt;status_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status_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="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;data&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status_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;status&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;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="sh"&gt;"&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;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABORTED&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;TIMED-OUT&lt;/span&gt;&lt;span class="sh"&gt;"&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;RuntimeError&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;Actor run terminated with status: &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="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;items_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://api.apify.com/v2/datasets/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dataset_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/items?token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;items_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;items_url&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;items_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="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&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;RuntimeError&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;Failed to fetch dataset items: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;items_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="n"&gt;items_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How can an AI agent detect that the max total charge cap has been tripped?
&lt;/h2&gt;

&lt;p&gt;An AI agent detects a tripped charge cap by inspecting run metadata for an ABORTED status combined with budget limit termination flags. Reading these fields lets the agent distinguish between empty post engagement and intentional cost enforcement shutdowns.&lt;/p&gt;

&lt;p&gt;Apify allows you to pass a limit on how much a run can cost by using the &lt;code&gt;maxTotalChargeUsd&lt;/code&gt; query parameter on the run endpoint. This maps internally to the environment variable &lt;code&gt;ACTOR_MAX_TOTAL_CHARGE_USD&lt;/code&gt; inside the running container.&lt;/p&gt;

&lt;p&gt;When the cost of the run reaches this threshold, the platform flags the run to abort. However, this is not an instantaneous hard kill. The container continues to run briefly while cleaning up resources, which means it may still consume minor compute overhead during its termination phase.&lt;/p&gt;

&lt;p&gt;An AI agent processing the outputs of a run must check the termination metadata. If the run ended because it hit the budget ceiling, the agent must inform the user that the data is incomplete due to budget limits rather than assuming the post simply had no more comments.&lt;/p&gt;

&lt;p&gt;Here is a JavaScript example showing how to inspect the run details and determine if a run was stopped by the cost safety cap:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkRunBudgetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;runId&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.apify.com/v2/actor-runs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiToken&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="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to fetch run metadata: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;terminationReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terminationReason&lt;/span&gt; &lt;span class="o"&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;wasAborted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;ABORTED&lt;/span&gt;&lt;span class="dl"&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;wasAborted&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;terminationReason&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;MAX_CHARGES_REACHED&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Budget limit reached. The output data represents a partial scrape.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;chargedAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usageUsd&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="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&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;SUCCEEDED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Run completed with status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="na"&gt;chargedAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usageUsd&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;h2&gt;
  
  
  What are the limitations and caveats of this scraper?
&lt;/h2&gt;

&lt;p&gt;When utilizing &lt;a href="https://apify.com/crawlerbros/facebook-comments-scraper" rel="noopener noreferrer"&gt;facebook-comments-scraper&lt;/a&gt;, several hard limitations exist in both the scraper itself and the Apify infrastructure that can cause an AI agent to fail if not handled in code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reels Require Login&lt;/strong&gt;: The scraper explicitly states that Reels (&lt;code&gt;/reel/&lt;/code&gt; URLs) are not accessible without a Facebook session. Because this scraper operates without requiring authentication, passing Reel URLs will result in zero scraped items. You must use post or video URL formats instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage Rate Limits&lt;/strong&gt;: Apify enforces strict storage rate limits: 60 requests per second for individual storage objects and 400 requests per second for dataset item pushes or request queue CRUD operations. High-concurrency agent workflows that exceed these rates will encounter throttling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request Queue Processing Restrictions&lt;/strong&gt;: On the Apify platform, a request queue can only be processed by one Actor or task run at a time. Multiple concurrent runs cannot process a single shared queue in parallel; fan-out patterns must allocate separate queues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unnamed Storage Expiration&lt;/strong&gt;: Unnamed storages on free Apify accounts retain only the 10 most recent runs for a maximum of 4 months before deletion. Integrations relying on persistent historical datasets must use named storages, which are permanently exempt from deletion rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Session Lifespans&lt;/strong&gt;: While datacenter proxies persist for 26 hours, residential proxy sessions (recommended for Facebook scraping) last approximately 30 minutes. Extremely long runs that paginate through large threads may lose their session IP, causing minor connection pauses while establishing new proxy sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is the exact cost model for executing runs?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;facebook-comments-scraper&lt;/code&gt; Actor uses the &lt;code&gt;PAY_PER_EVENT&lt;/code&gt; pricing model. You are charged solely for specific named events emitted during execution. There are no separate compute time fees or usage-based platform charges applied to the run.&lt;/p&gt;

&lt;p&gt;The charged events and their exact costs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;apify-actor-start&lt;/strong&gt;: Charged once per run at $0.05 per GB of memory allocated to the run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;apify-default-dataset-item&lt;/strong&gt;: Charged at $0.005 per event for every item written to the default dataset (representing a single scraped comment record).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This per-item event cost scales down based on Apify volume tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FREE&lt;/strong&gt;: $0.005 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BRONZE&lt;/strong&gt;: $0.00367 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SILVER&lt;/strong&gt;: $0.00233 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOLD&lt;/strong&gt;: $0.001 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PLATINUM&lt;/strong&gt;: $0.001 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIAMOND&lt;/strong&gt;: $0.001 per event&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primary controls over total cost are the allocated container memory and the number of returned comments. Setting &lt;code&gt;maxItems&lt;/code&gt; limits the total dataset item events generated per URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you process nested comments without double-charging?
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;includeNestedComments&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; in your input configuration when you only need top-level comment text. Every nested reply emitted as a record incurs a distinct &lt;code&gt;apify-default-dataset-item&lt;/code&gt; charge, so omitting reply rows directly reduces event volume.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;includeNestedComments&lt;/code&gt; is enabled (the default setting), the scraper collects reply rows under top-level comments and outputs them as individual records. Top-level records have a &lt;code&gt;threadingDepth&lt;/code&gt; of &lt;code&gt;0&lt;/code&gt;, while replies have a &lt;code&gt;threadingDepth&lt;/code&gt; of &lt;code&gt;1&lt;/code&gt; or higher and include a &lt;code&gt;replyToCommentId&lt;/code&gt; referencing their parent comment.&lt;/p&gt;

&lt;p&gt;Because every item emitted to the default dataset generates a billed event, extracting deep reply threads for posts with heavy discussion increases event counts rapidly. If your agent only evaluates overall post sentiment or top-level engagement, setting &lt;code&gt;includeNestedComments&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; prevents the scraper from fetching replies, saving both runtime and event costs.&lt;/p&gt;

&lt;p&gt;If you do collect nested comments, here is a Python helper to assemble the flat dataset items into a hierarchical tree in memory:&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;def&lt;/span&gt; &lt;span class="nf"&gt;structure_comment_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset_items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;comments_by_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;top_level_comments&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;comment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commentId&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;comment_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;comments_by_id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;comment_id&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;comment_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;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;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;text&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;author&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profileName&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;likes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;likesCount&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;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;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;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;threadingDepth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;threadingDepth&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;replies&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;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;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;comment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replyToCommentId&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;comment_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;comment_id&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;comments_by_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comments_by_id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;comment_id&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;parent_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;parent_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;comments_by_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;comments_by_id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parent_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;replies&lt;/span&gt;&lt;span class="sh"&gt;"&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;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&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;threadingDepth&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="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;top_level_comments&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;node&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;top_level_comments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How do you handle non-existent pages or authentication blocks?
&lt;/h2&gt;

&lt;p&gt;You handle failure states by checking for an empty dataset output and searching the run log for diagnostic warning phrases. Inspecting logs for keywords like "block" or "invalid url" allows your application to differentiate blocked requests from quiet posts.&lt;/p&gt;

&lt;p&gt;When scraping public Facebook URLs, invalid links, deleted posts, or restricted group content will result in empty datasets because no public comments can be rendered without login.&lt;/p&gt;

&lt;p&gt;An AI agent must be able to tell whether zero output records signify a newly created post with no comments or a blocked extraction attempt. Parsing the container log output provides immediate context regarding network or access issues.&lt;/p&gt;

&lt;p&gt;Here is a Node.js script using the official client that checks the dataset output, inspects run logs upon encountering an empty result, and raises distinct errors based on the output:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scrapeAndVerifyComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetUrl&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiToken&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;facebook-comments-scraper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;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="nx"&gt;targetUrl&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="na"&gt;maxItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;commentsMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;includeNestedComments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="k"&gt;if &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="nx"&gt;length&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="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logText&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;log&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;id&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lowerLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logText&lt;/span&gt; &lt;span class="o"&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;lowerLog&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;block&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="nx"&gt;lowerLog&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;captcha&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Scraping was blocked by anti-bot protections. Ensure residential proxies are enabled.&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="k"&gt;else&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;lowerLog&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;not found&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="nx"&gt;lowerLog&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;invalid url&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Target URL is private, restricted, or non-existent: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&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="k"&gt;else&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EMPTY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run succeeded, but no public comments were found on this post.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;data&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="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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;count&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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The Actor's README is the source of truth for its inputs, outputs and limits. Need a hand wiring this into your stack? Email &lt;a href="mailto:info@crawlerbros.com"&gt;info@crawlerbros.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>Tracking Per-Size ASOS Stock Across 10 Regional Stores</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Wed, 23 Sep 2026 00:01:45 +0000</pubDate>
      <link>https://dev.to/crawlerbros/tracking-per-size-asos-stock-across-10-regional-stores-21of</link>
      <guid>https://dev.to/crawlerbros/tracking-per-size-asos-stock-across-10-regional-stores-21of</guid>
      <description>&lt;p&gt;E-commerce pricing engines and retail analytics pipelines often fail when tracking inventory changes at the variant level. On fast-fashion platforms like ASOS, top-level product availability is misleading: a product page may list a dress as active, but the most common sizes (like S and M) are completely sold out, leaving only fringe sizes in stock. Furthermore, localized pricing, currency adjustments, and discount strategies vary across regional storefronts.&lt;/p&gt;

&lt;p&gt;Building a scraper to track these localized changes directly against ASOS requires handling market-specific endpoints, localized currency headers, and variant-level stock flags. The &lt;a href="https://apify.com/crawlerbros/asos-scraper" rel="noopener noreferrer"&gt;asos-scraper&lt;/a&gt; actor standardizes extraction across 10 regional stores (US, GB, FR, DE, IT, ES, AU, NL, SE, RU) and returns structured product and stock metadata down to the individual SKU level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Regional Differences and Per-Size Stock
&lt;/h2&gt;

&lt;p&gt;ASOS uses independent catalogs and currency defaults for each country storefront. Scraped data from the US store (&lt;code&gt;store: "US"&lt;/code&gt;) does not necessarily mirror the stock levels or active promotions found on the United Kingdom (&lt;code&gt;store: "GB"&lt;/code&gt;) or German (&lt;code&gt;store: "DE"&lt;/code&gt;) sites.&lt;/p&gt;

&lt;p&gt;When scraping top-level search results, ASOS provides summary metadata. However, granular sizing details—such as whether a specific size is low on inventory or completely out of stock—require fetching variant information from the underlying product display page (PDP) payloads.&lt;/p&gt;

&lt;p&gt;The actor supports several modes to balance payload size and data depth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search&lt;/code&gt;: Keyword queries over a target catalog.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;byCategory&lt;/code&gt;: Browsing structured taxonomy using preset names or numeric &lt;code&gt;categoryId&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;byProductIds&lt;/code&gt; and &lt;code&gt;byUrls&lt;/code&gt;: Direct lookup for specific catalog items.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stockCheck&lt;/code&gt;: A lightweight variant designed specifically for price and inventory monitors tracking fixed SKU lists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When extracting via &lt;code&gt;search&lt;/code&gt; or &lt;code&gt;byCategory&lt;/code&gt;, setting &lt;code&gt;includeDetails: true&lt;/code&gt; instructs the actor to fetch the PDP data for every result, populating the &lt;code&gt;sizes&lt;/code&gt; array with individual variant availability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution Walkthrough
&lt;/h2&gt;

&lt;p&gt;You can configure and trigger the actor via JSON payloads through the Apify API or platform interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Target Store and Filter Constraints
&lt;/h3&gt;

&lt;p&gt;To capture women's dresses on sale in the UK store within a specific price range, configure the mode, regional settings, and filter flags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"byCategory"&lt;/span&gt;&lt;span class="p"&gt;,&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;"women_dresses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minPrice"&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;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onSaleOnly"&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;"includeDetails"&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;"maxItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Extract Variant-Level Inventory
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;includeDetails&lt;/code&gt; is enabled (or when using &lt;code&gt;byProductIds&lt;/code&gt;), each emitted JSON record contains the &lt;code&gt;sizes&lt;/code&gt; array. Each object in this array exposes the specific stock condition for that size.&lt;/p&gt;

&lt;p&gt;Here is an example structure of a returned record:&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;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;210484705&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567"&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;"Design Midi Dress in Floral Print"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ASOS DESIGN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dresses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Women"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"colour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Multi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currentPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;35.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currentPriceFormatted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"£35.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previousPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rrpPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discountPercent"&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;"isOnSale"&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;"isInStock"&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;"isSellingFast"&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;"isRestockingSoon"&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;"sizes"&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;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK 6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"brandSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US 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;"variantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isInStock"&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;"isLowInStock"&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;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK 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;"brandSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US 4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"variantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-1002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isInStock"&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;"isLowInStock"&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;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK 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;"brandSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US 6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"variantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-1003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isInStock"&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;"isLowInStock"&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;span class="nl"&gt;"mainImageUrl"&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://images.asos-media.com/products/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productUrl"&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.asos.com/prd/210484705"&lt;/span&gt;&lt;span class="p"&gt;,&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;"2025-02-17T12:00:00.000Z"&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 3: Parse and Ingest into a Data Pipeline
&lt;/h3&gt;

&lt;p&gt;If you are running regular price checks on known products, switch to &lt;code&gt;mode: "stockCheck"&lt;/code&gt;. In this mode, the actor omits large text blocks, bullet points, and image arrays, outputting records with &lt;code&gt;recordType: "stock"&lt;/code&gt; to optimize throughput.&lt;/p&gt;

&lt;p&gt;You can process these records downstream using Python to flag items where popular sizes have sold out:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_asos_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;productId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;currentPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;discountPercent&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="c1"&gt;# Calculate stock availability across variants
&lt;/span&gt;    &lt;span class="n"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;sizes&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;total_sizes&lt;/span&gt; &lt;span class="o"&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;sizes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;available_sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&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;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&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;isInStock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;low_stock_sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&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;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&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;isLowInStock&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;product_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;product_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;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discount_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_stock_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;available_sizes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_sizes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_sizes&lt;/span&gt; &lt;span class="k"&gt;else&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;available_sizes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;available_sizes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low_stock_sizes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;low_stock_sizes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pricing and Execution Event Structure
&lt;/h2&gt;

&lt;p&gt;The actor operates on a pay-per-event pricing model. Charges are strictly tied to initialization and dataset ingestion events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start&lt;/strong&gt; (&lt;code&gt;apify-actor-start&lt;/code&gt;): $0.005 per GB of memory allocated to the run, billed once at the start of execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result Item&lt;/strong&gt; (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;): $0.005 per result emitted to the default dataset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Volume-tier prices apply directly to the result event based on usage tier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FREE: $0.005 per result&lt;/li&gt;
&lt;li&gt;BRONZE: $0.00433 per result&lt;/li&gt;
&lt;li&gt;SILVER: $0.00367 per result&lt;/li&gt;
&lt;li&gt;GOLD: $0.003 per result&lt;/li&gt;
&lt;li&gt;PLATINUM: $0.003 per result&lt;/li&gt;
&lt;li&gt;DIAMOND: $0.003 per result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are no additional per-minute or infrastructure charges beyond these named events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Pipeline Boundaries
&lt;/h2&gt;

&lt;p&gt;This actor does not interact with user accounts, cart sessions, or private checkout flows; it only reads publicly available catalog and inventory endpoints. If your workflow requires validating order limits, checking localized postal code delivery restrictions, or simulating an authenticated checkout session, this tool cannot perform those actions.&lt;/p&gt;

&lt;p&gt;For monitoring regional markdowns across multiple territories, running &lt;code&gt;mode: "stockCheck"&lt;/code&gt; on a defined list of &lt;code&gt;productIds&lt;/code&gt; across the 10 supported regional codes (&lt;code&gt;store&lt;/code&gt;) allows you to build historical pricing matrices without collecting unnecessary media assets.&lt;/p&gt;




&lt;p&gt;Everything above runs on &lt;a href="https://apify.com/crawlerbros/asos-scraper" rel="noopener noreferrer"&gt;ASOS Scraper&lt;/a&gt;. Start with a small input and a low result limit before you widen the run -- the output shape is easier to check that way.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prices quoted above are this Actor's published pay-per-event rates on the Apify Store, read from the Apify platform API on 2026-09-23. Check the Actor page for the current rates.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>devops</category>
      <category>python</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Why Batching Instagram Profiles Reduces Your Actor Start Costs</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:18:07 +0000</pubDate>
      <link>https://dev.to/crawlerbros/why-batching-instagram-profiles-reduces-your-actor-start-costs-21j6</link>
      <guid>https://dev.to/crawlerbros/why-batching-instagram-profiles-reduces-your-actor-start-costs-21j6</guid>
      <description>&lt;h2&gt;
  
  
  Orchestrating Instagram Profile Data Workflows
&lt;/h2&gt;

&lt;p&gt;Extracting public data from social media platforms like Instagram often feels like a cat-and-mouse game, complicated by rate limits, session management, and the sheer volume of data. For data engineers, the real challenge isn't just getting the data, but reliably wiring it into a broader pipeline without silent failures or unexpected costs. This article focuses on integrating the &lt;a href="https://apify.com/crawlerbros/instagram-profile-scraper" rel="noopener noreferrer"&gt;Instagram Profile Scraper&lt;/a&gt; into robust data workflows, emphasizing state management, efficient orchestration, and understanding the specific cost model to prevent double-processing on re-runs or overspending.&lt;/p&gt;

&lt;p&gt;We're going to examine how to run the &lt;code&gt;instagram-profile-scraper&lt;/code&gt; Actor to retrieve profile and post data, then process that output, all while managing the state to ensure idempotency when fetching new data or re-running a job. The focus is on the pipeline's mechanics: how to kick off a run, retrieve its results, and integrate them into downstream systems like n8n or a data warehouse, handling the critical aspects of data freshness and cost efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you avoid re-processing old Instagram data on subsequent runs?
&lt;/h2&gt;

&lt;p&gt;You can avoid re-processing old data by maintaining a record of the last &lt;code&gt;scraped_at&lt;/code&gt; timestamp for each profile or post. When initiating a new run, pass only the usernames that require updates or new posts since that timestamp. For posts, compare the &lt;code&gt;pub_date&lt;/code&gt; from new results against previously stored data to identify only new entries, ensuring your pipeline only ingests fresh information.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;instagram-profile-scraper&lt;/code&gt; Actor generates distinct &lt;code&gt;profile record&lt;/code&gt; and &lt;code&gt;post record&lt;/code&gt; types, each carrying a &lt;code&gt;scraped_at&lt;/code&gt; timestamp in ISO 8601 format. For profile records, this timestamp indicates when the profile metadata was last fetched. For post records, it marks when that specific post's data was collected.&lt;/p&gt;

&lt;p&gt;To implement robust state management, you need a system that tracks what you've already processed. A simple approach is to store the &lt;code&gt;scraped_at&lt;/code&gt; timestamp for the &lt;em&gt;latest&lt;/em&gt; item (either profile or post) processed for a given username. Before initiating a new Actor run, your orchestration layer queries your destination data store (e.g., a data warehouse, a database, or even a local JSON file for simpler setups) to find the most recent &lt;code&gt;scraped_at&lt;/code&gt; value for each username you're monitoring.&lt;/p&gt;

&lt;p&gt;While the Actor doesn't directly support an &lt;code&gt;onlyNewSince&lt;/code&gt; input parameter, you can achieve this by intelligently managing your input. For profiles, you would typically want the latest data, so you'd pass the username regardless. For posts, if you have stored the &lt;code&gt;pub_date&lt;/code&gt; of the last post scraped for a given profile, you can then filter newly retrieved posts based on this &lt;code&gt;pub_date&lt;/code&gt; before inserting them into your downstream system.&lt;/p&gt;

&lt;p&gt;Here’s a basic Python example demonstrating how to retrieve data and then simulate state management for posts:&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;apify_client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the ApifyClient with your API token
# You can get your API token from Apify Console &amp;gt; Settings &amp;gt; API &amp;amp; Integrations
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;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;# Replace with the actual Actor ID from the Apify Store URL
&lt;/span&gt;&lt;span class="n"&gt;ACTOR_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;crawlerbros/instagram-profile-scraper&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;run_scraper_and_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usernames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_posts_to_scrape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_processed_dates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Runs the Instagram Profile Scraper and processes its output,
    filtering for new posts based on a provided dictionary of last processed dates.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;run_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usernames&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;usernames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxPosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;max_posts_to_scrape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting Actor run with input: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_input&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="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="n"&gt;ACTOR_ID&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;dataset_items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;

    &lt;span class="n"&gt;new_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;updated_profiles&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post_url&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# This is a post record
&lt;/span&gt;            &lt;span class="n"&gt;post_pub_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&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;pub_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&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;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="c1"&gt;# Check if this post is newer than the last processed date for this user
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;last_processed_dates&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;post_pub_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;last_processed_dates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;username&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;last_processed_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# If no previous date, it's new
&lt;/span&gt;                &lt;span class="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author_meta&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# This is a profile record
&lt;/span&gt;            &lt;span class="c1"&gt;# Profile records typically overwrite or update existing profile data
&lt;/span&gt;            &lt;span class="n"&gt;updated_profiles&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;item&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;Found &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;new_posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; new posts and &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;updated_profiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; updated profiles.&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="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_profiles&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage:
# In a real scenario, last_processed_dates would come from your data warehouse
# For demonstration, let's assume we last processed up to May 1st, 2026 for 'instagram'
# and have no prior data for 'natgeo'
&lt;/span&gt;&lt;span class="n"&gt;last_processed_dates_example&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;instagram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-05-01T00:00:00&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;natgeo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt; &lt;span class="c1"&gt;# Represents no previous processing
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Run the scraper
&lt;/span&gt;&lt;span class="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_scraper_and_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;usernames&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;instagram&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;natgeo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;max_posts_to_scrape&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="n"&gt;last_processed_dates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;last_processed_dates_example&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# In a real pipeline, you would now push new_posts and updated_profiles
# to your downstream system (e.g., n8n, data warehouse)
&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- New Posts ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;new_posts&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="c1"&gt;# Print first 2 new posts as an example
&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;Post URL: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;post_url&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;, Pub Date: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pub_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Updated Profiles ---&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;profile&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;updated_profiles&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;# Print first updated profile as an example
&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;Username: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;author_meta&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;username&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;, Followers: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;author_meta&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;followers_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="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;This approach shifts the burden of deduplication from the scraper to your post-processing logic, which is generally more flexible and efficient. For each username, you'd update your &lt;code&gt;last_processed_dates&lt;/code&gt; with the &lt;code&gt;pub_date&lt;/code&gt; of the newest post ingested.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use managed Instagram sessions instead of supplying your own cookies?
&lt;/h2&gt;

&lt;p&gt;You should use managed Instagram sessions by leaving the &lt;code&gt;cookies&lt;/code&gt; input field blank for most use cases, especially for larger-scale or continuous scraping. The Actor automatically handles session rotation and retry logic, significantly reducing the operational overhead of managing Instagram accounts, IP blocks, and expiring cookies yourself.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;instagram-profile-scraper&lt;/code&gt; offers two primary authentication methods: providing your own Instagram cookies or letting the Actor use a managed pool of shared sessions. For single, infrequent runs or highly specialized use cases where you need absolute control over the session identity, supplying your own cookies might seem appealing. However, this comes with significant drawbacks. Instagram frequently invalidates sessions, requiring you to constantly re-export and update cookies. This introduces manual intervention and fragility into automated pipelines.&lt;/p&gt;

&lt;p&gt;The managed session pool, on the other hand, is designed for resilience. It leverages a large pool of automatically rotated sessions, proxy IP addresses, and built-in retry mechanisms to bypass common blocking challenges. This "set it and forget it" approach is particularly valuable for data engineers building robust, hands-off data pipelines. It streamlines maintenance and reduces the likelihood of runs failing due to authentication issues. Moreover, the cost model is volume-based, not duration-based, so you are not penalized for the overhead of managing these sessions; the cost scales with the number of &lt;code&gt;result&lt;/code&gt; events (scraped items).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the most efficient way to fan out scraping multiple Instagram profiles?
&lt;/h2&gt;

&lt;p&gt;The most efficient way to fan out scraping multiple Instagram profiles is to pass an array of usernames directly to a single &lt;code&gt;instagram-profile-scraper&lt;/code&gt; Actor run. The Actor is designed for batch processing and will scrape each profile sequentially within that single run, handling internal rate limits and retries. Using separate runs for each profile would incur additional &lt;code&gt;Actor Start&lt;/code&gt; charges and increase orchestration complexity without providing significant performance benefits.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;usernames&lt;/code&gt; input field accepts an array of strings. For example: &lt;code&gt;["cristiano", "nike", "natgeo"]&lt;/code&gt;. When you provide multiple usernames, the Actor processes them one after another. This internal batching is optimized for Instagram's rate limits and the Actor's operational logic.&lt;/p&gt;

&lt;p&gt;Consider an alternative: initiating a separate Actor run for each username. This would require your orchestration system (e.g., n8n, a custom Python script) to loop through your list of usernames, making an API call for each one. Not only does this add complexity to your orchestrator, but it also results in multiple &lt;code&gt;Actor Start&lt;/code&gt; charges. For example, scraping 100 profiles in a single run would incur one &lt;code&gt;Actor Start&lt;/code&gt; charge. Scraping 100 profiles in 100 separate runs would incur 100 &lt;code&gt;Actor Start&lt;/code&gt; charges. Given that the &lt;code&gt;Actor Start&lt;/code&gt; charge is $0.008 per GB of memory allocated, this can add up.&lt;/p&gt;

&lt;p&gt;Here’s an example of preparing input for a batch 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;"usernames"&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;"instagram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"natgeo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"nike"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"nasa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"therock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"cristiano"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"leomessi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arianagrande"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"kimkardashian"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"selenagomez"&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;"maxPosts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single JSON input sent to the Actor's run endpoint will initiate a job that processes all ten profiles. The Actor's internal mechanisms manage the queuing and execution for each profile, ensuring efficient resource utilization and adherence to best practices for Instagram scraping.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the pricing model affect pipeline design for this Actor?
&lt;/h2&gt;

&lt;p&gt;The pricing model for the &lt;code&gt;instagram-profile-scraper&lt;/code&gt; is pay-per-event, specifically $0.005 per &lt;code&gt;result&lt;/code&gt; (dataset item) and $0.008 per GB of memory allocated for an &lt;code&gt;Actor Start&lt;/code&gt;. This means your pipeline design should prioritize minimizing redundant &lt;code&gt;Actor Start&lt;/code&gt; events and only retrieving the necessary &lt;code&gt;result&lt;/code&gt; items to optimize costs.&lt;/p&gt;

&lt;p&gt;Understanding the pay-per-event model is crucial. Unlike duration-based billing, where longer runs cost more, here you pay for explicit actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;"result" (apify-default-dataset-item):&lt;/strong&gt; $0.005 per event. This is charged for every profile record and every post record pushed to the default dataset. If you scrape 10 profiles with &lt;code&gt;maxPosts: 0&lt;/code&gt;, you get 10 profile records. If you scrape 1 profile with &lt;code&gt;maxPosts: 100&lt;/code&gt;, you get 1 profile record and 100 post records.

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Volume tiers for this event:&lt;/strong&gt; FREE $0.005, BRONZE $0.00433, SILVER $0.00367, GOLD $0.003, PLATINUM $0.003, DIAMOND $0.003. As you process more results across all your Apify Actors, your per-item cost decreases.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;"Actor Start" (apify-actor-start):&lt;/strong&gt; $0.008 per GB of memory allocated to the run. This is charged once when the Actor begins execution.

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Volume tiers for this event:&lt;/strong&gt; FREE $0.008, BRONZE $0.00633, SILVER $0.00467, GOLD $0.003, PLATINUM $0.003, DIAMOND $0.003.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model incentivizes batching and intelligent input parameters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Batching &lt;code&gt;usernames&lt;/code&gt;:&lt;/strong&gt; As discussed, running multiple usernames in a single Actor run means you incur only one &lt;code&gt;Actor Start&lt;/code&gt; charge for all of them, regardless of how many profiles are processed. This is significantly more cost-effective than initiating a new run for each username.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Using &lt;code&gt;maxPosts&lt;/code&gt; wisely:&lt;/strong&gt; Setting &lt;code&gt;maxPosts&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt; allows you to scrape only profile metadata, avoiding the cost of post records if you don't need them. If you only need the latest few posts, keeping &lt;code&gt;maxPosts&lt;/code&gt; at a low value like &lt;code&gt;12&lt;/code&gt; (the default) or &lt;code&gt;50&lt;/code&gt; will be much cheaper than fetching 500 posts per profile.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;State Management:&lt;/strong&gt; By implementing the state management strategy outlined earlier, you avoid re-ingesting (and thus re-paying for) data you already have. Your post-processing logic should filter out duplicate posts based on &lt;code&gt;pub_date&lt;/code&gt; and &lt;code&gt;shortcode&lt;/code&gt;, ensuring that only truly new data incurs cost for downstream storage and processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;Actor Start&lt;/code&gt; charge is small per GB, but it's a fixed overhead per run. For large-scale operations, optimizing this can lead to substantial savings. This pricing structure reinforces the engineering principle of doing more with less, especially with API calls and task invocations.&lt;/p&gt;

&lt;p&gt;Checked against the Actor's input schema and Apify docs on 2026-09-22.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens if an Instagram profile does not exist or is private?
&lt;/h2&gt;

&lt;p&gt;If an Instagram profile does not exist or is private, the &lt;code&gt;instagram-profile-scraper&lt;/code&gt; Actor handles these scenarios gracefully without failing the entire run. For non-existent profiles, a dataset record with &lt;code&gt;status: "error"&lt;/code&gt; will be pushed, detailing the failure message (e.g., &lt;code&gt;Profile @username does not exist or is not available&lt;/code&gt;). For private accounts, the Actor will return the available profile metadata, and the &lt;code&gt;is_private&lt;/code&gt; field in the &lt;code&gt;author_meta&lt;/code&gt; object will be &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This behavior is critical for robust data pipelines. Instead of a hard crash that halts your entire batch, you receive clear, actionable feedback within the dataset. Your downstream processing can then identify these records by checking the &lt;code&gt;status&lt;/code&gt; field or the &lt;code&gt;is_private&lt;/code&gt; flag and handle them appropriately. This might involve logging the error, flagging the profile for review, or simply omitting posts from private accounts. Posts cannot be scraped from private accounts, as the Actor respects Instagram's privacy settings. The output will reflect this limitation rather than attempting to bypass privacy.&lt;/p&gt;

&lt;p&gt;Here's an &lt;code&gt;json&lt;/code&gt; example of how you might check for these conditions in your 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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"author_meta"&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;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nonexistent_profile_12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&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;"biography"&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;"profile_pic_url"&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;"is_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="nl"&gt;"is_private"&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;"is_business"&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;"followers_count"&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;"following_count"&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;"posts_count"&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;"profile_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/nonexistent_profile_12345/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"external_urls"&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;"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;"error_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Profile @nonexistent_profile_12345 does not exist or is not available"&lt;/span&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-09-22T10:00:00.000Z"&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;"author_meta"&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;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private_account_example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_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;"Private User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"biography"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This is a private 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;"profile_pic_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://..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_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="nl"&gt;"is_private"&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;"is_business"&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;"followers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"following_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"posts_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"profile_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/private_account_example/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"external_urls"&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;"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;"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-09-22T10:00:00.000Z"&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;When iterating through the dataset items, you would typically check the &lt;code&gt;status&lt;/code&gt; key. If &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;"error"&lt;/code&gt;, you have a problem profile. If it's &lt;code&gt;"success"&lt;/code&gt; and &lt;code&gt;author_meta.is_private&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, then you know posts won't be available, and you can adjust your downstream logic accordingly. This granular error reporting ensures your pipeline remains robust and provides specific insights into data collection failures or limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you integrate Apify Actor results into n8n for further automation?
&lt;/h2&gt;

&lt;p&gt;You integrate Apify Actor results into n8n using the Apify Trigger node, which fires upon Actor run completion, and then retrieve data from the associated dataset. This eliminates the need for manual polling and allows you to chain subsequent n8n nodes for data transformation, conditional logic, and pushing data to various destinations like webhooks, databases, or cloud storage.&lt;/p&gt;

&lt;p&gt;n8n offers a seamless integration with Apify, making it a powerful tool for extending your data pipelines. The key is to leverage the Apify Trigger node, specifically configured for "On Run Finished" events.&lt;/p&gt;

&lt;p&gt;Here's a high-level workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Configure Apify Trigger Node:&lt;/strong&gt; In n8n, add an Apify Trigger node. Select the "On Run Finished" trigger type and specify the &lt;code&gt;instagram-profile-scraper&lt;/code&gt; Actor. You'll need to link your Apify API key.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Start the Actor (Optional, or separate flow):&lt;/strong&gt; You can either manually start the Actor, schedule it on Apify, or even have a separate n8n workflow kick off the Actor run (using the "Apify" node set to "Execute Actor"). The trigger node only listens for completion, it doesn't start the run.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Retrieve Data with Apify Node:&lt;/strong&gt; After the trigger fires, add another "Apify" node. Set its operation to "Get All Dataset Items" or "Get Dataset Items (Paginated)" and link it to the &lt;code&gt;run["defaultDatasetId"]&lt;/code&gt; which will be available from the trigger node's output. This retrieves the scraped data.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Process and Transform:&lt;/strong&gt; Use n8n's vast array of nodes (e.g., "Set", "Function", "Item Lists", "Split In Batches") to clean, filter, and transform the data. This is where you'd implement your state management logic if it's not handled upstream: checking &lt;code&gt;scraped_at&lt;/code&gt; timestamps, deduplicating, or enriching data.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Downstream Actions:&lt;/strong&gt; Finally, push the processed data to your desired destination. This could be a "Webhook" node to send data to another service, a "PostgreSQL" or "MySQL" node to insert into a database, an "S3" node to store files, or even a "Slack" node for notifications.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using n8n is particularly useful for handling the variety of outputs from the &lt;code&gt;instagram-profile-scraper&lt;/code&gt;. For example, you might want to send profile updates to one system and new post data to another. n8n's conditional logic (&lt;code&gt;IF&lt;/code&gt; nodes) allows you to route &lt;code&gt;profile record&lt;/code&gt; types differently from &lt;code&gt;post record&lt;/code&gt; types based on the presence of fields like &lt;code&gt;post_url&lt;/code&gt; or &lt;code&gt;author_meta.posts_count&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="c1"&gt;# A hypothetical Python snippet that could be used in an n8n Function node
# or a custom script after fetching dataset items to filter for new posts.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_new_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_processed_dates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;new_posts&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post_url&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# Check if it's a post record
&lt;/span&gt;            &lt;span class="n"&gt;post_pub_date_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pub_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&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;post_pub_date_str&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;post_pub_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_pub_date_str&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;Z&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;+00:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# Handle 'Z' suffix
&lt;/span&gt;
                &lt;span class="c1"&gt;# Assume last_processed_dates looks like {"username": "ISO_DATE_STRING"}
&lt;/span&gt;                &lt;span class="n"&gt;last_date_for_user_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_processed_dates&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;username&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;last_date_for_user_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;last_date_for_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_date_for_user_str&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;Z&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;+00:00&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;post_pub_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;last_date_for_user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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="c1"&gt;# No previous date, consider all posts new
&lt;/span&gt;                    &lt;span class="n"&gt;new_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&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;new_posts&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage within an n8n function node (data would come from previous nodes)
# let items = $json["datasetItems"]; // Replace with actual path to dataset items
# let last_dates = { "instagram": "2026-05-01T00:00:00Z" }; // This would come from your DB/state store
# return [{ json: filter_new_posts(items, last_dates) }];
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example above illustrates the logic; in n8n, you'd likely map the &lt;code&gt;last_processed_dates&lt;/code&gt; from a database query node into the Function node, and the &lt;code&gt;dataset_items&lt;/code&gt; would be the output of the Apify "Get Dataset Items" node. This modularity makes n8n a powerful orchestrator for complex data flows originating from Apify Actors.&lt;/p&gt;

&lt;h2&gt;
  
  
  When does Apify's synchronous run endpoint time out, and what should you use instead?
&lt;/h2&gt;

&lt;p&gt;Apify's synchronous run endpoint hard-caps at 300 seconds (5 minutes) and returns an HTTP 408 "Request Timeout" error if the Actor run exceeds this duration. For runs expected to last longer, you should use the asynchronous run endpoint (&lt;code&gt;POST /v2/acts/&amp;lt;actorId&amp;gt;/runs&lt;/code&gt;) and implement polling for status updates or configure a webhook to receive notifications upon run completion.&lt;/p&gt;

&lt;p&gt;Many data scraping tasks, especially those involving multiple profiles or a high &lt;code&gt;maxPosts&lt;/code&gt; count, will naturally exceed the 5-minute synchronous limit. Attempting to use the synchronous endpoint for such runs will lead to unreliable behavior and frequent timeouts.&lt;/p&gt;

&lt;p&gt;The asynchronous approach involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Initiating the Run:&lt;/strong&gt; Make a &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;/v2/acts/&amp;lt;actorId&amp;gt;/runs&lt;/code&gt; endpoint. This immediately returns a &lt;code&gt;runId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Monitoring the Run:&lt;/strong&gt; You have two main options for monitoring:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Polling:&lt;/strong&gt; Periodically (&lt;code&gt;GET /v2/acts/&amp;lt;actorId&amp;gt;/runs/&amp;lt;runId&amp;gt;&lt;/code&gt;) to check the &lt;code&gt;status&lt;/code&gt; field. Continue polling until the status is &lt;code&gt;SUCCEEDED&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt;, &lt;code&gt;ABORTED&lt;/code&gt;, or &lt;code&gt;TIMED_OUT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Webhooks:&lt;/strong&gt; The more efficient and recommended method. When you start the Actor run, you can specify a &lt;code&gt;webhook&lt;/code&gt; URL in the request payload. Apify will then &lt;code&gt;POST&lt;/code&gt; to this URL with the run details when it completes. This is ideal for integrating with services like n8n, which has a dedicated Apify Trigger node that acts as a webhook listener.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Retrieving Results:&lt;/strong&gt; Once the run has completed, use the &lt;code&gt;defaultDatasetId&lt;/code&gt; from the run details to fetch the scraped data from the dataset.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This asynchronous pattern is standard practice for long-running operations in distributed systems. It prevents your client from blocking indefinitely and provides a resilient mechanism for handling unpredictable execution times.&lt;/p&gt;

&lt;p&gt;Here's a &lt;code&gt;bash&lt;/code&gt; example for starting an asynchronous run and an example of its webhook payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.apify.com/v2/acts/crawlerbros~instagram-profile-scraper/runs?token&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_APIFY_API_TOKEN&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "usernames": ["instagram", "natgeo"],
    "maxPosts": 50,
    "webhook": {
      "requestUrl": "https://your.n8n.webhook.url/webhook/endpoint",
      "eventTypes": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED"],
      "payloadTemplate": "{\"runId\": \"{{run.id}}\", \"defaultDatasetId\": \"{{run.defaultDatasetId}}\", \"status\": \"{{run.status}}\"}"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an example of the JSON payload that Apify would send to your webhook URL upon run completion (as defined by &lt;code&gt;payloadTemplate&lt;/code&gt; above):&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;"runId"&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_RUN_ID_ABCDEF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"defaultDatasetId"&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_DATASET_ID_12345"&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;"SUCCEEDED"&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;This JSON payload provides the necessary identifiers to then fetch the results from the correct dataset, all without your client having to wait or poll.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Caveats
&lt;/h2&gt;

&lt;p&gt;While the &lt;code&gt;instagram-profile-scraper&lt;/code&gt; is a powerful tool, it's crucial to be aware of its inherent limitations and specific platform behaviors to build a truly robust pipeline.&lt;/p&gt;

&lt;p&gt;Firstly, the Actor operates on publicly available data. If an Instagram account is private, the Actor returns the available profile metadata but &lt;strong&gt;cannot scrape any posts&lt;/strong&gt;. This is by design, respecting Instagram's privacy settings, and is reflected by the &lt;code&gt;is_private&lt;/code&gt; field in the &lt;code&gt;author_meta&lt;/code&gt; object. Your pipeline must account for these cases, perhaps by filtering out private profiles or marking them for manual review.&lt;/p&gt;

&lt;p&gt;Secondly, specific business contact details like &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;phone&lt;/code&gt;, and &lt;code&gt;category&lt;/code&gt; are &lt;strong&gt;only included in the output when Instagram exposes them on the profile itself&lt;/strong&gt;. The README clearly states that if Instagram's internal API errors for a particular account, these fields might be absent, even if other &lt;code&gt;author_meta&lt;/code&gt; fields are present. This means you cannot reliably expect these fields for every business profile. Your data model and downstream applications should treat these fields as optional and nullable.&lt;/p&gt;

&lt;p&gt;Thirdly, Instagram's profile API doesn't return complete data for every account. In a small number of cases, Instagram's backend may error, causing the Actor to fall back to the public profile page. While this still provides follower/following counts, bio, and verification status, it will result in the absence of business contact details (&lt;code&gt;email&lt;/code&gt;, &lt;code&gt;phone&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;). This is an Instagram-side restriction with no workaround, meaning the Actor always returns the fullest data it can retrieve, but that "fullest" can vary.&lt;/p&gt;

&lt;p&gt;Finally, while the Actor handles batching &lt;code&gt;usernames&lt;/code&gt; efficiently within a single run, it has a &lt;code&gt;maxPosts&lt;/code&gt; limit of 500 posts per profile. If you need more than 500 posts from a single profile, you would have to run the Actor multiple times, potentially using a more advanced pagination strategy that tracks previously scraped posts and filters new requests based on &lt;code&gt;pub_date&lt;/code&gt; or &lt;code&gt;shortcode&lt;/code&gt; to avoid duplicates and adhere to the state management principles discussed earlier. However, the schema does not natively expose a way to resume from a specific post cursor or date, so custom post-processing is essential for large historical scrapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Building reliable data pipelines that integrate external scraping tools requires careful consideration of not just data extraction, but also orchestration, state management, and cost optimization. The &lt;code&gt;instagram-profile-scraper&lt;/code&gt; Actor offers a robust way to collect public Instagram profile and post data, but its true power is unlocked when you wire it into a broader system that accounts for its specific input/output patterns and platform constraints.&lt;/p&gt;

&lt;p&gt;By understanding the pay-per-event pricing model, leveraging managed sessions for operational simplicity, employing asynchronous run patterns for longer jobs, and diligently managing state to avoid reprocessing, data engineers can construct efficient and cost-effective pipelines. Handling non-existent or private profiles gracefully ensures pipeline resilience, while integrating with tools like n8n streamlines post-processing and downstream delivery. This holistic approach moves beyond mere data collection to building sustainable and intelligent data workflows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Actor's README is the source of truth for its inputs, outputs and limits. Need a hand wiring this into your stack? Email &lt;a href="mailto:info@crawlerbros.com"&gt;info@crawlerbros.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why Strict Filters Return Empty Results on Google Suggest Scraper</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Tue, 22 Sep 2026 10:58:25 +0000</pubDate>
      <link>https://dev.to/crawlerbros/why-strict-filters-return-empty-results-on-google-suggest-scraper-2oa6</link>
      <guid>https://dev.to/crawlerbros/why-strict-filters-return-empty-results-on-google-suggest-scraper-2oa6</guid>
      <description>&lt;p&gt;Data engineering, at its core, is about resilience. We build systems that expect failure, predict its shape, and gracefully recover. When working with external data sources, especially those that abstract away underlying complexities like proxy management and rate limiting, understanding specific failure modes is paramount. The documentation often hints at these, but it rarely spells out the defensive coding strategies needed to handle them.&lt;/p&gt;

&lt;p&gt;Let's examine the Google Keywords Suggest Scraper Pro, an Actor on Apify for extracting autocomplete suggestions. Rather than a feature tour, we'll dissect its input schema, output shape, and platform constraints to identify implied failure modes and how to engineer against them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Input Constraints Mean for Run Failures
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;google-keywords-suggest-scraper-pro&lt;/code&gt; Actor exposes a clear input schema, which, while helpful for defining valid parameters, also implicitly defines several ways a run can fail or return unexpected data. The &lt;code&gt;keywords&lt;/code&gt; field, for instance, is an array of strings and is explicitly marked &lt;code&gt;[required]&lt;/code&gt;. Submitting an empty array or omitting this field will cause an immediate validation error. However, a more subtle failure mode arises when the keywords provided are highly obscure or niche, particularly when combined with restrictive filters.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;minLength&lt;/code&gt; and &lt;code&gt;maxLength&lt;/code&gt; integer fields, which drop suggestions outside a character range, are powerful but also dangerous. If &lt;code&gt;minLength&lt;/code&gt; is set too high (e.g., &lt;code&gt;minLength: 50&lt;/code&gt;) or &lt;code&gt;maxLength&lt;/code&gt; too low (e.g., &lt;code&gt;maxLength: 5&lt;/code&gt;), it's highly probable that many, if not all, legitimate suggestions will be filtered out. Similarly, the &lt;code&gt;containsKeyword&lt;/code&gt; string filter, especially when inverted with &lt;code&gt;!&lt;/code&gt;, can easily eliminate all results.&lt;/p&gt;

&lt;p&gt;The Actor's README explicitly addresses this: "What happens when filters drop everything? The actor finishes without fake rows and sets a status message with the active filters so you can loosen them." This is a crucial piece of information. It means you won't get empty placeholder records, but you &lt;em&gt;will&lt;/em&gt; get an empty dataset. Detecting this defensively requires inspecting the run's metadata or status message, not just the dataset's item count.&lt;/p&gt;

&lt;p&gt;Here’s a Python example demonstrating a valid but potentially self-defeating input, which you’d submit to the Apify API:&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;apify_client&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;run_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;an extremely obscure keyword that probably nobody searches&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;mode&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;all&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&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;minLength&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Highly restrictive, likely to drop everything
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxLength&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Equally restrictive
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;containsKeyword&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;!commonword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Invert filter, drop anything containing 'commonword'
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxItemsPerKeyword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&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;flat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Not executing the run here, just showing the input structure
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Does a Synchronous Run Timeout Affect Data Extraction?
&lt;/h2&gt;

&lt;p&gt;Synchronous API calls to an Actor, where you await completion, have a hard cap. Apify's synchronous run endpoint returns an HTTP 408 if the run exceeds 300 seconds (5 minutes). This means jobs expected to run longer than 5 minutes must use the asynchronous endpoint.&lt;/p&gt;

&lt;p&gt;This is a critical limitation for larger extraction jobs. If you submit a request with a high number of &lt;code&gt;keywords&lt;/code&gt; and &lt;code&gt;mode&lt;/code&gt; set to &lt;code&gt;alphabet&lt;/code&gt; (which generates 26 sub-queries per seed keyword), or if you set &lt;code&gt;maxItemsPerKeyword&lt;/code&gt; to a high value like 5000, the run duration can easily exceed this 300-second threshold. When this cap is hit, your application will receive a timeout error, even if the Actor itself continues to run in the background. Your code might interpret this as a complete failure, leading to retries or incomplete data processing. To handle this, any job expected to run longer than 5 minutes &lt;em&gt;must&lt;/em&gt; use the asynchronous &lt;code&gt;POST /v2/acts/&amp;lt;actorId&amp;gt;/runs&lt;/code&gt; endpoint, followed by polling the run status or using webhooks.&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;apify_client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example input that could potentially exceed 300 seconds
&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;long tail keyword &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="c1"&gt;# Many keywords
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&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;alphabet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 26 expansions per keyword
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&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;maxItemsPerKeyword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&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;flat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Start the Actor asynchronously
&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;crawlerbros/google-keywords-suggest-scraper-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_for_finish&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;run_id&lt;/span&gt; &lt;span class="o"&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;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;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;Actor run started with ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_id&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;# Poll for run completion (simplified example, consider exponential backoff)
&lt;/span&gt;&lt;span class="k"&gt;while&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;run_status&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;run_status&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;SUCCEEDED&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;Run succeeded!&lt;/span&gt;&lt;span class="sh"&gt;"&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;elif&lt;/span&gt; &lt;span class="n"&gt;run_status&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="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABORTED&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="s"&gt;Run failed or aborted: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusMessage&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;break&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;Run status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_status&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Waiting...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Wait 30 seconds before polling again
&lt;/span&gt;
&lt;span class="c1"&gt;# At this point, you'd fetch the dataset items
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Does Actor Memory Affect Billing and Performance?
&lt;/h2&gt;

&lt;p&gt;The "Actor Start" event in the pricing for the Google Keywords Suggest Scraper Pro is charged at $0.005 per GB of memory allocated to the run, with a minimum of one event. This means higher memory usage leads to increased "Actor Start" costs.&lt;/p&gt;

&lt;p&gt;This is a subtle but important detail. While this Actor is unlikely to be extremely memory-intensive for typical use cases, it’s worth noting that if your input &lt;code&gt;keywords&lt;/code&gt; array becomes extremely large, requiring significant internal processing or data structures, the system might allocate more memory, thus increasing the "Actor Start" cost. This isn't a direct failure mode but an implicit cost escalation you might not predict if you're only focused on the &lt;code&gt;result&lt;/code&gt; events. Monitoring memory usage for your Actor runs, especially for large-scale operations, can help optimize costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Partial or Sentinel Values in the Output Shape?
&lt;/h2&gt;

&lt;p&gt;The output schema for the Google Keywords Suggest Scraper Pro provides two formats: &lt;code&gt;flat&lt;/code&gt; and &lt;code&gt;tree&lt;/code&gt;. Understanding the nuances of these is crucial for robust data processing. In &lt;code&gt;flat&lt;/code&gt; mode, each record is a single suggestion. Key fields include &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;source_keyword&lt;/code&gt;, &lt;code&gt;expansion_query&lt;/code&gt;, &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;language&lt;/code&gt;, and &lt;code&gt;relevance&lt;/code&gt;. In &lt;code&gt;tree&lt;/code&gt; mode, one record is emitted per &lt;code&gt;source_keyword&lt;/code&gt;, containing a &lt;code&gt;suggestions&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;Consider the &lt;code&gt;relevance&lt;/code&gt; field. It's an integer, typically around 600, with higher values indicating more popularity. While this is a useful proxy for search volume, it's not the actual volume. Expecting search volume here would be a misinterpretation. The Actor explicitly states, "Volume requires Google Ads Keyword Planner (paid + OAuth). This actor returns Suggest's &lt;code&gt;relevance&lt;/code&gt; score... which is a useful proxy." This means &lt;code&gt;relevance&lt;/code&gt; is a &lt;em&gt;sentinel value&lt;/em&gt; – a proxy that tells you something, but not the exact metric you might be accustomed to from other SEO tools.&lt;/p&gt;

&lt;p&gt;Another important field is &lt;code&gt;expansion_query&lt;/code&gt;. The README clarifies: "It's the generated query that was sent to Google Suggest for that batch of results... Google can still return nearby variants, so the suggestion text is not guaranteed to start with that exact string." This means you cannot assume &lt;code&gt;text&lt;/code&gt; will always be a direct prefix of &lt;code&gt;expansion_query&lt;/code&gt;. Your downstream processing should account for &lt;code&gt;text&lt;/code&gt; being related but not necessarily strictly matching &lt;code&gt;expansion_query&lt;/code&gt; in a prefix sense.&lt;/p&gt;

&lt;p&gt;Here's an example of &lt;code&gt;flat&lt;/code&gt; output, showing these fields:&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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"suggestion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"python tutorial for beginners"&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_keyword"&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 tutorial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expansion_query"&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 tutorial f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alphabet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"relevance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suggestion_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;"QUERY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub_types"&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="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;],&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-04-29T10:30:00.000Z"&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;When consuming this data, you might want to filter or categorize based on &lt;code&gt;relevance&lt;/code&gt;. For instance, dropping suggestions below a certain &lt;code&gt;relevance&lt;/code&gt; score could be a valid data cleaning step, but it must be understood as filtering by a proxy, not an absolute volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Are Proxy Sessions Important for Consistent Data?
&lt;/h2&gt;

&lt;p&gt;Proxy session management is crucial for other Apify Actors that utilize them, though not explicitly exposed in this Actor. While the Google Keywords Suggest Scraper Pro doesn't require proxies, understanding their behavior is vital for robust data pipelines using other Actors.&lt;/p&gt;

&lt;p&gt;Datacenter proxies persist for around 26 hours, while residential proxies typically last only about 30 minutes. If you were to build a system that relies on persistent sessions for a different Actor, a residential proxy's short lifespan could lead to session drops and failed requests, even if the Actor itself attempts to manage retries. For this Actor, the lack of explicit proxy control means less configuration overhead but also less insight into potential transient network issues that Google's suggestion service might encounter globally. The Apify platform's ability to target specific US states with &lt;code&gt;country-US_XX&lt;/code&gt; for proxies (though not exposed in this Actor's input) highlights the granularity available for geographic targeting. When a future Actor &lt;em&gt;does&lt;/em&gt; expose proxy configuration, knowing these limits and capabilities prevents unexpected failures due to proxy expiration or geo-restriction issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Avoid Unnecessary Costs and Data Loss on the Free Tier?
&lt;/h2&gt;

&lt;p&gt;Understanding the cost model and storage retention policies is crucial for managing data pipelines, especially on the Apify free plan. The Google Keywords Suggest Scraper Pro Actor, available at &lt;a href="https://apify.com/crawlerbros/google-keywords-suggest-scraper-pro" rel="noopener noreferrer"&gt;https://apify.com/crawlerbros/google-keywords-suggest-scraper-pro&lt;/a&gt;, charges per event: "$0.002 per event" for "result" (a single record in the default dataset) and "$0.005 per GB of memory allocated to the run" for "Actor Start".&lt;/p&gt;

&lt;p&gt;Cost scales directly with the number of &lt;code&gt;result&lt;/code&gt; records. A run with &lt;code&gt;keywords=["python tutorial"]&lt;/code&gt;, &lt;code&gt;mode="alphabet"&lt;/code&gt;, &lt;code&gt;maxItemsPerKeyword=200&lt;/code&gt; would generate up to 260 suggestions (10 * 26). If it produced 200 suggestions, that would be 200 "result" events. For a free-tier user, this would be &lt;code&gt;200 * $0.002 = $0.40&lt;/code&gt;, plus the "Actor Start" cost. For users on volume tiers, prices drop: BRONZE $0.00167, SILVER $0.00133, GOLD $0.001, PLATINUM $0.001, DIAMOND $0.001 per result event.&lt;/p&gt;

&lt;p&gt;To avoid data loss on the free plan, remember that only the 10 most recent runs are retained for four months. After that, unnamed storages (like the default dataset) expire. If you need to persist data longer or across more than 10 runs, you &lt;em&gt;must&lt;/em&gt; use named storages or export your data regularly. Named storages are exempt from deletion. This is a common pitfall for new users who assume all data is permanently saved.&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;apify_client&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example of fetching dataset items after a run, and considering named storage
# Assuming 'my_named_dataset' is a dataset you created with a specific name
# If you didn't specify a named dataset in the Actor run, it uses the default (unnamed)
&lt;/span&gt;&lt;span class="n"&gt;dataset_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;my_named_dataset_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with actual named dataset ID if used
&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;dataset_items&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;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&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;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApifyApiError&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="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;Error fetching dataset items: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Check if dataset exists or is expired.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# To proactively save data to a named storage in a run:
# You'd typically set the datasetId in the ApifyClient.call() or Apify.main() options
# This example is illustrative of the concept.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Are Apify Schedules and Webhooks Reliable for Event-Driven Pipelines?
&lt;/h2&gt;

&lt;p&gt;Yes, but with caveats. Apify schedules use a 6-field cron syntax (seconds optional), with a minimum interval of 10 seconds. However, new schedules are created DISABLED by default, and an Actor must have run at least once before it can be scheduled. These are critical setup steps that, if missed, will prevent your scheduled pipelines from triggering. Always enable the schedule and ensure a successful manual run has occurred.&lt;/p&gt;

&lt;p&gt;Webhooks provide an event-driven primitive, POSTing to a URL on specific run events (like completion). This is the &lt;em&gt;only&lt;/em&gt; direct integration for eventing. If you need to push data to Slack or S3, you must route it through a webhook to an intermediary service like n8n, Make, or Zapier, or a custom backend. There is no native S3 or Slack integration directly from Apify.&lt;/p&gt;

&lt;p&gt;n8n offers a "Trigger node" that fires on run completion, eliminating the need for polling. This is a powerful feature for building low-latency, event-driven workflows. For n8n Cloud users, OAuth2 credentials are supported, while self-hosted n8n instances can use API keys.&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;"eventTypes"&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;"ACTOR.RUN.SUCCEEDED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACTOR.RUN.FAILED"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requestUrl"&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://your-webhook-endpoint.com/receive-apify-data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payloadTemplate"&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="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;runId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {{run.id}}, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{run.status}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;datasetId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{run.defaultDatasetId}}&lt;/span&gt;&lt;span class="se"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This JSON represents a webhook configuration that would notify an external endpoint about run success or failure, including the run ID and default dataset ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Real Limitations and Caveats of the Google Keywords Suggest Scraper Pro?
&lt;/h2&gt;

&lt;p&gt;The Google Keywords Suggest Scraper Pro Actor is highly effective for its stated purpose but has inherent limitations based on Google Suggest itself and the platform.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;No Direct Search Volume Data&lt;/strong&gt;: As noted, it returns a &lt;code&gt;relevance&lt;/code&gt; score, not actual search volumes. If your primary use case requires precise volume metrics, you'll need to augment this data with a Google Ads Keyword Planner integration, which is a separate, paid service with OAuth requirements.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Deep SERP Analysis&lt;/strong&gt;: This Actor focuses solely on autocomplete suggestions. It doesn't scrape full search engine results pages (SERPs), nor does it provide features like "People Also Ask" boxes or related searches shown on the SERP. Those would require a different, more complex scraper.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Rate Limiting Handled Internally&lt;/strong&gt;: While the Actor handles Google's rate limiting, you don't have explicit control over the pacing of requests. This is generally a benefit, but if you have very specific rate-limiting needs or want to integrate with a custom proxy solution, this Actor's abstraction limits that flexibility.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Complex Query Logic&lt;/strong&gt;: The &lt;code&gt;mode&lt;/code&gt; field provides specific expansion patterns (&lt;code&gt;questions&lt;/code&gt;, &lt;code&gt;prepositions&lt;/code&gt;, &lt;code&gt;alphabet&lt;/code&gt;, etc.). There's no way to pass arbitrary custom prefixes or suffixes beyond what the modes offer, nor complex boolean logic for keyword generation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Single Request Queue Consumer&lt;/strong&gt;: A crucial Apify platform detail is that a request queue can only be PROCESSED by one Actor or task run at a time. While multiple runs can &lt;em&gt;add&lt;/em&gt; to a queue, you cannot fan out processing across a single shared queue. If you have a massive list of keywords to process in parallel, you would need to create multiple input queues or use different mechanisms for load distribution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These aren't necessarily "failures" of the Actor, but rather design choices and platform constraints that define its scope and how it interacts with the broader Apify ecosystem. Understanding them prevents misaligned expectations and guides architectural decisions for integrating the data.&lt;/p&gt;

&lt;p&gt;Checked against the Actor's input schema and Apify docs on 2026-09-22.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Actor's README is the source of truth for its inputs, outputs and limits. Need a hand wiring this into your stack? Email &lt;a href="mailto:info@crawlerbros.com"&gt;info@crawlerbros.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Scraping Missouri Business Entities Without Cold-Session Blocks</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Tue, 22 Sep 2026 08:05:28 +0000</pubDate>
      <link>https://dev.to/crawlerbros/scraping-missouri-business-entities-without-cold-session-blocks-1gcl</link>
      <guid>https://dev.to/crawlerbros/scraping-missouri-business-entities-without-cold-session-blocks-1gcl</guid>
      <description>&lt;p&gt;Automating corporate entity verification across Secretary of State portals presents distinct infrastructure challenges. In Missouri, the Business Entity Search system (&lt;code&gt;bsd.sos.mo.gov&lt;/code&gt;) hosts public corporate records without requiring an account or a CAPTCHA. However, automated collection pipelines often fail when querying records directly because the underlying ASP.NET WebForms backend enforces session continuity. Fetching a detail page (&lt;code&gt;BusinessEntityDetail.aspx&lt;/code&gt;) cold—without the session cookies established during the initial search query—triggers an immediate "Access Denied" response.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/crawlerbros/missouri-sos-business-search-scraper" rel="noopener noreferrer"&gt;Missouri Secretary of State Business Search Scraper&lt;/a&gt; handles session persistence across searches and detail lookups while managing Cloudflare IP-reputation denials via proxy session rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cold-Session Bottleneck on Missouri SOS
&lt;/h2&gt;

&lt;p&gt;Missouri's business entity portal uses classic ASP.NET WebForms architecture. When a user executes a search, the portal writes specific session state to the browser. If an automated script parses the search results grid and attempts to request individual entity detail pages using isolated, stateless HTTP clients, the server blocks those requests. &lt;/p&gt;

&lt;p&gt;Furthermore, the portal sits behind a Cloudflare IP-reputation gate. Unlike JavaScript challenges or reCAPTCHA barriers, this gate responds with a flat HTTP "Access Denied" when a shared datacenter IP has accumulated too much traffic. A cold fetch to a detail page exacerbates this issue.&lt;/p&gt;

&lt;p&gt;To scrape full entity details reliably, an automation system must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maintain session cookies from the initial search POST request through every subsequent detail-page fetch.&lt;/li&gt;
&lt;li&gt;Catch "Access Denied" reputation blocks and rotate datacenter proxy IPs on demand.&lt;/li&gt;
&lt;li&gt;Traverse the 20-row-per-page ASP.NET pagination state until hitting a user-defined record cap.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Search Axes and Core Input Parameters
&lt;/h2&gt;

&lt;p&gt;The actor interfaces directly with the state's public search endpoints through two primary lookup modes and exact-to-broad matching rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"byBusinessName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"searchTerm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AMAZON CAPITAL SERVICES, INC."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nameSearchMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exactMatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"activeOnly"&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;"maxItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key configuration properties in the input schema include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mode&lt;/code&gt;: Accepts &lt;code&gt;byBusinessName&lt;/code&gt; to query registered corporate entities or &lt;code&gt;byRegisteredAgent&lt;/code&gt; to search against designated commercial or organizational agents.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;searchTerm&lt;/code&gt;: The target string (for example, a company name or agent name).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nameSearchMethod&lt;/code&gt;: Maps directly to the underlying ASP.NET dropdown options: &lt;code&gt;startingWith&lt;/code&gt;, &lt;code&gt;allWords&lt;/code&gt;, &lt;code&gt;anyWord&lt;/code&gt;, or &lt;code&gt;exactMatch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;activeOnly&lt;/code&gt;: A boolean flag that restricts returned records to entities currently in Good Standing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxItems&lt;/code&gt;: An integer (ranging from 1 to 200) setting a hard ceiling on extracted records.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Execution Workflow
&lt;/h3&gt;

&lt;p&gt;To run a search pipeline targeting active entities matching a specific naming pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure the search payload defining the target string, search axis, and match strategy.&lt;/li&gt;
&lt;li&gt;Initialize the run using the Apify Python Client:
&lt;/li&gt;
&lt;/ol&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;mode&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;byBusinessName&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;searchTerm&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;LOGISTICS&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;nameSearchMethod&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;startingWith&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;activeOnly&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;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&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;crawlerbros/missouri-sos-business-search-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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;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;charterNumber&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="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;entityName&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="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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The scraper accesses &lt;code&gt;bsd.sos.mo.gov&lt;/code&gt;, parses the grid, captures the internal IDs, and fetches the detail view for each row under the same session.&lt;/li&gt;
&lt;li&gt;If a detail page fails after internal retries, the actor preserves the high-level metadata (charter number, entity name, registration date, status) from the initial results grid rather than discarding the record.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Output Schema and Field Mapping
&lt;/h2&gt;

&lt;p&gt;Each emitted dataset record contains full corporate identity data extracted from the detail page:&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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"entity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entityName"&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 LOGISTICS LLC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"charterNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LC01429811"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entityType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Domestic Limited Liability Company"&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;"Good Standing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"domesticity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Domestic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"homeState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Missouri"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dateFormed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2018-04-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Perpetual"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"principalAddress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100 MAIN ST, KANSAS CITY, MO 64105"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registeredAgentName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CORPORATE CREATIONS NETWORK INC."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registeredAgentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RA0012948"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registeredAgentAddress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2847 S INGRAM MILL RD STE A-100, SPRINGFIELD, MO 65804"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"internalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12984711"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceUrl"&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://bsd.sos.mo.gov/BusinessEntity/BusinessEntityDetail.aspx?ID=12984711"&lt;/span&gt;&lt;span class="p"&gt;,&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;"2025-02-17T12:00:00.000Z"&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;Optional attributes such as &lt;code&gt;reportDue&lt;/code&gt; are emitted when present on the state's record and omitted when the field is empty on the source page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing and Cost Mechanics
&lt;/h2&gt;

&lt;p&gt;Billing for this scraper uses a flat pay-per-event pricing model. Charges are tied directly to run events rather than execution duration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start&lt;/strong&gt; (&lt;code&gt;apify-actor-start&lt;/code&gt;): $0.005 per GB of memory allocated to the run, billed once when execution begins (minimum 1 GB).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result Item&lt;/strong&gt; (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;): $0.005 per entity result emitted into the default dataset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under volume tiers, the per-result event price scales as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FREE: $0.005&lt;/li&gt;
&lt;li&gt;BRONZE: $0.00433&lt;/li&gt;
&lt;li&gt;SILVER: $0.00367&lt;/li&gt;
&lt;li&gt;GOLD: $0.003&lt;/li&gt;
&lt;li&gt;PLATINUM: $0.003&lt;/li&gt;
&lt;li&gt;DIAMOND: $0.003&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A 1 GB run yielding 50 records at the base tier costs $0.005 (Actor Start) + 50 × $0.005 (results) = $0.255.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Boundaries
&lt;/h2&gt;

&lt;p&gt;This scraper does not parse historical document filings or retrieve PDF copies of annual reports from the state archive. Additionally, for registered agent queries, the tool pulls the representative entity returned by Missouri's primary search grid rather than expanding the nested multi-entity index associated with large corporate agents. Large runs targeting high-volume registered agents require configuring higher run timeouts to accommodate IP rotation delays across repeated detail lookups.&lt;/p&gt;




&lt;p&gt;Runs in this article used &lt;a href="https://apify.com/crawlerbros/missouri-sos-business-search-scraper" rel="noopener noreferrer"&gt;Missouri Secretary of State Business Search Scraper&lt;/a&gt;. Its README is the reference for input fields and output structure; this post is only one path through them.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>programming</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Handling Imperva Cookie Handshakes for Massachusetts Entity Data</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Tue, 22 Sep 2026 04:01:44 +0000</pubDate>
      <link>https://dev.to/crawlerbros/handling-imperva-cookie-handshakes-for-massachusetts-entity-data-3a96</link>
      <guid>https://dev.to/crawlerbros/handling-imperva-cookie-handshakes-for-massachusetts-entity-data-3a96</guid>
      <description>&lt;p&gt;Automating business verification for Massachusetts entities often runs into a specific infrastructural barrier. The Massachusetts Corporations Division search (&lt;code&gt;corp.sec.state.ma.us&lt;/code&gt;) hosts registry data—entity type, registered agent name, principal office address, and active officers. However, the search form sits behind Imperva/Incapsula protection. &lt;/p&gt;

&lt;p&gt;Unlike registry portals that deploy full JavaScript execution walls (such as Nevada's SilverFlume portal, which returns a static 1 KB &lt;code&gt;_Incapsula_Resource&lt;/code&gt; iframe on raw HTTP requests), the Massachusetts registry operates protection primarily at the cookie level. Standard headless browser setups add unnecessary overhead when pure HTTP session warming can solve the access problem directly.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/crawlerbros/massachusetts-sos-scraper" rel="noopener noreferrer"&gt;massachusetts-sos-scraper&lt;/a&gt; actor bypasses this barrier over HTTP, collecting registry data and structuring it for downstream data pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bypassing Cookie Challenges Over Plain HTTP
&lt;/h2&gt;

&lt;p&gt;When a standard HTTP client hits the Massachusetts search portal for the first time, Imperva returns an interstitial response. If your scraper does not maintain the session state and evaluate cookie headers, subsequent search requests fail or get dropped.&lt;/p&gt;

&lt;p&gt;The scraper handles this protocol interaction automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It sends an initial warm-up HTTP request to capture the seeded &lt;code&gt;visid_incap_*&lt;/code&gt; and &lt;code&gt;incap_ses_*&lt;/code&gt; session cookies.&lt;/li&gt;
&lt;li&gt;It re-uses that initialized session for subsequent form submissions against &lt;code&gt;corp.sec.state.ma.us&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It queries the registry search endpoints and parses each entity summary page (&lt;code&gt;CorpSummary.aspx&lt;/code&gt;) without requiring a headless browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because every search match requires an independent detail-page request to extract complete records (such as officer tables and merger history), the actor uses deliberate delays between requests and falls back to free datacenter (AUTO) proxies if the target endpoint applies temporary rate limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Axes and Filtering Modes
&lt;/h2&gt;

&lt;p&gt;The scraper accepts four operational modes via the &lt;code&gt;mode&lt;/code&gt; parameter: &lt;code&gt;byEntityName&lt;/code&gt;, &lt;code&gt;byIndividualName&lt;/code&gt;, &lt;code&gt;byIdentificationNumber&lt;/code&gt;, and &lt;code&gt;byFilingNumber&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"byEntityName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entityName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Logistics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entityMatchMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&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="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;
  
  
  Entity Name Matching
&lt;/h3&gt;

&lt;p&gt;When querying with &lt;code&gt;mode: "byEntityName"&lt;/code&gt;, the &lt;code&gt;entityMatchMode&lt;/code&gt; parameter controls how the Massachusetts backend filters company records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;B&lt;/code&gt; (Begins with): Returns entities starting with the search string.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;M&lt;/code&gt; (Exact match): Restricts results to exact legal name matches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;F&lt;/code&gt; (Full text): Finds the query substring anywhere in the registered legal name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;S&lt;/code&gt; (Soundex): Matches phonetically similar company names.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Individual Lookups for Officers and Agents
&lt;/h3&gt;

&lt;p&gt;When auditing individuals or mapping corporate networks, setting &lt;code&gt;mode: "byIndividualName"&lt;/code&gt; queries the registry's officer and registered agent index. The search requires &lt;code&gt;lastName&lt;/code&gt;, with optional narrowing via &lt;code&gt;firstName&lt;/code&gt; and &lt;code&gt;middleName&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"byIndividualName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SMITH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JOHN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"individualMatchMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&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;Because individual search results return an entry for every distinct appointment across all registered entities, common surnames yield dozens of separate corporate records. Using &lt;code&gt;individualMatchMode: "M"&lt;/code&gt; prevents unbounded result lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Derived Inactivity Status and Data Structure
&lt;/h2&gt;

&lt;p&gt;The Massachusetts portal does not provide a standard &lt;code&gt;status: "Active"&lt;/code&gt; string on its summary page. Instead, inactive corporations display an explicit inactivity label (such as &lt;code&gt;Date of Involuntary Dissolution by Court Order or by the SOC&lt;/code&gt;) alongside a dissolution date, while active entities leave this section empty.&lt;/p&gt;

&lt;p&gt;The scraper normalizes this quirk into structured fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;: Outputs either &lt;code&gt;"Active"&lt;/code&gt; or &lt;code&gt;"Inactive"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;statusReason&lt;/code&gt;: Contains the exact dissolution or revocation string provided by the state when inactive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;statusDate&lt;/code&gt;: Formatted ISO date (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;) when inactive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;officers&lt;/code&gt;: Array of objects containing &lt;code&gt;{ title, name, address }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mergedWith&lt;/code&gt;: Array of absorbed entities (&lt;code&gt;{ entityName, date, sourceUrl }&lt;/code&gt;), linking directly to the merged entity's historical summary page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;previousNames&lt;/code&gt;: Historical record of legal name changes (&lt;code&gt;{ previousName, date }&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Empty fields are omitted from the resulting dataset items to keep payloads compact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Scraper via the Apify Python Client
&lt;/h2&gt;

&lt;p&gt;You can run this extraction pipeline programmatically using Python to pull data directly into your ETL flow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the client library:
&lt;/li&gt;
&lt;/ol&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;ol&gt;
&lt;li&gt;Initialize the client and run a targeted search:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="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;mode&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;byIdentificationNumber&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;identificationNumber&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;000445089&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;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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;crawlerbros/massachusetts-sos-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dataset_items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataset_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;Entity: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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;entityName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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="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;entity&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;statusReason&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;N/A&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="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="s"&gt;Agent: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&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;registeredAgentName&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;officer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entity&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;officers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;officer&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="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;officer&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pricing and Cost Calculation
&lt;/h2&gt;

&lt;p&gt;The actor uses a pay-per-event pricing model based strictly on executed events.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start&lt;/strong&gt; (&lt;code&gt;apify-actor-start&lt;/code&gt;): $0.005 per GB of memory allocated to the run (billed once upon run initialization).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt; (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;): Billed per record written to the dataset.

&lt;ul&gt;
&lt;li&gt;FREE: $0.005 per result&lt;/li&gt;
&lt;li&gt;BRONZE: $0.00433 per result&lt;/li&gt;
&lt;li&gt;SILVER: $0.00367 per result&lt;/li&gt;
&lt;li&gt;GOLD: $0.003 per result&lt;/li&gt;
&lt;li&gt;PLATINUM: $0.003 per result&lt;/li&gt;
&lt;li&gt;DIAMOND: $0.003 per result&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, on the FREE tier, running a batch job that initializes with 1 GB of memory ($0.005) and extracts 100 entity records ($0.50) costs a total of $0.505.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Constraints
&lt;/h2&gt;

&lt;p&gt;This scraper does not download original PDF filings or annual report documents, as raw document retrieval requires the Secretary of the Commonwealth's paid certificate portal. Furthermore, the &lt;code&gt;maxItems&lt;/code&gt; property is strictly capped at 100 items per run to prevent registry endpoint blocks during multi-page entity traversals. For large corporate directories, batch your inputs by distinct entity numbers or narrow prefixes rather than executing broad open-ended queries.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://apify.com/crawlerbros/massachusetts-sos-scraper" rel="noopener noreferrer"&gt;Massachusetts Secretary of State Business Search Scraper&lt;/a&gt; is the Actor behind these examples. If a selector in your own version breaks, compare your output against the fields listed in its README first.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>webdev</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Extracting B2B Software Metadata via Shared Catalog Records</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:00:54 +0000</pubDate>
      <link>https://dev.to/crawlerbros/extracting-b2b-software-metadata-via-shared-catalog-records-1h8f</link>
      <guid>https://dev.to/crawlerbros/extracting-b2b-software-metadata-via-shared-catalog-records-1h8f</guid>
      <description>&lt;p&gt;Building B2B competitive intelligence pipelines requires extracting consistent software catalog metrics across pricing models, platform dependencies, and core features. Bypassing front-end scraper protection on product aggregators directly often results in ip blocks or inconsistent DOM parsing. The &lt;a href="https://apify.com/crawlerbros/capterra-software-scraper" rel="noopener noreferrer"&gt;Capterra Software Scraper&lt;/a&gt; handles this by querying the underlying Gartner Digital Markets infrastructure behind Capterra and GetApp, returning structured JSON metadata across product catalogs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolving Capterra Products Without Direct HTML Scraping
&lt;/h2&gt;

&lt;p&gt;Extracting metadata directly from Capterra product pages presents scraping challenges due to aggressive bot mitigation controls. The catalog data powering Capterra is shared across the Gartner Digital Markets network, including GetApp. Instead of parsing heavily protected Capterra HTML directly, this actor targets the shared catalog backend through the GetApp stack while returning URL attributes pointing back to original Capterra listings.&lt;/p&gt;

&lt;p&gt;When executing queries, the actor uses a Google SERP proxy to resolve public product pages. For individual product detail extraction, it routes traffic through residential proxy rotation to ensure high retrieval rates without triggering rate limits.&lt;/p&gt;

&lt;p&gt;The extracted output includes high-value fields such as &lt;code&gt;startingPrice&lt;/code&gt;, &lt;code&gt;pricingPlans&lt;/code&gt;, &lt;code&gt;licensingModel&lt;/code&gt;, &lt;code&gt;features&lt;/code&gt;, &lt;code&gt;integrations&lt;/code&gt;, &lt;code&gt;pros&lt;/code&gt;, &lt;code&gt;cons&lt;/code&gt;, and vendor background data like &lt;code&gt;vendorFoundedYear&lt;/code&gt; and &lt;code&gt;vendorWebsite&lt;/code&gt;. Empty fields are omitted entirely rather than returned as null placeholders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Modes for Direct URLs vs Search Queries
&lt;/h2&gt;

&lt;p&gt;The actor operates in two distinct operational modes determined by the &lt;code&gt;mode&lt;/code&gt; parameter: direct URL ingestion (&lt;code&gt;byProductUrls&lt;/code&gt;) and discovery search (&lt;code&gt;bySearchQueries&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct Extraction with &lt;code&gt;byProductUrls&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When specific software entities are already identified, pass explicit Capterra product URLs. This approach bypasses search resolution entirely, moving directly to catalog data extraction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"byProductUrls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productUrls"&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;"https://www.capterra.com/p/135003/Slack/"&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.capterra.com/p/228385/Notion/"&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;"maxItems"&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="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;
  
  
  Keyword Resolution with &lt;code&gt;bySearchQueries&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For exploratory collection or dynamic catalog building, supply targeted search strings. The actor resolves these broad queries against public product pages before pulling catalog details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bySearchQueries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"searchQueries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Slack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"project management 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;"maxItems"&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="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;maxItems&lt;/code&gt; integer restricts the total number of processed products across the run execution, preventing unexpected dataset inflation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executing the Scraper Programmatically
&lt;/h2&gt;

&lt;p&gt;You can invoke the scraper using the Apify Python SDK. Install the client library first:&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;This Python script initializes the client, configures search queries to capture project management tools, and extracts the results into a pandas DataFrame for analysis:&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_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;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;mode&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;bySearchQueries&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;searchQueries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project management software&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;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="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;crawlerbros/capterra-software-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="c1"&gt;# Fetch results from the run's default dataset
&lt;/span&gt;&lt;span class="n"&gt;dataset_items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&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;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;starting_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;startingPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;features&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | Rating: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | Starts at: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;starting_price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Features count: &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;features&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Structure of the Extracted Catalog Schema
&lt;/h2&gt;

&lt;p&gt;A successfully processed product record returns structured JSON containing company, technical, and commercial parameters:&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;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"135003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Slack"&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;"Slack"&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.capterra.com/p/135003/Slack/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tagline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Where work happens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.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;"reviewCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startingPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8.75"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pricingModel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Per User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"freeTrial"&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;"freeVersion"&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;"primaryCategory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Collaboration Software"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"categories"&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;"Team Communication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Collaboration"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&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;"Chat/Messaging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"File Sharing"&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 Conferencing"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"featureCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"integrationsCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pros"&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;"Ease of use"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extensive integration 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;"cons"&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;"Search indexing limits on free plan"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vendorName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Slack Technologies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vendorCountry"&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"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a product fails to resolve during the search phase, the actor skips emitting the record entirely. It does not generate synthetic or fallback placeholder items.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Structure Analysis
&lt;/h2&gt;

&lt;p&gt;Pricing for this actor operates exclusively under a Pay-Per-Event billing model. You pay strictly for runtime initialization and successfully dataset-emitted items.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start Event (&lt;code&gt;apify-actor-start&lt;/code&gt;)&lt;/strong&gt;: Flat $0.005 per GB of memory allocated to the run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset Result Event (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;)&lt;/strong&gt;: $0.005 per emitted item at the base Free tier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Volume discounts apply to result events for higher platform usage tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BRONZE&lt;/strong&gt;: $0.00433 per item&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SILVER&lt;/strong&gt;: $0.00367 per item&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOLD / PLATINUM / DIAMOND&lt;/strong&gt;: $0.003 per item&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a execution run configured with 1 GB of memory ($0.005) that extracts 100 resolved software products on the base tier ($0.005 x 100 = $0.50) incurs a total run cost of $0.505.&lt;/p&gt;

&lt;p&gt;It is important to note that this actor does not scrape individual end-user review text strings or paginated category listing pages; it focuses exclusively on product-level catalog metadata.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://apify.com/crawlerbros/capterra-software-scraper" rel="noopener noreferrer"&gt;Capterra Software Scraper&lt;/a&gt; is the Actor behind these examples. If a selector in your own version breaks, compare your output against the fields listed in its README first.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>webdev</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Normalizing Luxury Resale Attributes Across 7 Inconsistent Departments</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Mon, 21 Sep 2026 20:01:39 +0000</pubDate>
      <link>https://dev.to/crawlerbros/normalizing-luxury-resale-attributes-across-7-inconsistent-departments-2i2o</link>
      <guid>https://dev.to/crawlerbros/normalizing-luxury-resale-attributes-across-7-inconsistent-departments-2i2o</guid>
      <description>&lt;p&gt;Secondary luxury resale markets do not follow uniform product schemas. If you write scrapers for authenticated resale platforms like The RealReal, you immediately hit schema fragmentation across departments. Clothing uses standard apparel sizes (&lt;code&gt;CLOTHING_SIZE&lt;/code&gt; or &lt;code&gt;FOREIGN_SIZE&lt;/code&gt;), footwear uses shoe sizing (&lt;code&gt;SHOE_SIZE&lt;/code&gt;), jewelry stores dimensions as &lt;code&gt;RING_SIZE&lt;/code&gt; alongside &lt;code&gt;METAL_TYPE&lt;/code&gt;, and watches store physical dimensions under &lt;code&gt;CASE_DIAMETER&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Extracting these disparate properties into a clean analytics pipeline usually requires writing custom normalization logic for each product category. If you try to track resale margins or build pricing models across multiple luxury departments, raw API responses leave critical fields empty unless your scraper resolves department-specific attribute mappings.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/crawlerbros/therealreal-scraper" rel="noopener noreferrer"&gt;therealreal-scraper&lt;/a&gt; Actor solves this by consolidating department-specific attributes across seven top-level departments (women, men, jewelry, watches, art, home, and kids) into unified output fields like &lt;code&gt;size&lt;/code&gt; and &lt;code&gt;material&lt;/code&gt;, while surfacing specialized luxury metadata when present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolving Department-Specific Attributes
&lt;/h2&gt;

&lt;p&gt;When crawling authenticated listings, general-purpose scrapers often drop department-level metadata. The RealReal's internal facet architecture separates fields based on category context. For example, a collector tracking luxury timepieces needs movement mechanisms and reference numbers, while a jewelry analyst needs clarity and gemstone details.&lt;/p&gt;

&lt;p&gt;The scraper maps these underlying attributes into concrete top-level fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watches&lt;/strong&gt;: Populates &lt;code&gt;watchModel&lt;/code&gt;, &lt;code&gt;referenceNumber&lt;/code&gt;, &lt;code&gt;watchCollection&lt;/code&gt;, &lt;code&gt;movementType&lt;/code&gt;, &lt;code&gt;caseMaterial&lt;/code&gt;, &lt;code&gt;bandMaterial&lt;/code&gt;, &lt;code&gt;watchStyles[]&lt;/code&gt;, and &lt;code&gt;complications[]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jewelry&lt;/strong&gt;: Populates &lt;code&gt;caratWeight&lt;/code&gt; (summed total carat weight across stones of that type), &lt;code&gt;clarityGrade&lt;/code&gt;, &lt;code&gt;colorGrade&lt;/code&gt;, &lt;code&gt;gemstone&lt;/code&gt;, &lt;code&gt;stoneType&lt;/code&gt;, and &lt;code&gt;stoneShapes[]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apparel&lt;/strong&gt;: Populates category-specific attributes such as &lt;code&gt;dressSilhouette&lt;/code&gt; (e.g., "Wrap Dress", "Evening Gown") for dresses and &lt;code&gt;chestSize&lt;/code&gt; (e.g., "42", "48 +") for men's garments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt;: Separates the structured &lt;code&gt;condition&lt;/code&gt; grade (&lt;code&gt;As Is&lt;/code&gt;, &lt;code&gt;Fair&lt;/code&gt;, &lt;code&gt;Good&lt;/code&gt;, &lt;code&gt;Very Good&lt;/code&gt;, &lt;code&gt;Excellent&lt;/code&gt;, &lt;code&gt;Pristine&lt;/code&gt;) from the free-text &lt;code&gt;conditionNotes&lt;/code&gt; field available when fetching complete detail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because empty fields are omitted from emitted records, items only contain the attributes disclosed by the source listing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovering Live Facets Before Extraction
&lt;/h2&gt;

&lt;p&gt;Targeting specific categories or designers often fails when scrapers rely on hardcoded URL slugs. If a brand changes its catalog naming or a taxonomy node shifts, scrapers return zero results.&lt;/p&gt;

&lt;p&gt;To prevent blind querying, the Actor provides two discovery modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;discoverCategories&lt;/code&gt;: Reads The RealReal's live category tree straight from facet data, returning records with &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;department&lt;/code&gt;, &lt;code&gt;categoryPath&lt;/code&gt;, &lt;code&gt;depth&lt;/code&gt;, and current &lt;code&gt;productCount&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;discoverDesigners&lt;/code&gt;: Emits up to 200 designer names for a department or category, returning &lt;code&gt;designerName&lt;/code&gt;, &lt;code&gt;designerSlug&lt;/code&gt;, &lt;code&gt;designerId&lt;/code&gt;, and &lt;code&gt;productCount&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using discovery runs first ensures that downstream extraction runs target valid &lt;code&gt;categoryPath&lt;/code&gt; and &lt;code&gt;designerSlug&lt;/code&gt; inputs with active inventory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution Walkthrough: Building a Filtered Watch Dataset
&lt;/h2&gt;

&lt;p&gt;To collect listings for luxury watches matching specific physical attributes, configure the scraper to target the watches department with precise attribute filtering.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Configure the Run Input
&lt;/h3&gt;

&lt;p&gt;Define the run parameters using the Actor's filtering fields. The configuration below queries the &lt;code&gt;watches&lt;/code&gt; department, applies price boundaries, filters for automatic movements in stainless steel cases, and caps the volume using &lt;code&gt;maxItems&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"byCategory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"watches"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"movementType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automatic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"caseMaterial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stainless Steel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&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;"Very Good"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Excellent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pristine"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sortBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price_asc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Execute via the Apify Python SDK
&lt;/h3&gt;

&lt;p&gt;Run the Actor and process the dataset records 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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&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;byCategory&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;gender&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;watches&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;minPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;movementType&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;Automatic&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;caseMaterial&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;Stainless Steel&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;condition&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;Very Good&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;Excellent&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;Pristine&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;sortBy&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;price_asc&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;maxItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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;crawlerbros/therealreal-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dataset_items&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;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;list_items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;items&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;dataset_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;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;brand&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="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;watchModel&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="sh"&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;| Ref: &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="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;referenceNumber&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="sh"&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;| Price: $&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="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;priceUsd&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="sh"&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;| Condition: &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="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;condition&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;
  
  
  3. Handle Product Detail Payloads
&lt;/h3&gt;

&lt;p&gt;When running in &lt;code&gt;byUrl&lt;/code&gt; mode with explicit &lt;code&gt;productUrls&lt;/code&gt;, the returned dataset items contain deep product data, including &lt;code&gt;measurements[]&lt;/code&gt;, &lt;code&gt;authenticationInfo&lt;/code&gt;, &lt;code&gt;carbonSavedKg&lt;/code&gt;, &lt;code&gt;waterSavedLiters&lt;/code&gt;, and &lt;code&gt;valuationReportUrl&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;"recordType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10984211"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WCH12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rolex"&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;"Submariner Date 116610LN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"watches"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Excellent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conditionNotes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Minor scratches throughout metal."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"movementType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automatic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"caseMaterial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stainless Steel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceUsd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estRetailPriceUsd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"productUrl"&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.therealreal.com/products/watches/rolex-submariner-date-116610ln"&lt;/span&gt;&lt;span class="p"&gt;,&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-07-27T10:15:30.000Z"&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;
  
  
  Actor Billing Mechanics
&lt;/h2&gt;

&lt;p&gt;This Actor operates on a pay-per-event pricing model rather than standard compute runtimes. Charges are calculated directly from two event types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Actor Start (&lt;code&gt;apify-actor-start&lt;/code&gt;)&lt;/strong&gt;: A flat charge of $0.005 per GB of memory allocated to the run, incurred once when execution begins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset Result (&lt;code&gt;apify-default-dataset-item&lt;/code&gt;)&lt;/strong&gt;: Billed per emitted item in the default dataset. Pricing follows volume tiers:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FREE&lt;/strong&gt;: $0.005 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BRONZE&lt;/strong&gt;: $0.00433 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SILVER&lt;/strong&gt;: $0.00367 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOLD&lt;/strong&gt;: $0.003 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PLATINUM&lt;/strong&gt;: $0.003 per event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIAMOND&lt;/strong&gt;: $0.003 per event&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, on the standard FREE tier, a run allocated 1 GB of memory that extracts 500 watch listings incurs $0.005 for the start event and $2.50 for the 500 emitted results ($0.005 × 500).&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline Boundaries
&lt;/h2&gt;

&lt;p&gt;This Actor is designed for extraction and facet discovery; it does not monitor real-time shopping cart state or automate the checkout process. Additionally, while &lt;code&gt;byUrl&lt;/code&gt; mode retrieves full condition descriptions and measurement arrays, markdown indicators like &lt;code&gt;discountPercent&lt;/code&gt; are rarely present because the underlying browse listing API does not reliably expose markdown signals. Filtering pipelines requiring strict markdown verification must calculate discounts downstream by comparing &lt;code&gt;priceUsd&lt;/code&gt; against &lt;code&gt;originalPriceUsd&lt;/code&gt; or &lt;code&gt;estRetailPriceUsd&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://apify.com/crawlerbros/therealreal-scraper" rel="noopener noreferrer"&gt;The RealReal Scraper&lt;/a&gt; is the Actor behind these examples. If a selector in your own version breaks, compare your output against the fields listed in its README first.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prices quoted above are this Actor's published pay-per-event rates on the Apify Store, read from the Apify platform API on 2026-09-21. Check the Actor page for the current rates.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Scrape 1,000 LinkedIn Post Reactions per URL Using the Voyager API</title>
      <dc:creator>Crawler Bros</dc:creator>
      <pubDate>Mon, 21 Sep 2026 16:04:07 +0000</pubDate>
      <link>https://dev.to/crawlerbros/scrape-1000-linkedin-post-reactions-per-url-using-the-voyager-api-14jk</link>
      <guid>https://dev.to/crawlerbros/scrape-1000-linkedin-post-reactions-per-url-using-the-voyager-api-14jk</guid>
      <description>&lt;p&gt;Extracting audience engagement data from social platforms often presents a common engineering hurdle: UI limitations. On LinkedIn, when a post accumulates thousands of reactions, clicking the reaction count only reveals a partial modal. The interface caps the visible list of users, preventing manual or basic DOM-scraping tools from capturing the full picture. &lt;/p&gt;

&lt;p&gt;For data engineers building lead generation pipelines or sentiment analysis models, this restriction is a critical bottleneck. Safely extracting this data requires interacting with the platform's internal APIs while managing session states and rate limits. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://apify.com/crawlerbros/linkedin-post-reactions-scraper" rel="noopener noreferrer"&gt;LinkedIn Post Reactions Scraper&lt;/a&gt; solves this by querying the internal Voyager API—the same data layer that powers the official web application. This allows developers to retrieve structured profiles of users who engaged with any specific post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing LinkedIn Reactor Data Structures
&lt;/h2&gt;

&lt;p&gt;When querying the reactions surface, the scraper bypasses the heavy web UI and directly extracts structured JSON. The output normalizes varied user interaction states into a clean, predictable schema. &lt;/p&gt;

&lt;p&gt;For each reactor, the tool returns a dataset containing the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reactorName&lt;/code&gt;: The full name of the user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reactorProfileUrl&lt;/code&gt;: The direct link to their LinkedIn profile.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reactorHeadline&lt;/code&gt;: The user's current headline or professional title.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reactionType&lt;/code&gt;: The specific interaction type, mapped from raw API codes to human-readable strings (&lt;code&gt;like&lt;/code&gt;, &lt;code&gt;celebrate&lt;/code&gt;, &lt;code&gt;love&lt;/code&gt;, &lt;code&gt;support&lt;/code&gt;, &lt;code&gt;insightful&lt;/code&gt;, &lt;code&gt;funny&lt;/code&gt;, &lt;code&gt;curious&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postId&lt;/code&gt;: The unique numerical activity ID associated with the update.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scrapedAt&lt;/code&gt;: An ISO 8601 UTC timestamp recording when the extraction occurred.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structured format makes it easy to pipe data directly into downstream database tables or CRM systems without complex post-processing regex.&lt;/p&gt;

&lt;p&gt;Here is an example of the structured JSON payload returned by the actor:&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;"reactorName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reactorProfileUrl"&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.linkedin.com/in/janedoe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reactorHeadline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Engineer at Acme Corp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reactionType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"like"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postUrl"&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.linkedin.com/feed/update/urn:li:activity:7234567890123456789/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7234567890123456789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputPostUrl"&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.linkedin.com/feed/update/urn:li:activity:7234567890123456789/"&lt;/span&gt;&lt;span class="p"&gt;,&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;"2025-01-15T10:30:00+00:00"&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;
  
  
  Configuring the Input Schema for Targeted Scraping
&lt;/h2&gt;

&lt;p&gt;To execute a run, the scraper requires specific configuration parameters to navigate authentication and scope the request.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Session Cookie (&lt;code&gt;cookie&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Because the LinkedIn reaction modal is not publicly accessible without authentication, you must provide a valid session cookie. Specifically, the scraper requires the &lt;code&gt;li_at&lt;/code&gt; cookie value, which authenticates requests to the Voyager API. You can extract this from your browser's Developer Tools under the Application tab, or export your full cookies array using a browser extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target Posts (&lt;code&gt;postUrls&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This array accepts complete LinkedIn URLs or bare activity IDs. The scraper natively parses standard formats, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Activity URNs: &lt;code&gt;urn:li:activity:7234567890123456789&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bare IDs: &lt;code&gt;7234567890123456789&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Feed update URLs: &lt;code&gt;https://www.linkedin.com/feed/update/urn:li:activity:7234567890123456789/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scrape Volume and Filtering (&lt;code&gt;maxReactorsPerPost&lt;/code&gt; and &lt;code&gt;reactionTypeFilter&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;LinkedIn strictly limits the visible list of reactors to approximately 1,000 per post, regardless of whether the post shows a higher reaction count in the UI. To optimize your run and control data volume, set the &lt;code&gt;maxReactorsPerPost&lt;/code&gt; parameter (defaults to 100, with a maximum cap of 5,000 to accommodate edge-case API behavior).&lt;/p&gt;

&lt;p&gt;If you only want to extract high-intent leads—such as users who left an "insightful" or "curious" reaction—you can configure the &lt;code&gt;reactionTypeFilter&lt;/code&gt; array. Passing specific values like &lt;code&gt;insightful&lt;/code&gt; or &lt;code&gt;love&lt;/code&gt; instructs the scraper to drop other reaction types before writing to the dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Extraction Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve your session cookie&lt;/strong&gt;: Log into LinkedIn, open your browser's Developer Tools, navigate to the Cookies storage section for &lt;code&gt;www.linkedin.com&lt;/code&gt;, and copy the value of the &lt;code&gt;li_at&lt;/code&gt; cookie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collect target URLs&lt;/strong&gt;: Copy the URLs of the LinkedIn posts you want to analyze.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the input JSON&lt;/strong&gt;: Construct your payload with the required variables. Below is a standard configuration payload:
&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;"postUrls"&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;"https://www.linkedin.com/feed/update/urn:li:activity:7234567890123456789/"&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;"cookie"&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_LI_AT_COOKIE_VALUE_HERE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxReactorsPerPost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reactionTypeFilter"&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;"insightful"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"love"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"groups"&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;"RESIDENTIAL"&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the Actor&lt;/strong&gt;: Execute the run on the Apify platform. The scraper will automatically handle rate limits using exponential backoff, mimicking human pacing to avoid session invalidation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Calculating Run Costs on the Pay-Per-Event Model
&lt;/h2&gt;

&lt;p&gt;This scraper operates under a strict Pay-Per-Event pricing model, meaning you are only billed for specific successful actions rather than execution time or arbitrary platform units. &lt;/p&gt;

&lt;p&gt;The costs are calculated using two distinct event types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Actor Start" (apify-actor-start)&lt;/strong&gt;: Charged once per run at a flat rate of $0.005 per GB of memory allocated to the run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"result" (apify-default-dataset-item)&lt;/strong&gt;: Charged per individual reactor profile successfully written to the default dataset. This event uses volume-tier pricing:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FREE&lt;/strong&gt;: $0.005 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BRONZE&lt;/strong&gt;: $0.00433 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SILVER&lt;/strong&gt;: $0.00367 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOLD&lt;/strong&gt;: $0.003 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PLATINUM&lt;/strong&gt;: $0.003 per result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIAMOND&lt;/strong&gt;: $0.003 per result&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, running a single-GB execution to extract 500 reactors under the FREE tier would cost exactly $0.005 for the start event and $2.50 for the 500 result events, bringing the total run cost to $2.505.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing When to Use a Different Tool
&lt;/h2&gt;

&lt;p&gt;While this scraper is highly efficient for targeted engagement analysis, it is not a general-purpose search tool. It cannot search for posts globally by keyword or hashtag; it requires specific, pre-determined post URLs or activity IDs to begin extraction. If your pipeline requires finding high-performing posts before analyzing who reacted to them, you would need to feed this actor with URLs extracted from a separate keyword-search scraper first.&lt;/p&gt;




&lt;p&gt;Source for the runs in this article: &lt;a href="https://apify.com/crawlerbros/linkedin-post-reactions-scraper" rel="noopener noreferrer"&gt;LinkedIn Post Reactions Scraper&lt;/a&gt;. The input schema there is authoritative; treat anything in this post that contradicts it as out of date.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>programming</category>
      <category>webscraping</category>
    </item>
  </channel>
</rss>
