Skip to content

Instantly share code, notes, and snippets.

@thetafferboy
Last active April 6, 2025 10:27
Show Gist options
  • Save thetafferboy/60df82aa4270b5dd837b177d5cc4721c to your computer and use it in GitHub Desktop.
Save thetafferboy/60df82aa4270b5dd837b177d5cc4721c to your computer and use it in GitHub Desktop.
AlsoAsked Unanswered Questions (Screaming Frog)
// AlsoAsked Unanswered Questions
// Note: Best used with Screaming Frog "List" mode and given a list of informational / blog pages
//
//
// IMPORTANT:
// You will need to supply your ChatGPT and AlsoAsked API key below.
// These will be stored as part of your SEO Spider configuration in plain text.
// Also be mindful if sharing this script that you will be sharing your API key
// THINGS TO CONFIGURE *****************
const settings = {
chatGpt: {
apiKey: 'ENTER CHATGPT API KEY', // Replace with your OpenAI API key
model: 'gpt-4o-mini', // ChatGPT model to use: https://platform.openai.com/docs/models
},
alsoAsked: {
apiKey: 'ENTER ALSOASKED API KEY', // Replace with your AlsoAsked API key
language: 'en', // Language to search: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
region: 'us', // Region to search: https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
depth: 2, // The depth to serach
// 2 is the default and returns the smallest number of questions and costs 1 credit
// 3 is a deep search and returns the most questions and costs 4 credits
fresh: true, // whether to perform a fresh search, or attempt to use cached results
// if cached results exist, this will not count towards your credit usage
},
};
// END OF THINGS TO CONFIGURE *****************
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const pageH1 = document.querySelector('h1') ? document.querySelector('h1').innerText : '';
// Ensure the page h1 is not empty
if (!pageH1) {
return seoSpider.error('The page H1 is empty');
}
// Get questions from the AlsoAsked API using the page h1
const getQuestions = new Promise(async (resolve, reject) => {
try {
// The options to send to the AlsoAsked API search endpoint
const searchOptions = {
terms: [pageH1],
language: settings.alsoAsked.language,
region: settings.alsoAsked.region,
depth: settings.alsoAsked.depth,
fresh: settings.alsoAsked.fresh,
async: false,
notify_webhooks: false,
};
// Fetch the People Also Ask questions from the AlsoAsked API
const response = await fetch('https://alsoaskedapi.com/v1/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': settings.alsoAsked.apiKey,
},
body: JSON.stringify(searchOptions),
});
// Ensure the response was successful
if (!response.ok) {
const errorDetails = await response.text();
reject(`Failed to fetch questions from AlsoAsked API: ${response.status} - ${response.statusText} \nDetails: ${errorDetails}`);
return;
}
const data = await response.json();
if (data.status !== 'success' || !data.queries.length) {
reject(`No questions found for "${pageH1}"`);
return;
}
// Flatten nested question results
const flattenResults = (questions) => {
return questions.reduce((flattened, result) => {
flattened.push(result.question);
if (result.results) {
flattened.push(...flattenResults(result.results));
}
return flattened;
}, []);
};
resolve(flattenResults(data.queries[0].results));
} catch (error) {
reject(`AlsoAsked API Error: ${error}`);
}
});
// Query OpenAI ChatGPT API to find unanswered questions
const queryChatGpt = (questions) => {
return new Promise(async (resolve, reject) => {
try {
await sleep(1500); // Prevent API rate limiting
const completionsOptions = {
model: settings.chatGpt.model,
response_format: { type: 'json_object' },
max_tokens: 500, // Prevent excessive token usage
messages: [
{
role: 'system',
content: `List the questions in this JSON array ${JSON.stringify(
questions
)} which are not answered in the text content of this page, but would make sense to answer in context to the rest of the content. Output the questions that are not answered in a JSON array of strings within an object called unanswered_questions.`,
},
{
role: 'user',
content: document.body.textContent.substring(0, 5000), // Limit text input to avoid API truncation
},
],
};
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${settings.chatGpt.apiKey}`,
},
body: JSON.stringify(completionsOptions),
});
if (!response.ok) {
const errorDetails = await response.text();
reject(`ChatGPT API Error (${response.status}): ${response.statusText} \nDetails: ${errorDetails}`);
return;
}
const data = await response.json();
if (!data.choices || !data.choices.length || !data.choices[0].message.content) {
reject('No valid response received from ChatGPT');
return;
}
const result = JSON.parse(data.choices[0].message.content);
resolve(result.unanswered_questions || []);
} catch (error) {
reject(`ChatGPT API Error: ${error}`);
}
});
};
// Run the process
return getQuestions
.then(queryChatGpt)
.then((unansweredQuestions) => seoSpider.data(unansweredQuestions))
.catch((error) => seoSpider.error(error));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment