Check your Google page titles for all of your search queries, across different countries and languages. 600 requests per month, for free. π
Update: This script now includes a meta description checker + timestamp (thanks to Jose Luis Hernando).
Check out the GIF to see it in action π₯

Or get the previous template that only includes page titles here.
How to add the Script to Google Sheets
1. Sign up to RapidAPI
Sign up to RapidAPI, an API marketplace, at https://rapidapi.com/auth/sign-up (you can even use your Google Account).

2. Subscribe to the Google Search API
Once you’re all set up on RapidAPI head to https://rapidapi.com/apigeek/api/google-search3/pricing and subscribe to the Basic Plan (Free). This plan has a hard 600 requests per month limit.

3. Get your API Key
The good thing about RapidAPI is that your API key can be used universally across any of the APIs in their marketplace. To copy your API key, go to https://rapidapi.com/developer/dashboard and head down to ‘My Apps’ and then ‘Security’. From there, you can copy your Application Key.

4. Copy the script below:
The script below also includes a cool script from https://script.gs/visually-display-status-when-looping-through-google-sheets-data/ that visually displays the status of each fetch as it’s called. π
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('Title Checker')
.addItem('Check Titles', 'titlechecker')
.addToUi();
}
function titlechecker() {
// Add Moment.js library
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js').getContentText());
// add API key from https://rapidapi.com/apigeek/api/google-search3
const apiKey = "";
// get active spreadsheet
const ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// function to get last row of single column
const getLastRowCol = (range) => {
let rowNum = 0;
let blank = false;
for (let row = 0; row < range.length; row++) {
if (range[row][0] === "" && !blank) {
rowNum = row;
blank = true;
} else if (range[row][0] !== "") {
blank = false;
};
};
return rowNum;
}
// range variables to make sure data can always be appended
const titleRange = ss.getRange('B:B').getValues();
const titleLastRow = getLastRowCol(titleRange) + 1;
const lastRowDiff = ss.getLastRow() - titleLastRow + 1;
const queryRange = ss.getRange(titleLastRow, 1, lastRowDiff).getValues();
// domain and language variables
const domain = ss.getRange('J2').getValue().replace(/^(?:https?:\/\/)?(?:www\.)?/i, "").split('/')[0];
const country = ss.getRange('K2').getValue();
const language = ss.getRange('L2').getValue();
// function to fetch SERPs from rapidapi
const fetchSerps = (keyword, language, country) => {
try {
const options = {
'method': 'GET',
'contentType': 'application/json',
'headers': {
'x-rapidapi-key': apiKey,
'x-rapidapi-host': 'google-search3.p.rapidapi.com'
}
};
const serpResponse = UrlFetchApp.fetch(`https://google-search3.p.rapidapi.com/api/v1/search/q=${keyword}&gl=${country}&hl=lang_${language}&num=100`, options);
const content = JSON.parse(serpResponse.getContentText());
const organicResults = content.results;
let row = [],
data;
for (i = 0; i < organicResults.length; i++) {
let data = organicResults[i];
const link = data.link;
// if any of the top 100 ranking URLs include the domain, return data
if (link.includes(domain)) {
// scrape the ranking URL
const urlResponse = UrlFetchApp.fetch(data.link).getContentText();
const $ = Cheerio.load(urlResponse);
// extract the title
const title = $('title').first().text().trim();
const description = $('meta[name="description"]').attr('content').trim();
// check whether ranking title is different to page title
const changed = title !== data.title ? "Changed" : "Unchanged";
const changed2 = description !== data.description? "Changed" : "Unchanged";
row.push(title, data.title, changed, description,data.description,changed2);
return row;
}
}
} catch (e) {
return false;
}
}
// loop over remaining URLs and set values while the loop is running
queryRange.forEach(function(row, i) {
row.forEach(function(col) {
// visually display a fetch status. Script inspired by https://script.gs visually-display-status-when-looping-through-google-sheets-data/
ss.getRange(titleLastRow + i, 9).setValue("Loading...");
SpreadsheetApp.flush();
const check = fetchSerps(col, language, country);
// if the SERPs check is successful, return row
check ? ss.getRange(titleLastRow + i, 2, 1, 6).setValues([check]) : ss.getRange(titleLastRow + i, 2).setValue("No data");
ss.getRange(titleLastRow + i, 9).setValue("Done");
// Add timestamp of extraction
ss.getRange(titleLastRow + i, 8).setValue(moment().format('llll'))
SpreadsheetApp.flush();
});
});
}
5. Head over to Google Sheets
Or if you’re really smart, create a new sheet by going to: https://sheets.new
Select Script editor from the Tools menu.
Paste the script and replace line 14 with your API key:
const apiKey = 'addapikeyhere';
Save it. You’ll also need to run the function in Apps Script first to authorise the script.

6. Add the Cheerio Library in Apps Script
Search for the Cheerio Library using the ID below. The Cheerio Library makes it easier to parse the HTML from the requested page.
1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0
If youβre not sure how to add a library in Apps Script, follow the gif below:

7. Run the script by heading to the ‘Title Checker’ menu and selecting ‘Check Titles’
To run the script, it’s easier if you copy a version of the Google Sheets template at the start of this guide. If you do want to build your own template, you’ll need to make sure:
1. You have 9 headings: Query (A1), Page Title (B1), Google Search Title (C1), Changed? (D1), Meta Description (E1), Google Description (F1), Changed? (G1), Timestamp (H1), Status (I1).
2. You add all of your search queries in Column A, beneath the Query heading.
3. You add inputs for Domain (J2), Country (K2), and Language (L2). The template at the beginning of this guide has a drop-down menu to select all countries and languages.
See the gif at the beginning of guide for more detail.

Thanks for stopping by π
I’m Andrew Charlton, the Google Sheets nerd behind Keywords in Sheets. π€
Questions? Get in touch with me on social or comment below π
This is awesome, thank you!
Very cool! What might be a nice addition, is to add URL’s and have them checked?
Cheers,
Vincent
For some reason the sheet is only in ‘View’ access for me — any advice? I can’t use it if it is in view.
All my sheets are ‘view’ access, so I can have a default template. You’ll just need to save a copy of the sheet and voila!
Hi, very nice tool but I have a great majority of “no data” and very few results. Is there any chance you see where it could come from?
Cheers π
Thank you. You’ll get “no data” only for a few reasons:
1) API Key has been input incorrectly
2) You haven’t subscribed to the free plan of the search API
3) The domain prohibits scraping
Hope that helps! π
Oh yes, that definitely helps! Is there any way to change the UA of the scraper ?
Thank you π