<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Refill Flows]]></title><description><![CDATA[Refill Flows]]></description><link>https://refillflows.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 14 Nov 2026 19:04:18 GMT</lastBuildDate><atom:link href="https://refillflows.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to bulk-audit Core Web Vitals for hundreds of URLs without a PageSpeed API key]]></title><description><![CDATA[Run real Lighthouse on a URL list or a whole sitemap, export lab Core Web Vitals to CSV, and skip the PageSpeed Insights quota.]]></description><link>https://refillflows.hashnode.dev/how-to-bulk-audit-core-web-vitals-without-a-pagespeed-api-key</link><guid isPermaLink="true">https://refillflows.hashnode.dev/how-to-bulk-audit-core-web-vitals-without-a-pagespeed-api-key</guid><category><![CDATA[Core Web Vitals]]></category><category><![CDATA[Lighthouse]]></category><category><![CDATA[SEO]]></category><category><![CDATA[web performance]]></category><category><![CDATA[apify]]></category><dc:creator><![CDATA[Refill Flows]]></dc:creator><pubDate>Thu, 12 Nov 2026 09:38:49 GMT</pubDate><content:encoded><![CDATA[<h1>How to bulk-audit Core Web Vitals for hundreds of URLs without a PageSpeed API key</h1>
<p><a href="https://pagespeed.web.dev">PageSpeed Insights</a> and the Lighthouse panel in Chrome DevTools are the right tools when you are debugging <strong>one</strong> URL. They are the wrong shape of tool when you need a spreadsheet of lab Core Web Vitals for a sitemap, every product template after a release, or a monthly report across a client's sites.</p>
<p>The usual next step is Google's PageSpeed Insights API. It works, and it is free up to a quota, but that shared quota is easy to burn through once you start auditing hundreds of URLs, on mobile and desktop, more than once. Local Lighthouse on your laptop also works, if you are happy to install Chrome, walk a sitemap, keep median runs and stitch the JSON together yourself.</p>
<p>This tutorial shows a practical way to run <strong>real Google Lighthouse</strong> on a list of URLs or a whole sitemap, with no PageSpeed Insights API key and no shared quota, using <a href="https://apify.com/refillflows/bulk-lighthouse-audit">Bulk Lighthouse Audit</a> on Apify. You can do the same job in the browser in a couple of minutes, or from Python or <code>curl</code> if you want it on a schedule or in CI. Only audit sites you own or are allowed to test.</p>
<h2>What you will get</h2>
<p>Each row in the dataset is one page on one device. Fields include:</p>
<ul>
<li><code>url</code>, <code>finalUrl</code>, <code>strategy</code> (<code>mobile</code> or <code>desktop</code>) and <code>engine</code></li>
<li><code>scores</code>: Performance, Accessibility, Best Practices and SEO (0–100)</li>
<li>Lab metrics: <code>largestContentfulPaintMs</code>, <code>cumulativeLayoutShift</code>, <code>totalBlockingTimeMs</code>, plus FCP, Speed Index, TTI, TTFB (<code>serverResponseTimeMs</code>), <code>totalByteWeight</code>, <code>domSize</code> and <code>networkRequests</code></li>
<li><code>labCwv</code> (LCP, CLS and TBT each rated <code>good</code>, <code>needs-improvement</code> or <code>poor</code>) and an overall <code>labCwvAssessment</code></li>
<li>The <code>lcpElement</code> and <code>lcpBreakdownMs</code> (time to first byte vs render delay)</li>
<li><code>opportunities</code>: the top fixes, with estimated time and bytes saved</li>
<li><code>failedAudits</code>: failing accessibility, SEO and best-practice checks, listed by name</li>
<li><code>passedThreshold</code>, if you set a performance budget</li>
<li>Optional <code>reportUrl</code> for a shareable HTML Lighthouse report (local engine only)</li>
<li>Optional <code>fieldData</code>: real-user CrUX p75 LCP, INP, CLS, FCP and TTFB, if you add a free Google API key</li>
<li><code>lighthouseVersion</code>, <code>runs</code>, <code>error</code> and <code>auditedAt</code></li>
</ul>
<p>The dataset has two views: <strong>Scores &amp; Core Web Vitals</strong>, and <strong>Top fixes</strong> (one row per recommendation). A <code>SUMMARY</code> record in the key-value store holds site-level averages, the slowest pages and the lab Core Web Vitals pass rate (<code>labCwvPassRate</code>).</p>
<p>Lab Core Web Vitals here are LCP, CLS and TBT. TBT is Lighthouse's lab stand-in for INP. Real-user INP only appears in <code>fieldData</code>, and that needs a Google API key.</p>
<h2>Option 1: run it from the Apify Console (no code)</h2>
<ol>
<li>Create a free <a href="https://console.apify.com/sign-up">Apify</a> account if you do not have one. The free plan includes $5 of monthly credit, which covers hundreds of local audits.</li>
<li>Open <a href="https://apify.com/refillflows/bulk-lighthouse-audit">Bulk Lighthouse Audit</a> and click <strong>Try for free</strong>.</li>
<li>Paste one or more URLs, or a sitemap URL. The form is prefilled with <code>https://example.com</code>. Sitemap indexes and <code>.gz</code> files are fine.</li>
<li>Keep <strong>Devices</strong> on <code>mobile</code> for a first run (a simulated mid-range phone on slow 4G, the same method PageSpeed Insights uses). Add <code>desktop</code> when you need both.</li>
<li>Leave <strong>Save full HTML Lighthouse reports</strong> off unless you need the shareable report. Leave the engine on <strong>Auto</strong>: with no key, that runs Lighthouse 13 inside the Actor's own Chrome.</li>
<li>Optionally set <strong>Max URLs</strong> (default 50, counted per device), a <strong>Performance budget</strong> (for example 80) and <strong>Runs per URL</strong>. Google recommends 3–5 runs and taking the median when you need stable numbers; each run is billed.</li>
<li>Click <strong>Start</strong>. A single mobile audit usually finishes in well under a minute.</li>
<li>Open the <strong>Dataset</strong> tab for scores and Core Web Vitals, and the key-value store for the <code>SUMMARY</code> record. Export <strong>CSV</strong> or JSON, or pull the results through the API.</li>
</ol>
<p>That is the whole one-off workflow. Schedule the same input weekly or after each release when you want a history.</p>
<h3>Example input</h3>
<p>A sitemap, mobile only, capped at 50 pages, with a performance budget:</p>
<pre><code class="language-json">{
  "sitemapUrls": ["https://crawlee.dev/sitemap.xml"],
  "maxUrls": 50,
  "strategies": ["mobile"],
  "performanceThreshold": 80,
  "runsPerUrl": 1,
  "saveHtmlReports": false
}
</code></pre>
<p>A short URL list on mobile and desktop:</p>
<pre><code class="language-json">{
  "urls": [
    "https://www.python.org",
    "https://www.wikipedia.org",
    "https://example.com"
  ],
  "strategies": ["mobile", "desktop"],
  "performanceThreshold": 80,
  "maxConcurrency": 1
}
</code></pre>
<p>You can combine <code>urls</code> and <code>sitemapUrls</code> in one run. The Actor collects the sitemap <code>loc</code> entries, de-duplicates them, and audits each page up to <code>maxUrls</code> per device.</p>
<h2>Option 2: run it from Python or curl</h2>
<p>Install the official client and call the Actor. Replace the token with your Apify API token (Console → Settings → Integrations). Do not commit the token.</p>
<pre><code class="language-python">from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("refillflows/bulk-lighthouse-audit").call(
    run_input={
        "sitemapUrls": ["https://example.com/sitemap.xml"],
        "maxUrls": 50,
        "strategies": ["mobile"],
        "performanceThreshold": 80,
        "saveHtmlReports": False,
    }
)

dataset_id = run["defaultDatasetId"]
items = list(client.dataset(dataset_id).iterate_items())
print(f"Got {len(items)} audits")
for row in items[:5]:
    print(row["url"], row["strategy"], row.get("scores"), row.get("labCwv"))

# Fail a CI job if any page missed the budget
failed = [r for r in items if r.get("passedThreshold") is False]
if failed:
    raise SystemExit(f"{len(failed)} page(s) below the performance budget")
</code></pre>
<p>The same call with <code>curl</code>, waiting for the dataset and returning CSV:</p>
<pre><code class="language-bash">curl -X POST \
  "https://api.apify.com/v2/acts/refillflows~bulk-lighthouse-audit/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN&amp;format=csv" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": ["https://example.com"],
    "strategies": ["mobile"],
    "saveHtmlReports": false
  }'
</code></pre>
<p>If you already have a <code>DATASET_ID</code> from a finished run, you can also download it with:</p>
<pre><code class="language-text">https://api.apify.com/v2/datasets/&lt;DATASET_ID&gt;/items?format=csv&amp;token=YOUR_APIFY_TOKEN
</code></pre>
<p>The site-level <code>SUMMARY</code> record lives in the run's default key-value store:</p>
<pre><code class="language-text">https://api.apify.com/v2/key-value-stores/&lt;VALUE_STORE_ID&gt;/records/SUMMARY?token=YOUR_APIFY_TOKEN
</code></pre>
<p>Schedule the Actor daily or weekly, set a <code>performanceThreshold</code>, and post rows where <code>passedThreshold</code> is <code>false</code> to Slack, Sheets or a webhook from a CI pipeline.</p>
<h2>Pricing, in practice</h2>
<p>The Actor is pay-per-event. You are charged for audits that complete, and platform usage is included in that price. Pages that cannot load are reported with an <code>error</code> field and are not charged.</p>
<table>
<thead>
<tr>
<th>What</th>
<th>Price</th>
</tr>
</thead>
<tbody><tr>
<td>Actor start</td>
<td>$0.005 per run (charged per GB of memory; this Actor defaults to 2 GB, so $0.01)</td>
</tr>
<tr>
<td>Lighthouse audit (local engine, no key)</td>
<td>$0.01 per page per device</td>
</tr>
<tr>
<td>Audit with your PageSpeed API key</td>
<td>$0.003 per page per device</td>
</tr>
</tbody></table>
<p>A 100-page site on mobile is about $1.00, or $2.00 for mobile plus desktop, plus the $0.01 start charge. With your own Google key it is $0.30 per 100 pages. If you set <strong>Runs per URL</strong> to 3, you pay for three audits per page. The $5 free-plan credit covers hundreds of local audits, or more if you switch to the PSI engine. Set <strong>Max URLs</strong> or a max cost per run if you are exploring.</p>
<h2>Tips that save time</h2>
<ul>
<li><strong>No key is required.</strong> The default <strong>Auto</strong> engine runs local Lighthouse when no Google API key is set, so there is no PSI quota to run out of. Add a key only if you want the cheaper PSI engine and real-user CrUX field data (<code>includeFieldData</code> defaults to on once a key is present; it falls back from URL-level to origin-level data).</li>
<li><strong>To get a free Google API key</strong>, open Google Cloud Console, enable the <strong>PageSpeed Insights API</strong> and the <strong>Chrome UX Report API</strong>, and create an API key. Paste it into <strong>Google API key</strong>.</li>
<li><strong>Lab scores vary slightly between machines.</strong> They come from a simulated Moto G Power on slow 4G. Use <code>runsPerUrl</code> of 3 or more when you need precise numbers, and remember that each run is billed.</li>
<li><strong>Keep <code>maxConcurrency</code> at 1</strong> for the most accurate local scores (CPU contention skews metrics). Use 2–4 with 4 GB or more of memory when you need speed. The PSI engine can go higher, up to 10.</li>
<li><strong>Fewer Lighthouse categories is faster.</strong> The default is all four (<code>performance</code>, <code>accessibility</code>, <code>best-practices</code>, <code>seo</code>). You can drop the ones you do not need.</li>
<li><strong>Leave HTML reports off</strong> for routine monitoring. Turn <strong>Save full HTML Lighthouse reports</strong> on when you want a shareable <code>reportUrl</code> for a specific page (local engine only).</li>
<li><strong><code>maxUrls</code> is per device.</strong> 50 URLs on mobile and desktop is 100 audits.</li>
<li><strong>Failed loads are free.</strong> DNS errors, interstitials and pages that never paint are stored with an <code>error</code> and are not billed. The default timeout is 120 seconds per audit (<code>auditTimeoutSecs</code>).</li>
</ul>
<h2>May I audit any website?</h2>
<p>The Actor loads only the pages you give it, the same way a normal browser visit does. Only audit sites you own or are allowed to test.</p>
<h2>Further reading</h2>
<ul>
<li><a href="https://apify.com/refillflows/bulk-lighthouse-audit">Bulk Lighthouse Audit on Apify Store</a></li>
<li><a href="https://pagespeed.web.dev">PageSpeed Insights</a>, which is still the fastest way to inspect a single URL</li>
<li><a href="https://docs.apify.com/api/v2">Apify API docs</a></li>
<li><a href="https://docs.apify.com/platform/integrations/mcp">Apify MCP server</a>, if you want an agent to ask "audit this site's speed" against fresh data</li>
</ul>
<p>If you also work with Shopify App Store listings or public Luma events, the same publisher has <a href="https://apify.com/refillflows/shopify-app-store-scraper">Shopify App Store Scraper</a> and <a href="https://apify.com/refillflows/luma-events-scraper">Luma Events Scraper</a>.</p>
<p><em>Disclosure: I built the Bulk Lighthouse Audit tool this tutorial uses.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to track Shopify App Store rankings and pricing over time]]></title><description><![CDATA[Export keyword ranks, pricing plans and reviews to CSV, then run the same job on a schedule. Shopify has no public API for this.]]></description><link>https://refillflows.hashnode.dev/how-to-track-shopify-app-store-rankings-and-pricing-over-time</link><guid isPermaLink="true">https://refillflows.hashnode.dev/how-to-track-shopify-app-store-rankings-and-pricing-over-time</guid><category><![CDATA[shopify]]></category><category><![CDATA[ecommerce]]></category><category><![CDATA[web scraping]]></category><category><![CDATA[apify]]></category><category><![CDATA[SaaS]]></category><dc:creator><![CDATA[Refill Flows]]></dc:creator><pubDate>Thu, 12 Nov 2026 09:36:50 GMT</pubDate><content:encoded><![CDATA[<h1>How to track Shopify App Store rankings and pricing over time</h1>
<p>If you build, market or invest in Shopify apps, two numbers matter week after week: <strong>where you rank for a keyword</strong> and <strong>what competitors charge</strong>. The <a href="https://apps.shopify.com">Shopify App Store</a> shows both, but only one page at a time. There is no official API for search rankings, pricing plans or merchant reviews, and copy-pasting the first page of results into a spreadsheet does not scale past a couple of keywords.</p>
<p>For a one-off check, the manual route is still the right one. Search for <code>subscriptions</code> or <code>reviews</code>, note the organic order, and open a competitor's pricing tab. That takes a few minutes. It falls apart when you want a daily history: "we dropped from position 3 to 7 for <code>upsell</code> on Tuesday, and App X added a cheaper plan."</p>
<p>This tutorial shows a practical way to export those public listings to CSV and to run the same job on a schedule, using the <a href="https://apify.com/refillflows/shopify-app-store-scraper">Shopify App Store Scraper</a> on Apify. You can do it in the browser in a couple of minutes, or from Python or <code>curl</code> if you want it to repeat. The Actor only reads pages any visitor can open without logging in.</p>
<h2>What you will get</h2>
<p>Each app is one record. Useful columns for a ranking or pricing tracker include:</p>
<ul>
<li><code>handle</code>, <code>name</code> and <code>url</code> (use <code>handle</code> as the stable key)</li>
<li><code>searchPosition</code> and <code>isSponsored</code> (organic rank; ads are skipped by default)</li>
<li><code>rating</code>, <code>reviewCount</code> and, in details mode, <code>ratingDistribution</code></li>
<li><code>pricingSummary</code>, plus <code>pricingPlans</code> (name, price, currency, billing period, features) and <code>planCurrency</code></li>
<li><code>builtForShopify</code>, <code>tagline</code>, <code>launchedDate</code>, <code>languages</code>, <code>worksWith</code></li>
<li><code>developerName</code>, <code>developerWebsite</code>, <code>supportEmail</code></li>
<li>Shopify's <code>reviewsAiSummary</code></li>
<li><code>source</code> (which search, category or developer page found the app) and <code>scrapedAt</code></li>
</ul>
<p>Merchant reviews are optional. When you turn them on they are nested on the app record (<code>reviewId</code>, <code>rating</code>, <code>date</code>, <code>text</code>, <code>storeName</code>, <code>storeCountry</code>, <code>usageDuration</code>, and any developer reply). The dataset's <strong>Reviews</strong> view flattens them to one row per review.</p>
<h2>Option 1: export from the Apify Console (no code)</h2>
<ol>
<li>Create a free <a href="https://console.apify.com/sign-up">Apify</a> account if you do not have one. The free plan includes $5 of monthly credit, which is enough for thousands of apps.</li>
<li>Open the <a href="https://apify.com/refillflows/shopify-app-store-scraper">Shopify App Store Scraper</a> and click <strong>Try for free</strong>.</li>
<li>Enter a search keyword. The form is prefilled with <code>subscriptions</code>. You can also paste a category URL or slug, a developer page such as <code>https://apps.shopify.com/partners/klaviyo</code>, or specific app URLs.</li>
<li>For daily rank tracking, turn <strong>Scrape full app details</strong> off. That is fast mode: rank, rating, review count, pricing summary and tagline from the listing pages, at the lower price. Leave details on when you want every pricing plan, launch date and integrations.</li>
<li>Leave <strong>Skip sponsored (ad) results</strong> on so <code>searchPosition</code> is organic rank, not a mix of ads and organic results.</li>
<li>Leave <strong>Scrape reviews</strong> off unless you need merchant review text. If you do, set <strong>Max reviews per app</strong>, <strong>Review sort</strong> (<code>newest</code> is best for monitoring) and optionally <strong>Reviews since</strong> or <strong>Only these star ratings</strong> (for example <code>[1, 2]</code> for complaints).</li>
<li>Set <strong>Max apps per search/category/developer</strong> so a first run stays small. 10–25 is plenty for a test (the form prefills 10).</li>
<li>Click <strong>Start</strong>. About 10 apps with full details usually finish in under a minute.</li>
<li>Open the <strong>Dataset</strong> tab (the <strong>Apps</strong> view), click <strong>Export</strong>, and choose <strong>CSV</strong> (or Excel). Open the file in Google Sheets or Excel.</li>
</ol>
<p>To watch change over time, open the Actor's <strong>Schedules</strong> tab and run the same input daily. Keep <code>handle</code> as the join key and compare <code>searchPosition</code>, <code>pricingSummary</code> and, in details mode, <code>pricingPlans</code>.</p>
<h3>Example input</h3>
<p>Daily organic rank for two keywords, fast mode, first 25 results each:</p>
<pre><code class="language-json">{
  "searchQueries": ["subscriptions", "upsell"],
  "maxAppsPerSource": 25,
  "scrapeAppDetails": false,
  "excludeSponsored": true,
  "scrapeReviews": false
}
</code></pre>
<p>A weekly pricing snapshot: the same keywords, plus one category, with full plan details (no reviews):</p>
<pre><code class="language-json">{
  "searchQueries": ["subscriptions"],
  "categoryUrls": ["marketing-and-conversion-marketing-email-marketing"],
  "maxAppsPerSource": 50,
  "scrapeAppDetails": true,
  "scrapeReviews": false
}
</code></pre>
<p>You can mix sources in one run: <code>searchQueries</code>, <code>categoryUrls</code>, <code>developerUrls</code> and <code>appUrls</code>. Category and developer values can be full URLs or slugs (<code>klaviyo</code>, or <code>https://apps.shopify.com/partners/klaviyo</code>).</p>
<h2>Option 2: run it from Python or curl</h2>
<p>Install the official client and call the Actor. Replace the token with your Apify API token (Console → Settings → Integrations). Do not commit the token.</p>
<pre><code class="language-python">from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("refillflows/shopify-app-store-scraper").call(
    run_input={
        "searchQueries": ["subscriptions"],
        "maxAppsPerSource": 25,
        "scrapeAppDetails": True,
        "scrapeReviews": False,
    }
)

dataset_id = run["defaultDatasetId"]
items = list(client.dataset(dataset_id).iterate_items())
print(f"Got {len(items)} apps")
for app in items[:5]:
    print(app.get("searchPosition"), app["handle"], app.get("pricingSummary"))
</code></pre>
<p>The same call with <code>curl</code>, waiting for the dataset and returning CSV:</p>
<pre><code class="language-bash">curl -X POST \
  "https://api.apify.com/v2/acts/refillflows~shopify-app-store-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN&amp;format=csv" \
  -H "Content-Type: application/json" \
  -d '{
    "searchQueries": ["subscriptions"],
    "maxAppsPerSource": 25,
    "scrapeAppDetails": true,
    "scrapeReviews": false
  }'
</code></pre>
<p>If you already have a <code>DATASET_ID</code> from a finished run, you can also download it with:</p>
<pre><code class="language-text">https://api.apify.com/v2/datasets/&lt;DATASET_ID&gt;/items?format=csv&amp;token=YOUR_APIFY_TOKEN
</code></pre>
<p>Schedule the same input daily in Apify Console (or with the API) and send new rows to Google Sheets, Slack or a webhook. Diff yesterday's file against today's on <code>handle</code>. Fast mode is enough to plot <code>searchPosition</code> and to notice a change in the listing's <code>pricingSummary</code>. Use a slower details run when you need every plan's price and feature list.</p>
<h2>Pricing, in practice</h2>
<p>The Actor is pay-per-event. You are charged for apps and reviews you actually receive, and platform usage is included in that price.</p>
<table>
<thead>
<tr>
<th>What</th>
<th>Price</th>
</tr>
</thead>
<tbody><tr>
<td>Actor start</td>
<td>$0.005 per run</td>
</tr>
<tr>
<td>App from listing (fast mode)</td>
<td>$0.80 per 1,000 apps</td>
</tr>
<tr>
<td>App with full details</td>
<td>$2.00 per 1,000 apps</td>
</tr>
<tr>
<td>Review</td>
<td>$0.30 per 1,000 reviews</td>
</tr>
</tbody></table>
<p>A daily fast-mode run of 25 results for one keyword is a fraction of a cent per day, or roughly $0.25 a month including start charges. A full category of 300 apps with details is about $0.60. 10,000 reviews cost $3. The $5 free-plan credit covers thousands of apps. Set <strong>Max apps per search/category/developer</strong>, or a max cost per run, if you are exploring.</p>
<h2>Tips that save time</h2>
<ul>
<li><strong>Use fast mode for rank, details mode for pricing.</strong> Listing data already includes <code>searchPosition</code>, <code>rating</code>, <code>reviewCount</code> and <code>pricingSummary</code>. Turn <strong>Scrape full app details</strong> on when you need <code>pricingPlans</code>, <code>launchedDate</code>, <code>worksWith</code>, <code>ratingDistribution</code> or <code>reviewsAiSummary</code>.</li>
<li><strong>Always store <code>planCurrency</code>.</strong> Shopify shows plan prices in the visitor's local currency. On Apify's servers (US) that is USD. If you run the Actor elsewhere, or through a proxy in another country, the numbers will be in that currency. Comparing a USD snapshot to an INR snapshot will look like a price change when it is not.</li>
<li><strong><code>handle</code> is the stable key</strong>, not the display name. Names and taglines change; the handle in the App Store URL usually does not.</li>
<li><strong>De-duplication is by <code>handle</code>.</strong> If the same app matches both a search keyword and a category in one run, it appears once. The <code>source</code> field tells you which query or page found it first.</li>
<li><strong>Reviews are optional and cost extra.</strong> Leave them off for a rank or pricing tracker. If you want recent 1–2★ feedback for feature ideas, set <code>scrapeReviews: true</code>, <code>reviewRatings: [1, 2]</code>, <code>reviewsSort: "newest"</code> and a <code>reviewsSince</code> date. Rating-only reviews (no text) are real and are kept, with <code>text</code> set to <code>null</code>.</li>
<li><strong>For large crawls</strong> (thousands of apps or reviews), enable an Apify datacenter proxy and raise <code>maxConcurrency</code> and <code>maxRequestsPerMinute</code> (defaults are 5 and 120).</li>
</ul>
<h2>Is this allowed?</h2>
<p>Shopify does not offer a public App Store API for rankings, plans or reviews. This Actor only reads <strong>public</strong> App Store pages that any visitor can see without logging in. Review authors are shown as their <strong>store names</strong>, as Shopify publishes them. You are still responsible for how you use the data, including Shopify's terms and privacy law in your jurisdiction (for example GDPR and CCPA).</p>
<h2>Further reading</h2>
<ul>
<li><a href="https://apify.com/refillflows/shopify-app-store-scraper">Shopify App Store Scraper on Apify Store</a></li>
<li><a href="https://docs.apify.com/api/v2">Apify API docs</a></li>
<li><a href="https://docs.apify.com/platform/integrations/mcp">Apify MCP server</a>, if you want an agent to ask "what are the top 10 Shopify subscription apps and their cheapest paid plan?" against fresh data</li>
</ul>
<p>If you also collect public Luma events or need Lighthouse / Core Web Vitals in bulk, the same publisher has <a href="https://apify.com/refillflows/luma-events-scraper">Luma Events Scraper</a> and <a href="https://apify.com/refillflows/bulk-lighthouse-audit">Bulk Lighthouse Audit</a>.</p>
<p><em>Disclosure: I built the Shopify App Store Scraper this tutorial uses.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to export all Luma events for a city to CSV]]></title><description><![CDATA[Luma (lu.ma / luma.com) is where a large share of tech, AI and community events are listed now. The site is easy to browse for a single evening, and less…]]></description><link>https://refillflows.hashnode.dev/how-to-export-all-luma-events-for-a-city-to-csv</link><guid isPermaLink="true">https://refillflows.hashnode.dev/how-to-export-all-luma-events-for-a-city-to-csv</guid><category><![CDATA[web scraping]]></category><category><![CDATA[Python]]></category><category><![CDATA[data]]></category><category><![CDATA[automation]]></category><category><![CDATA[events]]></category><dc:creator><![CDATA[Refill Flows]]></dc:creator><pubDate>Wed, 11 Nov 2026 11:08:47 GMT</pubDate><content:encoded><![CDATA[<p>Luma (lu.ma / luma.com) is where a large share of tech, AI and community events are listed now. The site is easy to browse for a single evening, and less easy when you want <strong>every upcoming event in a city</strong> as a spreadsheet: a newsletter "what's on this week" list, a DevRel calendar, or a prospecting list of organizers.</p>
<p>Luma does have an official API, but it is aimed at organizers on paid plans and is scoped to <strong>calendars you manage</strong>. It will not give you "all public AI meetups in Bengaluru next month". Community calendars also offer an iCal subscription, which is perfect when you already know the calendar, and useless for discovering events on calendars you do not follow.</p>
<p>This tutorial shows a practical way to export public Luma events for a city to CSV, using the <a href="https://apify.com/refillflows/luma-events-scraper">Luma Events Scraper</a> on Apify. You can do the same job in the browser in a couple of minutes, or from Python if you want it on a schedule. The Actor only reads events that any visitor can see without logging in. It does not collect guest lists or private attendee data.</p>
<h2>What you will get</h2>
<p>Each row in the CSV is one event, with fields such as:</p>
<ul>
<li>Name and Luma URL</li>
<li>Start and end time, timezone</li>
<li>Venue, address, city, and coordinates</li>
<li>Free or paid, price range, sold-out and waitlist flags, remaining spots</li>
<li>RSVP / guest count</li>
<li>Organizing calendar and hosts</li>
<li>A <code>source</code> field that records which city, category or keyword found the event</li>
</ul>
<p>You can also turn on an optional details mode for the full description and ticket tiers.</p>
<h2>Option 1: export from the Apify Console (no code)</h2>
<ol>
<li>Create a free <a href="https://console.apify.com/sign-up">Apify</a> account if you do not have one. The free plan includes $5 of monthly credit, which is enough for a few thousand events.</li>
<li>Open the <a href="https://apify.com/refillflows/luma-events-scraper">Luma Events Scraper</a> and click <strong>Try for free</strong>.</li>
<li>In <strong>Locations</strong>, enter a city, for example <code>San Francisco</code>, <code>London</code>, or <code>Bengaluru</code>. You can pass <code>"lat,lon"</code> if you prefer. Featured Luma cities resolve immediately; other names are geocoded.</li>
<li>Leave <strong>Categories</strong> and <strong>Search keywords</strong> empty if you want everything nearby. To narrow the list, add a category such as <code>ai</code> or <code>tech</code>, or a keyword such as <code>hackathon</code>.</li>
<li>Optionally set a date window (<code>Start date</code> / <code>End date</code>) and <strong>Max events</strong> so a first run stays small. 25–100 is plenty for a test.</li>
<li>Click <strong>Start</strong>. A few dozen events usually come back in seconds.</li>
<li>Open the <strong>Dataset</strong> tab, click <strong>Export</strong>, and choose <strong>CSV</strong> (or Excel). Open the file in Google Sheets or Excel.</li>
</ol>
<p>That is the whole one-off workflow. The dataset also has a <strong>Location &amp; map</strong> view if you want to plot coordinates.</p>
<h3>Example input</h3>
<p>A week of AI and tech events in two cities, plus one community calendar:</p>
<pre><code class="language-json">{
  "locations": ["San Francisco", "Bengaluru"],
  "categories": ["ai", "tech"],
  "calendarUrls": ["https://luma.com/genai-sf"],
  "startDate": "2026-11-12",
  "endDate": "2026-11-19",
  "maxItems": 300
}
</code></pre>
<p>If you only want one city, all upcoming public events:</p>
<pre><code class="language-json">{
  "locations": ["San Francisco"],
  "radiusKm": 25,
  "maxItems": 500
}
</code></pre>
<p>A 25 km radius around San Francisco is often 500+ upcoming events, so keep <code>maxItems</code> in mind.</p>
<h2>Option 2: export to CSV from Python</h2>
<p>Install the official client and call the Actor. Replace the token with your Apify API token (Console → Settings → Integrations). Do not commit the token.</p>
<pre><code class="language-python">from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("refillflows/luma-events-scraper").call(
    run_input={
        "locations": ["San Francisco"],
        "searchQueries": ["hackathon"],
        "maxItems": 100,
    }
)

# Write a CSV via the dataset API, or iterate in Python
dataset_id = run["defaultDatasetId"]
items = list(client.dataset(dataset_id).iterate_items())
print(f"Got {len(items)} events")
for event in items[:5]:
    print(event["startAt"], event["name"], event["url"])
</code></pre>
<p>To download the whole dataset as a CSV file without iterating:</p>
<pre><code class="language-text">https://api.apify.com/v2/datasets/&lt;DATASET_ID&gt;/items?format=csv&amp;token=YOUR_APIFY_TOKEN
</code></pre>
<p>You can also start a run with <code>curl</code> and then hit that export URL. Schedule the same input daily in Apify Console (or with the API) and send new rows to Google Sheets, Slack or a webhook. Use the <code>eventId</code> field as the stable key when you diff yesterday's file against today's.</p>
<h2>Pricing, in practice</h2>
<p>The Actor is pay-per-event. You are charged for events you actually receive, and platform usage is included in that price.</p>
<table>
<thead>
<tr>
<th>What</th>
<th>Price</th>
</tr>
</thead>
<tbody><tr>
<td>Actor start</td>
<td>$0.005 per run</td>
</tr>
<tr>
<td>Event scraped</td>
<td>$1.50 per 1,000 events</td>
</tr>
<tr>
<td>Full description and ticket tiers (optional)</td>
<td>$2.00 per 1,000 events</td>
</tr>
</tbody></table>
<p>A 1,000-event city export is about $1.50. The $5 free-plan credit covers roughly 3,000 events. Set <strong>Max events</strong> or a run cost limit if you are exploring.</p>
<h2>Tips that save time</h2>
<ul>
<li><strong>De-duplication is built in.</strong> If the same event matches both the <code>ai</code> category and a "hackathon" keyword, it appears once. The <code>source</code> field tells you which query found it first.</li>
<li><strong>Host social links are off by default.</strong> Turn them on only if you have a lawful reason to process that data.</li>
<li><strong>iCal is still better for calendars you already follow.</strong> Use the Actor for city and category discovery; subscribe to a calendar's iCal feed when you just want updates from one organizer.</li>
<li><strong>Luma will rate-limit very large pulls.</strong> The Actor retries with backoff. For 10,000+ events or details mode, enable an Apify datacenter proxy.</li>
<li><strong>This is not a guest-list tool.</strong> Public event metadata only.</li>
</ul>
<h2>Is this allowed?</h2>
<p>The Actor collects information Luma already shows to any logged-out visitor: title, time, venue, price, public guest count, organizer. It does not access private events or attendee lists. You are still responsible for how you use the data, including Luma's terms and privacy law in your jurisdiction (for example GDPR, if you turn on host social links).</p>
<h2>Further reading</h2>
<ul>
<li><a href="https://apify.com/refillflows/luma-events-scraper">Luma Events Scraper on Apify Store</a></li>
<li><a href="https://docs.apify.com/api/v2">Apify API docs</a></li>
<li><a href="https://docs.apify.com/platform/integrations/mcp">Apify MCP server</a>, if you want an agent to ask "what is on in this city next week" against fresh data</li>
</ul>
<p>If you also work with Shopify App Store listings or need Lighthouse / Core Web Vitals in bulk, the same publisher has <a href="https://apify.com/refillflows/shopify-app-store-scraper">Shopify App Store Scraper</a> and <a href="https://apify.com/refillflows/bulk-lighthouse-audit">Bulk Lighthouse Audit</a>.</p>
]]></content:encoded></item></channel></rss>