「生成AIに聞くより、コードを書いた方が早い」って言ってくる人いないですか?
強者マウントに負けてはいけません。動けばいいのです。書けなくても。
わたくしおきなかと、生成AIを相棒に書かないGAS
の旅に出かけましょう。
2年越しの夢
LINDEでLT登壇したスライド
毎朝数社のwebサイトの更新を確認する業務がめんどくさい。サイトを巡回し更新があればLINE通知する仕組みを作りたかったけど、うまくいかなかった。
この2年間で目覚ましく進化したAI。 もしかして今ならやれるのでは?よしやってやる。
コードは生成AIに書かせて完成!
できたもはこちら
1日に1回指定のサイトを巡回し、記事やURLを取得してスプレッドシードに記録。
その差分をLINEに通知する仕組みが完成💪
コードは1行も書かなかったので、その戯れを振り返ります。
GPTとGASは犬猿の仲(個人意見)
まずコード生成のスピードが遅い。
書けない人が、何回デプロイするのか分かっているのでしょうか。
我々書かない勢は、エラーが出ても、その中身なんて理解しません。
出たエラーを生成AIに打ち込んで、もう一回コードを生成してもらっての繰り返し。
1回の生成スピードの遅さは命取りです。
あと、文字化けなども何度指摘しても直してくれません。
2年前もこういったことが続いて諦めてしまいました。
餅は餅屋 GASならGemini
と思い立って、Geminiを使ってみました。
まずコードの生成スピードが格段に違う。早い。いい。
GASとの相性がすごく良さそうで、エラーが出る回数はかなり減った気がします(体感)
エラーを貼って、修正してもらって…の繰り返しなのですが、
「エラーだけじゃなく画面全体のスクショを提供して」など、賢く提案してくれます。
『エラーが出てるけど、そもそもお前のコードの貼り方や、スクリプトプロパティが間違ってない?』など、先回りして問題解決に努めてくれます。
監視ブロックを乗り越えろ
いくつかのサイトでは、『アクセスがブロックされます』といったエラーが発生してしまいます。
最初から監視しに行こうとするとブロックされるのかな?と推察して、『監視はしなくていいので、シンプルにHTMLの構造を分析して、指定した箇所のデータを抜き出してほしい』、と丁寧に、1ステップずつお願いしてみました。
監視ブロックの壁を乗り越えて、毎回きちんと情報を取得できるようになりました。
総デプロイ回数212回
を要したコード、ご精査ください。
一旦2社分のみ作ってます。
Code.gs (クリックで表示)
/**
* メインの処理を実行し、メールを送信します。
*/
function mainFunction() {
var scriptProperties = PropertiesService.getScriptProperties();
var spreadsheetId = scriptProperties.getProperty("SPREADSHEET_ID");
var ss = SpreadsheetApp.openById(spreadsheetId);
var sheet = ss.getActiveSheet();
// ①スプレッドシートの過去のデータを写す
transferPastData(sheet);
// ②イオンの情報を取得する
var aeonNews = AeonNews.getNews(sheet);
// ③その情報をスプレッドシートに書き込む(イオン)
writeNewsToSheet(sheet, aeonNews, 2, "イオン株式会社"); // 2行目から書き込み
// ④イオン九州のデータを取得する
var aeonKyushuNews = AeonKyushuNews.getNews(sheet);
// ⑤イオンのデータの下に九州のデータを書き連ねる
writeNewsToSheet(sheet, aeonKyushuNews, 2 + aeonNews.length, "イオン九州株式会社"); // イオンのデータの次の行から書き込み
// ⑥差分を見てメールする
sendEmail(sheet);
}
/**
* スプレッドシートの過去のデータを転記します。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
*/
function transferPastData(sheet) {
var lastRow = sheet.getLastRow();
if (lastRow > 1) {
var pastData = sheet.getRange(2, 2, lastRow - 1, 3).getValues();
sheet.getRange(2, 5, lastRow - 1, 3).setValues(pastData);
}
}
/**
* ニュース記事をスプレッドシートに書き込みます。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
* @param {Array<Object>} news - ニュース記事の配列
* @param {number} startRow - 書き込み開始行
* @param {string} siteName - サイト名
*/
function writeNewsToSheet(sheet, news, startRow, siteName) {
var newData = news.map(function (item) {
return [item.date, item.title, item.url];
});
sheet.getRange(startRow, 2, newData.length, 3).setValues(newData);
// サイト名をA列に書き込み
var siteNameArray = [];
for (var i = 0; i < newData.length; i++) {
siteNameArray.push([siteName]);
}
sheet.getRange(startRow, 1, newData.length, 1).setValues(siteNameArray);
}
/**
* 新着記事の有無を確認し、メールを送信します。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
*/
function sendEmail(sheet) {
var allNews = getAllNewsFromSheet(sheet);
var pastTitles = getPastTitles(sheet);
var siteNews = groupNewsBySite(allNews);
var recipient = Session.getActiveUser().getEmail();
var subject = "ニュース更新情報";
var body = "";
for (var site in siteNews) {
var news = siteNews[site];
var newArticles = getNewArticles(news, pastTitles);
body += createSiteMessage(site, news, newArticles);
}
MailApp.sendEmail(recipient, subject, body);
}
/**
* スプレッドシートからすべてのニュース記事を取得します。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
* @returns {Array<Object>} - すべてのニュース記事
*/
function getAllNewsFromSheet(sheet) {
var lastRow = sheet.getLastRow();
var titles = sheet.getRange(2, 3, lastRow - 1, 1).getValues().flat();
var dates = sheet.getRange(2, 2, lastRow - 1, 1).getValues().flat();
var urls = sheet.getRange(2, 4, lastRow - 1, 1).getValues().flat();
var sites = sheet.getRange(2, 1, lastRow - 1, 1).getValues().flat();
return titles.map(function (title, index) {
return {
date: dates[index],
title: title,
url: urls[index],
site: sites[index],
};
});
}
/**
* スプレッドシートから過去の記事タイトルを取得します。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
* @returns {Array<string>} - 過去の記事タイトル
*/
function getPastTitles(sheet) {
return sheet
.getRange(2, 6, 10, 1)
.getValues()
.flat()
.filter(String);
}
/**
* サイトごとにニュース記事をグループ化します。
* @param {Array<Object>} allNews - すべてのニュース記事
* @returns {Object} - サイトごとにグループ化されたニュース記事
*/
function groupNewsBySite(allNews) {
var siteNews = {};
allNews.forEach(function (article) {
if (!siteNews[article.site]) {
siteNews[article.site] = [];
}
siteNews[article.site].push(article);
});
return siteNews;
}
/**
* 新しい記事を取得します。
* @param {Array<Object>} news - ニュース記事
* @param {Array<string>} pastTitles - 過去の記事タイトル
* @returns {Array<Object>} - 新しい記事
*/
function getNewArticles(news, pastTitles) {
return news.filter(function (article) {
return !pastTitles.includes(article.title);
});
}
/**
* サイトごとのメッセージを作成します。
* @param {string} site - サイト名
* @param {Array<Object>} news - ニュース記事
* @param {Array<Object>} newArticles - 新しい記事
* @returns {string} - サイトごとのメッセージ
*/
function createSiteMessage(site, news, newArticles) {
var message = "会社名:" + site + "\n";
var siteUrl = getSiteUrl(site); // サイトURLを取得
if (newArticles.length > 0) {
message += "新着記事がありました。\n\n";
message += "ニュースサイトURL:" + siteUrl + "\n\n"; // サイトURLを追加
newArticles.forEach(function (article) {
message += "日付:" + formatDate(article.date) + "\n";
message += "タイトル:" + article.title + "\n";
message += "URL:" + article.url + "\n\n";
});
} else {
message += "前回からの更新はありません。\n\n";
message += "サイトURL:" + siteUrl + "\n"; // サイトURLを追加
message += "最新記事\n";
message += "日付:" + formatDate(news[0].date) + "\n";
message += "タイトル:" + news[0].title + "\n";
message += "URL:" + news[0].url + "\n\n";
}
return message;
}
/**
* 日付を月日表示にフォーマットします。
* @param {string} dateString - 日付文字列
* @returns {string} - フォーマットされた日付文字列
*/
function formatDate(dateString) {
var date = new Date(dateString);
var month = date.getMonth() + 1;
var day = date.getDate();
return month + "月" + day + "日";
}
/**
* サイト名からサイトURLを取得します。
* @param {string} siteName - サイト名
* @returns {string} - サイトURL
*/
function getSiteUrl(siteName) {
if (siteName === "イオン株式会社") {
return "https://www.aeon.info/news/";
} else if (siteName === "イオン九州株式会社") {
return "https://www.aeon-kyushu.info/news/";
}
// 他のサイトが追加された場合はここに追加
return "";
}
AeonNews.gs (クリックで表示)
var AeonNews = {
/**
* イオン株式会社のニュースを取得し、記事オブジェクトの配列を返します。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
* @returns {Array<Object>} - ニュース記事の配列
*/
getNews: function (sheet) {
var url = "https://www.aeon.info/news/";
var regex = /<dt class="date"><span class="date-time">([^<]+)<\/span><i class="ico ico-[a-z]+">[^<]+<\/i><\/dt><dd class="txt"><a href="([^"]+)">([^<]+)<\/a><\/dd>/g;
return this.fetchNews(url, regex, function (match) {
return {
date: match[1],
url: match[2],
title: match[3],
};
});
},
/**
* ニュース記事を取得し、記事オブジェクトの配列を返します。
* @param {string} url - ニュースサイトのURL
* @param {RegExp} regex - ニュース記事を抽出する正規表現
* @param {function} createNewsItem - マッチした結果から記事オブジェクトを作成する関数
* @returns {Array<Object>} - ニュース記事の配列
*/
fetchNews: function (url, regex, createNewsItem) {
var response = UrlFetchApp.fetch(url);
var content = response.getContentText("UTF-8");
var news = [];
var match;
var count = 0;
while ((match = regex.exec(content)) && count < 5) {
news.push(createNewsItem(match));
count++;
}
return news;
},
};
AeonKyushuNews.gs (クリックで表示)
var AeonKyushuNews = {
/**
* イオン九州株式会社のニュースを取得し、記事オブジェクトの配列を返します。
* @param {Sheet} sheet - スプレッドシートのシートオブジェクト
* @returns {Array<Object>} - ニュース記事の配列
*/
getNews: function (sheet) {
var url = "https://www.aeon-kyushu.info/news/";
var regex =
/<a href="(\/files\/optionallink\/[^"]+\.pdf)" target="_blank" rel="noopener noreferrer">.*?<div class="date">([^<]+)<\/div>.*?<div class="title">([^<]+)<\/div>/gs;
return this.fetchNews(url, regex, function (match) {
return {
date: match[2],
url: "https://www.aeon-kyushu.info" + match[1],
title: match[3],
};
});
},
/**
* ニュース記事を取得し、記事オブジェクトの配列を返します。
* @param {string} url - ニュースサイトのURL
* @param {RegExp} regex - ニュース記事を抽出する正規表現
* @param {function} createNewsItem - マッチした結果から記事オブジェクトを作成する関数
* @returns {Array<Object>} - ニュース記事の配列
*/
fetchNews: function (url, regex, createNewsItem) {
var response = UrlFetchApp.fetch(url);
var content = response.getContentText("UTF-8");
var news = [];
var match;
var count = 0;
while ((match = regex.exec(content)) && count < 5) {
news.push(createNewsItem(match));
count++;
}
return news;
},
};
(コードを)書いた方が早い?
と思います🚄多分。
が、作った人や動かした人が偉いのである。
うっせぇわ!といって、動くまでやったりましょう💪
コードライティングなんて「手段」の一つなので!
これからも、AE〇Nの主要サイトどんどん登録していきます。
コードは書けなくてもモノづくりするみなさん、応援してます🤗諦めないで!
書かないGASの輪拡大中😏