サークルでDiscordを利用しており、Botに記事を1日に1つ配信してもらう仕組みを作っています。ある日突然Botからの通知がなくなったため、どうしたんだろうと思いつつ、試行錯誤した結果、修正できたので備忘録として記します。海外留学や海外旅行に関する記事を配信してくれるBotです。
元のソースコード
function myFunction(){
const cache = CacheService.getScriptCache();
const discordWebHookURL = "";//ここにwebhookURLを貼る
mute_user_list = [""];//ミュートしたいユーザーネームをまとめるリスト
mute_hashtag_list = [""];//ミュートしたいハッシュタグをまとめるリスト
let send_list = new Array;
let bf_send_list = cache.get("send_list")
console.log(bf_send_list)
if(bf_send_list != null && bf_send_list != ""){
bf_send_list = bf_send_list.split(",")
bf_send_list.forEach(item =>{
send_list.push(item)
}
);
}
getNote("https://note.com/hashtag/%E6%B5%B7%E5%A4%96?f=hot&paid_only=false","海外").forEach(item => {
send_list.push(item);
});
let last_list = send_list.filter((item, index) => send_list.indexOf(item) === index);
console.log(send_list)
console.log(last_list)
for (i = 0; i < 1;i++){
if (last_list.length > 0){
send_switch = 0
var article = last_list.pop()
send_url = "https://note.com" + article
const message = {
"content": send_url,
"tts": false
}
const param = {
"method": "POST",
"headers": { 'Content-type': "application/json"},
"payload": JSON.stringify(message)
}
var user_name = article.substring(1,article.indexOf("/",2));
var article_id = article.substring(article.lastIndexOf("/")+1);
mute_user_list.forEach((value) => {
if (user_name == value){
send_switch += 1
}
})
var hashtag_url = `https://note.com/api/v3/notes/${article_id}`;
let jsonArticleInfo = UrlFetchApp.fetch(hashtag_url, {'method':'get'});
var parsedData = JSON.parse(jsonArticleInfo);
var hashtags = parsedData.data.hashtag_notes;
var hashtagNames = hashtags.map(function(item) {
return item.hashtag.name;
}
)
mute_hashtag_list.forEach((value) => {
hashtagNames.forEach((value2) => {
if (value == value2){
send_switch += 1
}
})
})
if (send_switch == 0){
UrlFetchApp.fetch(discordWebHookURL,param);
}
}}
cache.put("send_list",last_list,60 * 60 * 24);
}
function getNote(url,key) {//スクレイピングしてnoteのURLをとってきている
const cache = CacheService.getScriptCache();
let bf_url_list = cache.get(key);
if (bf_url_list != null){
bf_url_list = bf_url_list.split(",");
} else{
bf_url_list = new Array()
}
let url_list = new Array();
let response = UrlFetchApp.fetch(url);
let content = response.getContentText("utf-8");
let texts = Parser.data(content).from('<div class="m-largeNoteWrapper__card" data-v-1e6ad5ad>').to('</div>').iterate();
texts.forEach((value, index) => {
let i = 0
url_temp_l = [];
let url_temp = "";
let html_list = value.split("");
html_list.forEach((value2,index) => {
if (value2 == '"'){
i += 1
}
if (i == 1){
url_temp_l.push(value2)
}
})
for (i = 1; i < url_temp_l.length; i++){
url_temp += url_temp_l[i]
}
url_list.push(url_temp);
});
last_url_list = url_list.filter(item => !bf_url_list.includes(item));
cache.put(key,url_list,60 * 60 * 24);
return(last_url_list);
}
問題と解決方法
結論から言うと問題個所は、
let texts = Parser.data(content).from('<div class="m-largeNoteWrapper__card" data-v-1e6ad5ad>').to('</div>').iterate();
でスクレイピングが失敗していることでした。つまりこのdivタグが存在していないということです。では、この問題の解決プロセスを以下に示します。
まずは問題個所を把握するのが重要です。記事が表示されないということはgetNote()関数内で問題が起きている可能性が高いです。console.log()を利用して"url", "response", "content", "texts"などの表示を確認していきます。
console.log(content)までは良い感じに表示されていたのですが、console.log(texts)が正しく表示されていないようでした。(明らかにcontentの中のdivタグ以外の内容もスクレイピングされている。)
contentに関してですが、console.log(content)だと量が多すぎて表示されないので、分割して表示を確認しましょう。そのためには、以下のようにcontentをoutputという単位に分割して表示させます。
HTMLを確認するには、
const output = content.split('\n');//改行で分割して1行ごとに要素とした配列
for(let i=0;i<output.length;i++){
console.log(output[i]);
}
というコードをcontentのすぐ下に追加します。
そして、表示されたHTMLコードを全てテキストファイルなどにコピペし、ctrl+Fなどで該当するHTMLタグを探しましょう。
noteがアップデートしたのか、HTMLを確認してみると、
<div class="m-largeNoteWrapper__card" data-v-1e6ad5ad>
というタグが存在していませんでした。
似たタグを探していると、今回は、divタグが
<div class="m-largeNoteWrapper__card" data-v-bcbdb926>
に置き換わっていることが判明しました。 noteのアップデートによるものなのでしょうか。これこそスクレイピングができなかった根本の理由ですね。
divタグをこれに書き換えれば、問題なく動きました。
問題解決後のソースコード
function myFunction(){
const cache = CacheService.getScriptCache();
const discordWebHookURL = "";//ここにwebhookURLを貼る
mute_user_list = [""];//ミュートしたいユーザーネームをまとめるリスト
mute_hashtag_list = [""];//ミュートしたいハッシュタグをまとめるリスト
let send_list = new Array;
let bf_send_list = cache.get("send_list")
console.log(bf_send_list)
if(bf_send_list != null && bf_send_list != ""){
bf_send_list = bf_send_list.split(",")
bf_send_list.forEach(item =>{
send_list.push(item)
}
);
}
getNote("https://note.com/hashtag/海外?f=hot&paid_only=false","海外").forEach(item => {
send_list.push(item);
});
let last_list = send_list.filter((item, index) => send_list.indexOf(item) === index);
console.log(send_list)
console.log(last_list)
for (i = 0; i < 1;i++){
if (last_list.length > 0){
send_switch = 0
var article = last_list.pop()
send_url = "https://note.com" + article
const message = {
"content": send_url,
"tts": false
}
const param = {
"method": "POST",
"headers": { 'Content-type': "application/json"},
"payload": JSON.stringify(message)
}
var user_name = article.substring(1,article.indexOf("/",2));
var article_id = article.substring(article.lastIndexOf("/")+1);
mute_user_list.forEach((value) => {
if (user_name == value){
send_switch += 1
}
})
var hashtag_url = `https://note.com/api/v3/notes/${article_id}`;
let jsonArticleInfo = UrlFetchApp.fetch(hashtag_url, {'method':'get'});
var parsedData = JSON.parse(jsonArticleInfo);
var hashtags = parsedData.data.hashtag_notes;
var hashtagNames = hashtags.map(function(item) {
return item.hashtag.name;
}
)
mute_hashtag_list.forEach((value) => {
hashtagNames.forEach((value2) => {
if (value == value2){
send_switch += 1
}
})
})
if (send_switch == 0){
UrlFetchApp.fetch(discordWebHookURL,param);
}
}}
cache.put("send_list",last_list,60 * 60 * 24);
}
function getNote(url,key) {//スクレイピングしてnoteのURLをとってきている
const cache = CacheService.getScriptCache();
let bf_url_list = cache.get(key);
if (bf_url_list != null){
bf_url_list = bf_url_list.split(",");
} else{
bf_url_list = new Array()
}
let url_list = new Array();
let response = UrlFetchApp.fetch(url);
let content = response.getContentText("utf-8");
// const output = content.split('\n');//改行で分割して1行ごとに要素とした配列
// for(let i=0;i<output.length;i++){
// console.log(output[i]);
// }
// スクレイピングができなかった理由はここ
let texts = Parser.data(content).from('<div class="m-largeNoteWrapper__card" data-v-bcbdb926>').to('</div>').iterate();
console.log(texts)
texts.forEach((value, index) => {
let i = 0
url_temp_l = [];
let url_temp = "";
let html_list = value.split("");
html_list.forEach((value2,index) => {
if (value2 == '"'){
i += 1
}
if (i == 1){
url_temp_l.push(value2)
}
})
for (i = 1; i < url_temp_l.length; i++){
url_temp += url_temp_l[i]
}
url_list.push(url_temp);
});
last_url_list = url_list.filter(item => !bf_url_list.includes(item));
cache.put(key,url_list,60 * 60 * 24);
return(last_url_list);
}
一言
スクレイピングの仕組みの理解が深まったので良かった。
参考文献
【誰でも導入可】ShadowverseのnoteをDiscordに投げてくれるbotを作ってみた。※追記,機能追加済
GASを使ったWebスクレイピング
logging output too large. truncating output.の対策