Skip to content
All posts

How to pull scraped data into Google Sheets with Apps Script

Integrations4 min read

A lot of scraping ends in a spreadsheet anyway. Google Sheets can call the scrape.land API itself through Apps Script, the JavaScript environment built into every Google spreadsheet, so the rows land where your team already works, with no server to run. This tutorial writes a short script that fetches a practice catalog, writes one row per product, keeps your API key out of the code, and refreshes itself on a schedule.

Step 1: store the key in Script Properties

Open your sheet, then Extensions > Apps Script. Do not paste the key into the code: code gets copied, pasted into chats and shared with a copy of the sheet, and the key would travel with it. Instead, in the Apps Script editor open Project Settings (the gear icon), scroll to Script Properties, and add a property named SCRAPELAND_KEY with your key as the value. The code reads it at run time.

Step 2: the script

Paste this into Code.gs. It asks the API for the title, price and stock of every book on one catalog page of books.toscrape.com, and rewrites a tab called Books with the result.

Apps Script
const API = "https://scrape.land/v1/extract";

function refreshBooks() {
  const key = PropertiesService.getScriptProperties().getProperty("SCRAPELAND_KEY");
  if (!key) throw new Error("Add SCRAPELAND_KEY in Project Settings > Script Properties");

  const body = {
    url: "https://books.toscrape.com/catalogue/page-1.html",
    fields: {
      books: {
        css: "article.product_pod",
        fields: { title: "h3 a@title", price: ".price_color", stock: ".availability" },
      },
    },
  };
  const res = UrlFetchApp.fetch(API, {
    method: "post",
    contentType: "application/json",
    headers: { "X-Api-Key": key },
    payload: JSON.stringify(body),
    muteHttpExceptions: true, // read the API's error message instead of a generic one
  });
  if (res.getResponseCode() !== 200) {
    throw new Error("scrape.land " + res.getResponseCode() + ": " + res.getContentText());
  }

  const books = JSON.parse(res.getContentText()).data.books;
  const rows = books.map((b) => [b.title, b.price, b.stock, new Date()]);

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName("Books") || ss.insertSheet("Books");
  sheet.clearContents();
  sheet.getRange(1, 1, 1, 4).setValues([["Title", "Price", "Stock", "Fetched at"]]);
  sheet.getRange(2, 1, rows.length, 4).setValues(rows);
}

Click Run with refreshBooks selected. The first run asks you to authorize the script to connect to an external service and edit the spreadsheet; that is Google's standard prompt for UrlFetchApp and SpreadsheetApp.

What lands in the sheet

This is the exact request the script sends. We ran it against the API and it returned twenty books; here are the first eight, in the columns the script writes:

Terminal showing the extraction request for books.toscrape.com page 1 and a table of the first eight rows returned: A Light in the Attic 51.77 pounds, Tipping the Velvet, Soumission, Sharp Objects and more, all In stock
The real response to the script's request, shown as the rows it writes: title, price, stock.

Prices arrive as text ("£51.77"), exactly as the page shows them. To get numbers you can sum, either strip the symbol in the script (parseFloat(b.price.replace(/[^0-9.]/g, ""))) or, on the Scale plan and up, use an AI prompt with a schema that declares "price": "number" (see AI schema extraction).

Step 3: refresh on a schedule

Apps Script can run a function on a timer. Run this once to create a daily trigger, or add it from the Triggers page (the clock icon) in the editor:

Apps Script
function installDailyTrigger() {
  ScriptApp.newTrigger("refreshBooks").timeBased().everyDays(1).atHour(6).create();
}

To keep a history instead of overwriting, replace clearContents() with sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 4).setValues(rows) so every run appends. That turns the sheet into a simple price tracker; monitoring prices on a schedule covers the same idea with cron and a CSV.

A menu for your team

Apps Script
function onOpen() {
  SpreadsheetApp.getUi().createMenu("Scraping")
    .addItem("Refresh books", "refreshBooks")
    .addToUi();
}

With this in the same file, the sheet gets a Scraping menu, and anyone with edit access can refresh the data without opening the editor. Keep in mind that editors can also open Project Settings and read the key there, so share edit access only with people you would trust with the key, and give everyone else view access.

Tips

What it costs

Each run of the script is one plain fetch with CSS fields: 1 request unit. A daily refresh is about 30 units a month, well inside the Free plan's 1,000 requests. See pricing.

Next steps

Selectors and endpoints are in the docs. Create a free account, paste the key into Script Properties, and run the script.

Start free with 1,000 requests Read the docs