Skip to content
Automation3 min read

Fetch a table from URL in Google sheets if IMPORTHTML fails due to WAF blocking

A release-tracking dashboard, and the firewall that decided to stop feeding it.

Cover image for a post about Google Sheets automation

Fetch a Table From a URL in Google Sheets When IMPORTHTML Fails (WAF Blocking)

I maintain a personal Google Sheet dashboard to track the latest releases of PHP, Drupal, Drush, and various other open-source solutions in a single place. For most tracking, the fantastic site Endoflife.date works flawlessly.

However, some sites utilize aggressive Web Application Firewalls (WAFs) that block automated scraping requests. When this happens, Google Sheets’ native =IMPORTHTML() function fails and throws a generic error.

The culprits are usually strict security rules blocking the default user-agent string sent by Google’s servers. To bypass these simpler WAF restrictions, I wrote a custom Google Apps Script that mimics IMPORTHTML but allows you to modify the user-agent header to look like a standard web browser request.

Feel free to use the Apps Script below if you are hitting a similar wall!

/**
 * Fetches and parses an HTML table from a URL, mimicking the IMPORTHTML function.
 * This version uses UrlFetchApp to include a User-Agent header, which helps
 * bypass restrictions on standard Google Sheets imports.
 *
 * @param {string} url The URL of the page to fetch.
 * @param {string} query The type of element to look for. Currently, only "table" is supported.
 * @param {number} index The 1-based index of the table to retrieve from the page.
 * @return {Array>} A 2D array of the table data.
 * @customfunction
 */
function IMPORTHTML_WITH_USER_AGENT(url, query, index) {
  // Check if the required arguments are provided.
  if (!url || !query || !index) {
    return "Error: Missing required arguments. Usage: IMPORTHTML_WITH_USER_AGENT(url, query, index)";
  }

  // This script currently only supports fetching tables.
  if (query.toLowerCase() !== "table") {
    return `Error: Query type "${query}" is not supported. Only "table" is available.`;
  }

  // Adjust index to be zero-based for array access.
  const tableIndex = index - 1;
  if (tableIndex /is;
    const tables = content.match(new RegExp(tableRegex.source, 'gis'));

    if (!tables || tables.length === 0) {
      return "Error: No tables found on the page.";
    }

    if (tableIndex >= tables.length) {
      return `Error: Table with index ${index} not found. Only ${tables.length} tables were found.`;
    }

    const targetTableHtml = tables[tableIndex];
    const result = [];

    // Find all table rows () within the selected table.
    const rowRegex = //is;
    const rows = targetTableHtml.match(new RegExp(rowRegex.source, 'gis'));

    if (rows) {
      rows.forEach(rowHtml => {
        const rowData = [];
        // Find all table cells ( for headers,  for data) within each row.
        const cellRegex = /([\s\S]*?)/is;
        const cells = rowHtml.match(new RegExp(cellRegex.source, 'gis'));

        if (cells) {
          cells.forEach(cellHtml => {
            // Extract the inner content of the cell.
            const cellMatch = cellHtml.match(cellRegex);
            let cellContent = cellMatch ? cellMatch[2] : '';
            // Clean the content by removing any remaining HTML tags and trimming whitespace.
            cellContent = cellContent.replace(/]*>/g, '').trim();
            rowData.push(cellContent);
          });
        }
        result.push(rowData);
      });
    }

    // Return the 2D array, which will populate the cells in the spreadsheet.
    return result;

  } catch (e) {
    // Return a descriptive error message if the script fails.
    return `Error: An unexpected error occurred. ${e.toString()}`;
  }
}

How to Set It Up in Google Sheets

Deploying custom scripts in Google Sheets is straightforward. Follow these steps to get started:

  • Open your target spreadsheet in Google Sheets.

  • In the top menu, navigate to Extensions > Apps Script.

  • Clear out any default boilerplate code inside the editor block.

  • Copy the complete script provided above and paste it into the editor.

  • Click the Save project icon (the floppy disk symbol) at the top or press Ctrl + S (Cmd + S on Mac).

  • Return to your spreadsheet. You can now use the new custom function just like the native alternative:

Excel

=IMPORTHTML_WITH_USER_AGENT("https://example.com/data-page", "table", 1)

Technical Disclaimer: This script utilizes Regular Expressions (RegEx) to process and extract the DOM layout. While this lightweight approach works flawlessly for traditional data tables, heavily structured sites using deeply nested modern tables or dynamic client-side rendering may require more granular adjustments.

All writing