Last active
April 6, 2025 10:27
-
-
Save thetafferboy/60df82aa4270b5dd837b177d5cc4721c to your computer and use it in GitHub Desktop.
AlsoAsked Unanswered Questions (Screaming Frog)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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