0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google Apps Scriptでnote記事をスクレイピングできなくなった場合の対処法

Last updated at Posted at 2024-09-23

サークルで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.の対策

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?