Turns a name and a company into that person's profile page address, using only pages a web search actually returned.
Uses
Claude
Google Sheets
Serper
Find each person's public profile page
Give it a sheet of people with a name and a company each, and run it to look up the next batch. Each person gets one web search restricted to the profile site you name, the AI picks the result that is genuinely their own page, and what was found is written to a separate results tab.
Each row holds the profile address, whether it was found, the exact search used, a note and the date. No address is ever built or guessed. The only value that can reach the 'Profile URL' column is one the search itself returned, and it must match a result exactly and sit on the site you named. When nothing can be confirmed the cell is left empty and the note says why. Nobody is looked up twice: anyone already on the results tab is skipped, rows are only ever appended, and a row missing a name or company is reported and skipped.
Before the first run you need a tab with 'Name' and 'Company' columns and an empty tab for the results, which gets its header row on the first run.
spreadsheetUrl to blank.Set spreadsheetUrl to blank.peopleSheetName to blank.Set peopleSheetName to blank.resultsSheetName to blank.Set resultsSheetName to blank.profileSite to blank.Set profileSite to blank.maxPeople to 10.Set maxPeople to 10.profileSite lowercase and save it as siteHostRaw.Make profileSite lowercase and save it as siteHostRaw.siteHostRaw split by :// split by / and save it as siteHost.Get the first item of the last item of siteHostRaw split by :// split by / and save it as siteHost.siteHost is emptyIf siteHost is emptyspreadsheetUrl. Save it as record.Read the rows of spreadsheetUrl. Save it as record.recordHeaders to record.headers.Set recordHeaders to record.headers.recordRows to record.rows.Set recordRows to record.rows.recordHeaders is emptyIf recordHeaders is emptyresultsSheetName in spreadsheetUrl.Add a row to resultsSheetName in spreadsheetUrl.alreadyLookedUp to an empty list.Set alreadyLookedUp to an empty list.recordRow in recordRows:For each recordRow in recordRows:recordErrorIf a step below fails, save the error as recordErrorrecordKey to recordRow.Name in lowercase @ recordRow.Company in lowercase.Set recordKey to recordRow.Name in lowercase @ recordRow.Company in lowercase.recordKey to the end of alreadyLookedUp and save it as alreadyLookedUp.Add recordKey to the end of alreadyLookedUp and save it as alreadyLookedUp.recordError as text.Show in the output: Ignored a row already on the record that has no Name and Company: recordError as text.spreadsheetUrl. Save it as people.Read the rows of spreadsheetUrl. Save it as people.peopleRows to people.rows.Set peopleRows to people.rows.checkedCount to 0.Set checkedCount to 0.foundCount to 0.Set foundCount to 0.person in peopleRows:For each person in peopleRows:checkedCount multiplied by 1 is greater than or equal to maxPeople multiplied…If checkedCount multiplied by 1 is greater than or equal to maxPeople multiplied…personErrorIf a step below fails, save the error as personErrorperson.Name and save it as personName.Trim person.Name and save it as personName.person.Company and save it as personCompany.Trim person.Company and save it as personCompany.personKey to personName in lowercase @ personCompany in lowercase.Set personKey to personName in lowercase @ personCompany in lowercase.personName is empty or personCompany is emptyIf personName is empty or personCompany is emptySkipped a row that does not have both a Name and a Company..Show in the output: Skipped a row that does not have both a Name and a Company..personKey.Show in the output: Already on the record, so not looked up again: personKey.searchUsed to @personName @personCompany site:siteHost.Set searchUsed to @personName @personCompany site:siteHost.searchUsed. Save it as searchResult.Search Google for searchUsed. Save it as searchResult.hits to searchResult.results.Set hits to searchResult.results.linkList to an empty list.Set linkList to an empty list.optionsText to blank.Set optionsText to blank.hit in hits:For each hit in hits:hitError If a step below fails, save the error as hitErrorhit.link and save it as hitLink.Trim hit.link and save it as hitLink.hitLink is nullIf hitLink is nullhitLink to blank.Change hitLink to blank.hit.title and save it as hitTitle.Trim hit.title and save it as hitTitle.hitTitle is nullIf hitTitle is nullhitTitle to blank.Change hitTitle to blank.hit.snippet and save it as hitSnippet.Trim hit.snippet and save it as hitSnippet.hitSnippet is nullIf hitSnippet is nullhitSnippet to blank.Change hitSnippet to blank.hitText to hitTitle hitSnippet.Set hitText to hitTitle hitSnippet.hitLink isn't empty and hitText contains personName (ignoring case)If hitLink isn't empty and hitText contains personName (ignoring case)hitLink to the end of linkList and save it as linkList.Add hitLink to the end of linkList and save it as linkList.optionsText to optionsText@hitLink - hitTitleChange optionsText to optionsText@hitLink - hitTitle
.hitError as text.Show in the output: Skipped a search result that could not be read: hitError as text.profileUrl to blank.Set profileUrl to blank.lookupNote to No page in the search results could be confirmed as this person's own profile, so the Profile URL was left empty..Set lookupNote to No page in the search results could be confirmed as this person's own profile, so the Profile URL was left empty..linkList isn't emptyIf linkList isn't emptypromptIntro to You are matching one person to their own public profile page.Set promptIntro to You are matching one person to their own public profile page.
Person: personName
Company: personCompany
.optionsText'. Save it as verdict.
Maximum response length: 200verdict.response_text and save it as candidate.Trim verdict.response_text and save it as candidate.candidate lowercase and save it as candidateLower.Make candidate lowercase and save it as candidateLower.linkList includes candidate and save it as cameFromResults.Check whether linkList includes candidate and save it as cameFromResults.candidateLower split by :// split by / and save it as candidateHost.Get the first item of the last item of candidateLower split by :// split by / and save it as candidateHost.hostMatches to candidateHost is siteHost or candidateHost ends with .@siteHost.Set hostMatches to candidateHost is siteHost or candidateHost ends with .@siteHost.schemeOk to candidateLower starts with https:// or candidateLower starts with http://.Set schemeOk to candidateLower starts with https:// or candidateLower starts with http://.cameFromResults is yes and schemeOk is yes and hostMatches is yesIf cameFromResults is yes and schemeOk is yes and hostMatches is yesprofileUrl to candidate.Change profileUrl to candidate.lookupNote to Matched to a page this search returned..Change lookupNote to Matched to a page this search returned..lookupNote to The search returned no page on siteHost that mentions this person by name.Change lookupNote to The search returned no page on siteHost that mentions this person by name.foundFlag to no.Set foundFlag to no.profileUrl isn't emptyIf profileUrl isn't emptyfoundFlag to yes.Change foundFlag to yes.foundCount.Add 1 to foundCount.resultsSheetName in spreadsheetUrl.Add a row to resultsSheetName in spreadsheetUrl.personKey to the end of alreadyLookedUp and save it as alreadyLookedUp.Add personKey to the end of alreadyLookedUp and save it as alreadyLookedUp.checkedCount.Add 1 to checkedCount.personError as text.Show in the output: Skipped a row that could not be read: personError as text.checkedCount as text people and found a profile page for foundCount as text. Every result is on the '@resultsSheetName' tab.Show in the output: Looked up checkedCount as text people and found a profile page for foundCount as text. Every result is on the '@resultsSheetName' tab.