見出し画像

【GAS】正規表現でURLを抽出する

メール群からURLのみを抽出するにはどうすれば?

LINE BOTを作った際、その準備段階として、Gmailの中から特定のタイトルのメールを抽出し、抽出した各メールに含まれるYou Tubeリンクを抽出するコードを書きました。

当初は、このリンク抽出という処理にParserライブラリを使用していました。具体的には以下のようにfromとtoを利用して、特定のフレーズの後から、次の特定のフレーズの前までの文字列を取得していました。

function getLinksInGmails() {
  const query = 'subject:筋トレ部へのご参加、ありがとうございました'
  const threads = GmailApp.search(query);

  const dateAndLinkArr = []; 
  threads.forEach(thread => {
    const messages = thread.getMessages();
    messages.forEach(message => {
      const recievedDate = message.getDate(); //日付
      const messageBody = message.getPlainBody(); //本文
      const youtubeLink = Parser.data(messageBody)
        .from('トレーニングの参考にしていただければと思います。<br />\r\n')
        .to('<br />')
        .iterate();
      const strYoutubeLink = String(youtubeLink); 

      dateAndLinkArr.push([recievedDate, strYoutubeLink]);
    });

    Sh1.getRange(2, 1, dateAndLinkArr.length, 1).setNumberFormat("yyyy/MM/dd"); // グローバルで指定したSh1の1列目の表示形式を設定
    Sh1.getRange(2, 1, dateAndLinkArr.length, dateAndLinkArr[0].length).setValues(dateAndLinkArr); // グローバルで指定したSh1に貼り付け
  });
}

ですが、上のコードは、gmailに含まれるリンクの前後の「特定のフレーズ」が変更されると用をなさなくなってしまいます。そこで、You Tubeリンク自体をターゲットに指定するため、正規表現を利用することに。

正規表現でURLを抽出する

文字列からのURL抽出に広く使える「秘伝のタレ」はこちら✨

const regex = /https?:\/\/\S*/;

*タブ、改行などを含むホワイトスペースで終わるURLに適用できます

ここでは、なぜこれで文字列からURLが抽出できるのかを、MDNと突き合わせて復習していきます。

まず、抽出したい文字列の前後をスラッシュで囲み、「正規表現ですよ」と合図を送っています。

さらに、抽出したい文字列に元からスラッシュが含まれている場合は、直前にバックスラッシュを入れて1つ1つエスケープします。

つまり、https:// を抽出したければ

/https:\/\//

となります。

さらに、https、http のどちらもあり得る(sがつく場合と付かない場合がある)ので、sを「sが0個か1個ある」という正規表現に変えます。

MDNを見ると以下のように書かれています。

つまり、?はそのすぐ左の項目が0個か1個、を表す、ということなので、httpsのsをs?に書き換えます。

こうして、https:// または http:// を抽出したければ、

/https?:\/\//

と書けばよいことになります。

さらに、https://もしくはhttp://の後に続く文字列を、改行の前まで取得したい、という場合、バックスラッシュでエスケープした大文字のSと*を追加して、

/https?:\/\/\S*/

となります。

エスケープした小文字のsは、
スペース、タブ、改ページ、改行を含むホワイトスペース文字を表し、
エスケープした大文字のSは
上記の否定(つまりホワイトスペース以外の文字)を表します。

そして*はすぐ左の文字列が0個以上、という意味。

ですので、上の正規表現は、https://ホワイトスペース以外の0文字以上、を取ってきてくれるのです(つまりホワイトスペースの前まででカット)。

以上が、文字列の中から、改行などで終わるURLを抽出する一般的な正規表現でした。

さらに、文字列に含まれるすべてのURLを取ってきたい場合は、正規表現の最後のスラッシュの後にgを追加します(グローバルフラグ)。(追加しないと、最初にマッチした1つのみを取ってきます)

/https?:\/\/\S*/g

となりますね。
さらに、今回は複数のURLが含まれる文字列の中から、You Tubeリンクのみを抽出したいので、「youtu」を含めて

/https?:\/\/youtu\S*/g

とします。

一般的には上記の表現で、文字列の中からYou Tubeリンクが取れます。

ただ、GmailからgetPlainBodyで取得した文字列にこれを当てはめると、改行箇所に<br />が入っているため、取得される文字列が

https://youtu.be/hogehoge<br

となってしまいます(<brが入ってしまう)。

そこで、MDNから探し出したのがこちら↓

つまり、[^A]と書くと、A以外、という意味になるようです。

ということは、

https://youtu.be/hogehoge<br>

という文字列から<の前までを取り出したい時は、先程の正規表現の\S*(スペース以外の文字列である限り取り続ける)を[^<]*(<以外の文字列である限り取り続ける)に置き換えればいいはず、、、。

ということで実験してみました。

上の画像で、regex1による出力とregex2による出力を比較すると、[^<]*を正規表現に含めることで、<を含まない文字列(<の前までの文字列)が抽出できていることが分かります。

これで無事、gmailからgetPlainBodyで取得した文字列の中から、You Tubeリンクを抽出して配列にする準備が整いました!

【matchメソッドについての参考URL】https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/match

メールからYou Tubeリンクを抽出するコード

最終的に、Gmailから特定タイトルのメッセージを抽出し、その中から更に、日付とYou Tubリンクを抽出して二次元配列にして返すコードは、以下のようになりました。

function getInfoFromEmails() {
  const titleToSearch = 'subject:筋トレ部へのご参加、ありがとうございました';
  const threads = GmailApp.search(titleToSearch);
  const emails = threads.map(thread => thread.getMessages()).flat();

  const dateAndLinkArr = [];

  emails.forEach(email => {
    const emailDate = email.getDate();
    const emailBody = email.getPlainBody();

    const strToExtract = /https?:\/\/youtu[^<]*/g;
    const youtubeLinkArr = emailBody.match(strToExtract);

    if (!youtubeLinkArr) { throw 'youtubeリンクが含まれていません'; }
    if (youtubeLinkArr.length > 1) { throw 'youtubeリンクが2つ以上あります'; }

    dateAndLinkArr.push([emailDate, youtubeLinkArr[0]]);
  });
  return dateAndLinkArr;
}

※getPlainBodyによって得られた文字列のYou Tubeリンクの後が<br…となっている場合に特化した正規表現です。

※当初は、文字列からURLを抽出をする部分を別関数に切り分けていたのですが、対象をYou Tubeに限定にしたこと、そして、文末が<である場合に限定したことで、汎用性が限定されたため、元の関数の中へ戻しています。

※mapとforEachは、いずれも配列の各要素に対して処理を行いますが、戻り値を配列にして後で利用したい場合はmap、そうでなく単に反復処理したいだけの場合はforEach、と使い分けるそうです。emailsの取得にはmapを使わず、forEachをネストする形も可能です。(今回は、別の用途も考えていたため、1回目の反復処理にはmapを使っています)

この記事が気に入ったらサポートをしてみませんか?