先に断っておくとこのコードでサジェストは作れませんが、
エンジニアなら何をすればわかる(もしくは何をしても無駄か)がわかるようになっています。
(ただjsでつくるのはかなり面倒です。)
実際は本当のユーザがあるキーワードの検索ボリュームに対して X%の回数を異なるIPから検索するとサジェストを生成できます。
そのgoogle extentionの原型(はじめるにはここからが良い)を作りました。
目次
まずあるフォルダをローカルに作ってください
そして以下のコードを保存していきましょう
background.js
const API_KEY = "AIzaSyCzQa9lX-SoK9KG7Y6MheVO5iq6abwe3R6ldc";//これは実際のキーではなくてランダムな文字です
const SEARCH_ENGINE_ID = "802c27322a9fc374a6f4";//これは実際のキーではなくてランダムな文字です
const SPREADSHEET_ID = "1V89e7ddjLK1TF8TCW1WcyaEYNk-Hw3H_eedWnWF0dx8ek_HKa8";//これは実際のキーではなくてランダムな文字です
const INTERVAL = 30000;
const STOP_TIME = 30 * 60 * 1000;
let interval;
let currentIndex = 1;
// === 📝 ログを記録 & スプレッドシートに保存 ===
async function logMessage(row, message) {
console.log(`[ログ] ${message}`);
await appendToSheet(row, message);
chrome.storage.local.get("logs", (data) => {
let logs = data.logs || [];
logs.push(message);
if (logs.length > 20) logs.shift();
chrome.storage.local.set({ logs: logs });
});
}
async function logError(row, message) {
console.error(`[エラー] ${message}`);
await appendToSheet(row, `⚠️ ${message}`);
chrome.storage.local.get("logs", (data) => {
let logs = data.logs || [];
logs.push(`⚠️ ${message}`);
if (logs.length > 20) logs.shift();
chrome.storage.local.set({ logs: logs });
});
}
// === 📝 スプレッドシートの「次の空欄の列」にログを追記 ===
async function appendToSheet(row, message) {
let sheetTab = "yamamoto";
let url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${sheetTab}!${row}:${row}?key=${API_KEY}`;
try {
let response = await fetch(url);
let data = await response.json();
if (!data.values || data.values.length === 0) {
console.error(`[シート更新エラー] ${row}行目のデータを取得できませんでした`);
return;
}
// **C列から始めて最初の空欄の列を探す**
let nextColumn = data.values[0].length + 1;
if (nextColumn < 3) nextColumn = 3; // C列から記録
let columnLetter = getColumnLetter(nextColumn);
let updateRange = `${sheetTab}!${columnLetter}${row}`;
let updateUrl = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${updateRange}?valueInputOption=RAW&key=${API_KEY}`;
let body = { values: [[message]] };
await fetch(updateUrl, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
console.log(`[シート更新] ${row}行目の${columnLetter}列にログを追加: ${message}`);
} catch (error) {
console.error(`[シート更新エラー] ${error}`);
}
}
// === 🔠 列番号をアルファベットに変換 ===
function getColumnLetter(columnNumber) {
let letter = "";
while (columnNumber > 0) {
let remainder = (columnNumber - 1) % 26;
letter = String.fromCharCode(65 + remainder) + letter;
columnNumber = Math.floor((columnNumber - 1) / 26);
}
return letter;
}
// === 📝 スプレッドシートのデータ取得 ===
async function fetchSheetData(sheetTab) {
let url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${sheetTab}!A:B?key=${API_KEY}`;
console.log(`[API] シートデータ取得開始: ${url}`);
try {
let response = await fetch(url);
let data = await response.json();
if (!data.values || data.values.length === 0) {
console.error(`[API] スプレッドシートのデータ取得失敗: データなし`);
return [];
}
console.log(`[API] シートデータ取得成功: ${data.values.length} 行`);
return data.values;
} catch (error) {
console.error(`[API] スプレッドシートのデータ取得エラー: ${error}`);
return [];
}
}
// === 🔄 検索&クリックを実行 ===
async function searchAndOpen(sheetTab) {
chrome.storage.local.get(["startTime"], async (data) => {
if (Date.now() - data.startTime > STOP_TIME) {
stopScript();
return;
}
});
let rows = await fetchSheetData(sheetTab);
if (!rows || rows.length === 0) return;
if (currentIndex >= rows.length) {
currentIndex = 1;
}
let searchWord = rows[currentIndex][0];
let targetSite = rows[currentIndex][1];
if (searchWord && targetSite) {
await logMessage(currentIndex + 1, `[A列${currentIndex + 1}行目] 「${searchWord}」を検索します`);
await new Promise(resolve => setTimeout(resolve, 10000));
let searchUrl = await fetchSearchResults(searchWord, targetSite);
if (searchUrl) {
await logMessage(currentIndex + 1, `[B列${currentIndex + 1}行目] 「${targetSite}」を見つけてクリックしました`);
chrome.tabs.create({ url: searchUrl, active: false });
} else {
await logError(currentIndex + 1, `[B列${currentIndex + 1}行目] 「${targetSite}」のURLが見つかりませんでした`);
}
}
currentIndex++;
}
// === 🔍 Google検索を実行 ===
async function fetchSearchResults(query, targetSite) {
let url = `https://www.googleapis.com/customsearch/v1?q=${encodeURIComponent(query)}&cx=${SEARCH_ENGINE_ID}&key=${API_KEY}`;
console.log(`[API] Google検索開始: ${url}`);
try {
let response = await fetch(url);
let data = await response.json();
if (!data.items) {
console.error(`[検索失敗] ${query}: 検索結果なし`);
return null;
}
let foundItem = data.items.find(item => item.link.includes(targetSite) || targetSite.includes(item.link));
if (foundItem) {
console.log(`[検索成功] ${foundItem.link} を発見`);
return foundItem.link;
} else {
console.error(`[検索結果] ${targetSite} のURLが見つかりませんでした`);
return null;
}
} catch (error) {
console.error(`[API] Google検索エラー: ${error}`);
return null;
}
}
// === 🔄 スクリプトの開始 / 停止 ===
function startScript(sheetTab) {
chrome.storage.local.set({ isRunning: true, startTime: Date.now(), sheetTab: sheetTab });
searchAndOpen(sheetTab);
interval = setInterval(() => searchAndOpen(sheetTab), INTERVAL);
}
function stopScript() {
chrome.storage.local.set({ isRunning: false });
clearInterval(interval);
}
// === 📨 メッセージリスナー ===
chrome.runtime.onMessage.addListener((request) => {
if (request.action === "start") {
startScript(request.sheetTab);
} else if (request.action === "stop") {
stopScript();
}
});
ここで前半で書いてあるようにgoogleのクラウドからspredsheetのAPIキーとsearch apiのキー、そして
検索エンジンのIDを登録してください
検索エンジンのIDはここから取得できます。(エリアを日本にすることを忘れないこと)
次に、
manifest.json
{
"manifest_version": 3,
"name": "Google Auto Search & Click",
"version": "1.0",
"permissions": ["tabs", "activeTab", "storage", "scripting"],
"background": {
"service_worker": "background.js"
},
"host_permissions": ["https://www.google.com/*"],
"content_scripts": [
{
"matches": ["https://www.google.com/search*"],
"js": ["content.js"]
}
],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
次にpopup.jsを書きましょう
document.addEventListener("DOMContentLoaded", () => {
const statusDiv = document.getElementById("status");
const toggleButton = document.getElementById("toggle");
const logDiv = document.getElementById("log");
function updateStatus() {
chrome.storage.local.get(["isRunning", "startTime", "logs"], (data) => {
if (data.isRunning) {
let elapsedTime = Math.floor((Date.now() - data.startTime) / 1000);
let minutes = Math.floor(elapsedTime / 60);
let seconds = elapsedTime % 60;
statusDiv.textContent = `稼働中 (経過時間: ${minutes}:${seconds.toString().padStart(2, "0")})`;
statusDiv.className = "running";
toggleButton.textContent = "停止";
} else {
statusDiv.textContent = "停止中";
statusDiv.className = "stopped";
toggleButton.textContent = "開始";
}
// ログの更新
logDiv.innerHTML = (data.logs || []).join("<br>") || "ログ出力中...";
});
}
// **リアルタイムで経過時間を更新**
setInterval(updateStatus, 1000); // 1秒ごとに更新
toggleButton.addEventListener("click", () => {
chrome.storage.local.get(["isRunning", "sheetTab"], (data) => {
if (!data.isRunning) {
let sheetTab = prompt("タブ名はなんですか?(例:yamamoto)");
if (!sheetTab) {
alert("タブ名が設定されていません。");
return;
}
chrome.storage.local.set({ isRunning: true, startTime: Date.now(), sheetTab: sheetTab, logs: [] });
chrome.runtime.sendMessage({ action: "start", sheetTab: sheetTab });
} else {
chrome.storage.local.set({ isRunning: false });
chrome.runtime.sendMessage({ action: "stop" });
}
});
});
// 初期状態更新
updateStatus();
});
document.addEventListener("DOMContentLoaded", () => {
const statusDiv = document.getElementById("status");
const toggleButton = document.getElementById("toggle");
const logDiv = document.getElementById("log");
function updateStatus() {
chrome.storage.local.get(["isRunning", "startTime", "logs"], (data) => {
if (data.isRunning) {
let elapsedTime = Math.floor((Date.now() - data.startTime) / 1000);
let minutes = Math.floor(elapsedTime / 60);
let seconds = elapsedTime % 60;
statusDiv.textContent = `稼働中 (経過時間: ${minutes}:${seconds.toString().padStart(2, "0")})`;
statusDiv.className = "running";
toggleButton.textContent = "停止";
} else {
statusDiv.textContent = "停止中";
statusDiv.className = "stopped";
toggleButton.textContent = "開始";
}
// ログの更新
logDiv.innerHTML = (data.logs || []).join("<br>") || "ログ出力中...";
});
}
// **リアルタイムで経過時間を更新**
setInterval(updateStatus, 1000); // 1秒ごとに更新
toggleButton.addEventListener("click", () => {
chrome.storage.local.get(["isRunning", "sheetTab"], (data) => {
if (!data.isRunning) {
let sheetTab = prompt("タブ名はなんですか?(例:yamamoto)");
if (!sheetTab) {
alert("タブ名が設定されていません。");
return;
}
chrome.storage.local.set({ isRunning: true, startTime: Date.now(), sheetTab: sheetTab, logs: [] });
chrome.runtime.sendMessage({ action: "start", sheetTab: sheetTab });
} else {
chrome.storage.local.set({ isRunning: false });
chrome.runtime.sendMessage({ action: "stop" });
}
});
});
// 初期状態更新
updateStatus();
});
そしてpopup.htmlを書きましょう
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拡張機能ステータス</title>
<style>
body {
width: 250px;
text-align: center;
font-family: Arial, sans-serif;
}
#status {
font-size: 16px;
font-weight: bold;
margin: 10px;
padding: 5px;
border-radius: 5px;
}
.running {
background-color: green;
color: white;
}
.stopped {
background-color: red;
color: white;
}
button {
margin-top: 10px;
padding: 5px 10px;
font-size: 14px;
}
#log {
text-align: left;
font-size: 12px;
background: #f4f4f4;
border: 1px solid #ccc;
padding: 5px;
margin-top: 10px;
height: 100px;
overflow-y: auto;
}
</style>
</head>
<body>
<h3>拡張機能の状態</h3>
<div id="status" class="stopped">停止中</div>
<button id="toggle">開始</button>
<div id="log">ログ出力中...</div>
<script src="popup.js"></script>
</body>
</html>
そしてcontent.jsを書いてください
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "findTargetSite") {
let targetSite = request.targetSite;
let links = document.querySelectorAll("a");
for (let link of links) {
if (link.href.includes(targetSite)) {
link.click();
sendResponse({ success: true, time: new Date().toLocaleTimeString() });
return;
}
}
sendResponse({ success: false });
}
});
最後に同じフォルダにicon.pngという名前で適当なアイコンを保存してください
そして、
エクステンションの画面にいきます
ここの「パッケージ化されていない拡張機能を読み込む」でフォルダを選択して読み込んでください
次にスプレッドシートを開き設定していきます。
2行のA列に検索したい言葉、B列にその言葉で検索した時にクリックしたいドメインを入力します

そしてextentionを実行しましょう
するとこんな感じになります
今、タブは「yamamoto」というタブで書いているので
検索条件が記載されているyamamotoというタブ名を入力します

どんどん検索してクリックしてタブが開かれていくことがわかりますね
あまりやりすぎてもブロックされてしまうので10秒間ずつのアクションで
30分間動くようにつくっています。
===
実際はこれだけだとサジェストは生成されません。
cxのブラウザではなくて本当の
https://www.google.com/search?q=xxxxxsourceid=chrome&ie=UTF-8
にアクセスして検索をかけないといけません。
cxはあくまでもカスタマイズなのでサジェストには影響しないからです。
原型としてはよいコードかと思いますので
みなさん使ってください。ここからsearchでカスタマイズできます。
後ほどsearchで方法もありますが
大変なので仕事としてやります。
例えばgoogleはフォームから検索をしたときにはパラメーターにsxsrfやgs_lpというパラメータをつけています。
これは同じ言葉を同じフォームから検索しても必ず違うものになります。
このidはgoogle側が発行してるのでここだけランダムに生成したidにしても
むしろ「こいつサジェストをハックしようとしているか、ランキングを操作しようとしているな」と判断されます。
ちなみに同じパラメーターでよければ
my-extension/
├── manifest.json
├── popup.html
├── popup.js
にしてpopup.htmlは
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Automated Search</title>
</head>
<body>
<h3>Enter Keywords:</h3>
<div id="keyword-list">
<div class="keyword-item">
<input type="text" class="keyword-input" placeholder="Enter a keyword">
<button class="remove-keyword">-</button>
</div>
</div>
<button class="add-keyword">+</button>
<button id="startButton">Start Search</button>
<div id="logs"></div> <!-- ログをここに表示 -->
<script src="popup.js"></script>
</body>
</html>
popup.jsは
document.addEventListener('DOMContentLoaded', function() {
// プラスボタンで新しいキーワード入力フィールドを追加
document.querySelector('.add-keyword').addEventListener('click', function() {
const keywordList = document.getElementById('keyword-list');
const newKeywordItem = document.createElement('div');
newKeywordItem.classList.add('keyword-item');
newKeywordItem.innerHTML = `
<input type="text" class="keyword-input" placeholder="Enter a keyword">
<button class="remove-keyword">-</button>
`;
keywordList.appendChild(newKeywordItem);
// 新しい入力フィールドに削除ボタンのイベントを追加
newKeywordItem.querySelector('.remove-keyword').addEventListener('click', function() {
newKeywordItem.remove();
});
});
// 「Start Search」ボタンをクリックしたときの処理
document.getElementById('startButton').addEventListener('click', function() {
// キーワードリストの取得
const keywords = [];
document.querySelectorAll('.keyword-input').forEach(input => {
const keyword = input.value.trim();
if (keyword.length > 0) {
keywords.push(keyword);
}
});
if (keywords.length === 0) {
logMessage("Please enter at least one keyword.");
return;
}
logMessage(`Starting search for ${keywords.length} keywords...`);
let searchCount = 1; // 検索回数のカウント
// 5秒ごとに検索を繰り返し、1分間続ける
const interval = setInterval(function() {
// キーワードを順番に選択する
const currentKeyword = keywords[(searchCount - 1) % keywords.length];
logMessage(`Searching for (Search #${searchCount}): ${currentKeyword}`);
// 現在のタブのURLを取得して、パラメーターを抽出
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
const currentTabUrl = tabs[0].url; // 現在のタブのURL
const urlParams = new URLSearchParams(new URL(currentTabUrl).search);
// 抽出したパラメーターを使って新しい検索URLを作成
const newSearchURL = `https://www.google.com/search?q=${encodeURIComponent(currentKeyword)}&${urlParams.toString()}`;
// 新しいタブをバックグラウンドで開く
chrome.tabs.create({ url: newSearchURL, active: false }, function(tab) {
logMessage(`Opened tab for: ${currentKeyword} with parameters (Tab ID: ${tab.id})`);
});
});
// 検索回数をインクリメント
searchCount++;
}, 5000); // 5秒ごとに検索
// 1分後に自動的に停止
setTimeout(function() {
clearInterval(interval);
logMessage("1 minute has passed, stopping search.");
}, 60000); // 1分 = 60,000ms
});
});
// ログを表示する関数
function logMessage(message) {
const logContainer = document.getElementById('logs');
const logElement = document.createElement('p');
logElement.textContent = message;
logContainer.appendChild(logElement);
}
にして、manifestは
{
"manifest_version": 2,
"name": "Automated Google Search",
"version": "1.0",
"description": "An extension to automatically search Google with multiple keywords",
"permissions": [
"tabs"
],
"browser_action": {
"default_popup": "popup.html",
"default_icon": "icon.png" // アイコンを1つに統一
},
"icons": {
"48": "icon.png" // アイコンを1つに統一
},
"background": {
"scripts": ["background.js"],
"persistent": false
}
}
これで、複数ワードを同じブラウザのタブから1分間5秒ごとに検索し続けるextentionは可能ですでもやっぱりsxsrfやgs_lpも完全に攻略した検索が欲しいですよね
マウスを動かしてクリックさせないとできないじゃないかと思うかもしれませんが
その通りです。
document.getElementById('moveAndClickButton').addEventListener('click', function() {
// 指定した座標 (1041, 46) へマウスを移動
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {
code: `
function moveMouse(x, y) {
// マウスを指定された位置に動かす
window.scrollTo(x, y); // ページをスクロールしてマウスポインタを指定の位置に移動
}
moveMouse(1041, 46); // 例:縦46px 横1041px
`
});
});
// クリックした場所の座標を取得
document.getElementById('position').innerHTML = "Click anywhere on the page to get coordinates.";
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {
code: `
document.addEventListener('click', function(e) {
const x = e.clientX; // クリックした横の位置
const y = e.clientY; // クリックした縦の位置
alert('Clicked at: X=' + x + ', Y=' + y);
});
`
});
});
});
このようなコードでマウスを移動させてクリックさせることができます。
クリックをしたり別のタブを開くとextentionが閉じてしまう問題も
background.js
や content.js
でブラウザ全体を制御することが可能です
これを合わせると….(続く)
かなり面倒だということはわかっていただけたのではないかと思います。
(実際はUIPathとかでやれば一撃ではないかと思う方がいると思いますがその通りです。)