// Sheets · Apps Script

Pull data from a web page into Google Sheets with Apps Script.

Use UrlFetchApp.fetch to grab raw HTML from any public URL, then extract a specific value with a targeted regex — no DOM parser needed, no external dependencies.

I want to pull a specific number or piece of text off a public web page and write it into my spreadsheet automatically, without setting up a server or buying an API.

The script

copy · paste · trigger
scrapeToSheet.gs
Apps Script
// Fetch one value from a public page and write it to Sheet1 A1
function scrapeToSheet() {
  var url = 'https://example.com/stats';
  var response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });

  if (response.getResponseCode() !== 200) {
    Logger.log('Fetch failed: ' + response.getResponseCode());
    return;
  }

  var html = response.getContentText();

  // Target the number that follows id="total-users">
  var match = html.match(/id="total-users">([0-9,]+)</);
  if (!match) { Logger.log('Pattern not found'); return; }

  var value = match[1].replace(/,/g, '');
  SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('Sheet1')
    .getRange('A1')
    .setValue(Number(value));
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

Why UrlFetchApp instead of ImportXML

IMPORTXML and IMPORTHTML are the obvious Sheets tools, but they fail silently on pages that require a cookie, set X-Frame-Options headers, or use JavaScript to render content after the initial HTML load. The first time I hit this, I wasted an hour wondering why my formula returned an empty cell with no error. UrlFetchApp.fetch runs server-side, bypasses all of that, and gives you the raw bytes Google's servers received — the same bytes you would see doing curl on the URL.

The tradeoff is that you get HTML as a plain string, not a parsed document tree. Apps Script has no built-in DOM parser (no querySelector, no getElementById). That sounds like a dealbreaker until you realize most scraping targets are a single number or a short text value sitting next to a predictable marker in the markup.

Anchoring your regex to a stable marker

The failure mode for HTML regex is greed: patterns that try to match across the whole structure break the moment the site changes a class name or reorders a div. The working approach is narrower — pick the closest unique identifier to your target value and write the pattern to consume only the gap between that anchor and the value.

In the example above, the anchor is the id attribute id="total-users". The pattern /id="total-users">([0-9,]+)</ captures only digits and commas between the closing > of that tag and the next < character. If the site wraps the number in a span with a dynamic class, the id on the container is almost always stable across deploys. Inspect the page source (not DevTools Elements, which shows the post-JS DOM) by viewing page source directly, so you see what UrlFetchApp will actually receive.

After capture, the replace(/,/g, '') strips thousand-separator commas before converting to Number. Skipping that step means '1,204' becomes NaN in the cell — a quiet failure that is annoying to debug.

Scheduling and quota ceilings

Once the function works manually, attach a time-driven trigger: in the Apps Script editor open Triggers (the clock icon), add a trigger for scrapeToSheet, and set the interval. For a stat you want once a day, a daily trigger at a fixed hour is enough. For something you want every hour, the minimum interval Apps Script time triggers support is one minute, but UrlFetchApp counts against your daily URL Fetch quota — 20,000 calls per day for consumer accounts, 100,000 for Workspace. Hourly calls on a single URL use only 24 of those, so quota is not a real constraint for normal monitoring use.

muteHttpExceptions: true is load-bearing in the fetch options. Without it, any 4xx or 5xx response throws an unhandled exception and the trigger logs a failure with no useful context. With it, the response object comes back regardless, and getResponseCode() lets you branch on the actual status. The Logger.log lines flow to Apps Script's execution log, which you can read under Executions in the editor sidebar.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Why does UrlFetchApp return empty content when the page loads fine in a browser?
The page is almost certainly rendering its content with JavaScript after the initial HTML load. UrlFetchApp fetches only the raw server response — no JavaScript runs. Check the actual page source (Ctrl+U in Chrome) rather than DevTools Elements; if your target value is absent from that source, UrlFetchApp will not find it either. You need either a public API endpoint or a headless browser approach outside Apps Script.
Can I scrape a page that requires login?
Yes, if you can replay the session cookie. Fetch the login form, submit credentials to get a Set-Cookie response, then pass that cookie value in subsequent requests via the headers option: { headers: { Cookie: 'session=abc123' } }. This is fragile against CSRF tokens and multi-step auth flows, but works for simple form-based logins.
How do I pull multiple values from the same page without fetching it twice?
Fetch once into a variable, then run as many match() calls as you need against the same html string. One fetch, multiple regex passes. Each match captures a different anchor pattern. Write the results to adjacent cells in a single getRange call if you want to minimize Sheets API round-trips.
The site returns a 403 when Apps Script fetches it. What can I do?
The server is blocking Google's datacenter IP range, which is a common anti-scraping measure. You can try spoofing a browser User-Agent header with { headers: { 'User-Agent': 'Mozilla/5.0 ...' } } in the fetch options — some servers check only that. If the site uses Cloudflare or a similar WAF, the block is usually at the IP level and header tricks will not help. In that case, check whether the site exposes an official API or a data export.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.