16
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LINEBotを作った背景

初めまして!

セブンイレブン大好きな若手エンジニアです!
私の家の近くにはセブンイレブンがあり、毎日散歩をするついでにセブイレに寄って、
店内をウロウロして、気になる新作などがあれば買ってしまいます。
(スイーツやアイスなどがメイン)

皆さんご存知の通り、セブンイレブンは毎週火曜日に、ホームページの新作の情報が更新され、結構な頻度で新作が出るので飽きたりしづらく、僕は大好きです。

ただ毎回火曜日にホームページをわざわ見に行くのも面倒だし、案外忘れてしまったりしてしまいます。。

そこで散歩しながらふと思いつきました。

エンジニアらしく毎週火曜日に、セブイレの新作の情報をスクレイピングでとってきて、
LINEで知らせてくれるBotを作ればいいのでは???

早速作ってみることにしました。

やったこと

やったこととしては以下の3つです。

  1. LINEbotを作成するために,LINEdevelopersに登録し、botを作成
  2. botでメッセージを送信するために、GASでプログラムを作成!
  3. 作成したプログラムを定期実行させる設定

1.LINEbotを作成するために,LINEdevelopersに登録し、botを作成

こちら多くの記事で既に紹介されているため、詳細は割愛します!
以下にLINEdevelopersの公式ページに開始するための流れが記載されていますので、そちらをご覧ください。
MessageApiを作成しよう
ボットを作成する
※webhookURLの設定に関しては次で説明します。

2.botでメッセージを送信するために、GASでプログラムを作成

botでメッセージを送信するために、GAS(GoogleAppScriptの略称)でプログラムを作っていきます。
GASであれば、サーバーの準備などは不要ということでとにかく楽ちんです。

先ほどのLINEBotの設定の中に、webhookURLを設定する箇所があります。
これはbotからメッセージを受け取ったりする際に動くプログラムを書く場合に必要ですが、最終的には必要がありません。
ただ、複数人が入るグループにメッセージを送りたい場合などは、グループIDなどが必要になってきますので、設定する必要があります。
=>グループ作成
=>LINEdevelopersで作成したbotをグループに追加
=>自分がグループに対して、何かつぶやく
=>botが受け取ったメッセージに対して、webhookURLで設定したURLにリクエストが飛ぶ
=>あとで必要なグループIDを返すようにGASでプログラムを組んでおく
=>botがグループIDを返してくれる
といった作業が必要なため、初めはwebhookURLが必要になるケースがあります。
上記の手順はこちらの記事を参考にしています。

GASを開くと「デプロイ」をクリックし、「新しいデプロイ」をクリックします。
スクリーンショット 2021-11-29 21.49.05.png

ここでアクセスできるユーザーは全員に設定しておきましょう。
スクリーンショット 2021-12-02 20.18.31.png

そうすると、ここでURLが出てくるので、こちらをLINEbotの作成したWebhookURLに設定しましょう。
都度必要に応じて、新しくデプロイをすれば良いかと思います。
スクリーンショット 2021-12-02 20.21.40.png

そんなこんなで大体の下準備が終了すると、コードを実際に見ていきましょう。

コード

const LINE_URL = 'https://api.line.me/v2/bot/message/push';
const CHANNEL_TOKEN = 'xxxxxxx'; //LINEdevelopersページの自分で作成したBotの設定ページにあるチャネルアクセストークン
  
const TO = "xxxxxxxxx" //LINEのgroupId
const KINKI_NEW_ITEM_URL = 'https://www.sej.co.jp/products/a/thisweek/area/kinki/1/l100/' //近畿住みなのでkinkiのみにしています

function main() {
    let attachments = []
    let columns = []
    let html = UrlFetchApp.fetch(KINKI_NEW_ITEM_URL).getContentText()
   
   const items = Parser.data(html).from('"list_inner').to('list_inner').iterate()
    for(i=0;i<items.length;++i){  
      let link   = 'https://www.sej.co.jp' + items[i].match(/<a href="(.+)\/">/)[1]
      let image  = items[i].match(/data-original="([^"]+)" alt=""/)[1]
      let name   = items[i].match(/<div class="item_ttl">.+">(.+?)<\/a>/)[1]
      let price  = '価格:' + items[i].match(/<div class="item_price"><p>(.+?)<\/p>/)[1]
      let launch = '発売日:' + items[i].match(/<div class="item_launch"><p>(.+?)<\/p>/)[1]

      columns.push(makeColumn(link, image, name, price, launch))
      if(columns.length % 10 == 0 || i == items.length -1 ){
        attachments.push(makeAttachment(columns))
        columns = []
      }
    }
  sendLine(attachments)
}

function makeColumn(link, image, name, price, launch) {
  return {
    thumbnailImageUrl: image,
    title: name,
    text: `${price}\n${launch}`,
    defaultAction: {
        type: "uri",
        label: "商品の詳細を見る",
        uri: link
    },
    actions: [{
      type: "uri",
      label: "商品の詳細を見る",
      uri: link
    }]
  }   
}

function makeAttachment(columns) {
  return {
    type: "template",
    altText: "今週の新商品です",
    template: {
      type: 'carousel',
      columns: columns,
      imageAspectRatio: "square",
      imageSize: "cover"
    }
  }
}

function sendLine(attachments) {
  messages = [
    {
      type: 'text',
      text: '今週の新商品です'
    } 
  ]
  for(i=0;i<attachments.length;++i){  
  
    messages.push(attachments[i])

    if(messages.length % 5 == 0 || i == attachments.length -1){
      payload = JSON.stringify({
        to : TO,
        messages: messages,
      });

      options = {
        'headers': {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + CHANNEL_TOKEN,
        },
        "method": "post",
        "payload": payload,
      };
     
        UrlFetchApp.fetch(LINE_URL, options);
        
      
      messages = []
    } 
  } 
}


コードの説明

LINEでは送れるメッセージのタイプがいくつかあり、カルーセルテンプレートタイプを使いました。こちら
上記のリンクの中身を見ながら、最終的にどんな形式のリクエストを送れば良いのか、いくつまでデータは送信できるのか、といった情報を確認しながら作りました。

用意されているものに当てはめていくだけなので、結構簡単ですね!!

手順的には
1.セブイレの近畿ページの新作100件表示したもの1ページ目を表示(ぎりぎり100件未満のケースが多いのでこのページのみにしている)
今回はライブラリのParserを使用しています。
ライブラリの設定方法はこちら

let html = UrlFetchApp.fetch(KINKI_NEW_ITEM_URL).getContentText()
const items = Parser.data(html).from('"list_inner').to('list_inner').iterate()

2.そこから各商品の情報だけいい感じに抜き出す(画像、詳細のリンク、商品名、発売時期、価格)
正規表現でいい感じに情報を抜き出します。

let link   = 'https://www.sej.co.jp' + items[i].match(/<a href="(.+)\/">/)[1]
let image  = items[i].match(/data-original="([^"]+)" alt=""/)[1]
let name   = items[i].match(/<div class="item_ttl">.+">(.+?)<\/a>/)[1]
let price  = '価格:' + items[i].match(/<div class="item_price"><p>(.+?)<\/p>/)[1]
let launch = '発売日:' + items[i].match(/<div class="item_launch"><p>(.+?)<\/p>/)[1]

3.それらのデータを整形して、データを10個ずつまとめる(makeColumnという関数の部分)
LINEで表示するデータひとつひとつの内容ですね。

function makeColumn(link, image, name, price, launch) {
  return {
    thumbnailImageUrl: image,
    title: name,
    text: `${price}\n${launch}`,
    defaultAction: {
        type: "uri",
        label: "商品の詳細を見る",
        uri: link
    },
    actions: [{
      type: "uri",
      label: "商品の詳細を見る",
      uri: link
    }]
  }   
}

4.10個溜まったら一つのメッセージを作る(makeAttachmentという関数の部分)
さきほどのmakeColumnで作ったデータをまとめてどのような形式で送るかの内容ですね。
テンプレートの種類や画像の比率など。

function makeAttachment(columns) {
  return {
    type: "template",
    altText: "今週の新商品です",
    template: {
      type: 'carousel',
      columns: columns,
      imageAspectRatio: "square",
      imageSize: "cover"
    }
  }
}

5.2〜4を繰り返してメッセージが5個溜まると、LINEに送信する(sendLineの部分)
先ほどのメッセージ部分を5個作成すると送信するようにしています。

function sendLine(attachments) {
  messages = [
    {
      type: 'text',
      text: '今週の新商品です'
    } 
  ]
  for(i=0;i<attachments.length;++i){  

    messages.push(attachments[i])

    if(messages.length % 5 == 0 || i == attachments.length -1){
      payload = JSON.stringify({
        to : TO,
        messages: messages,
      });

      options = {
        'headers': {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + CHANNEL_TOKEN,
        },
        "method": "post",
        "payload": payload,
      };

        UrlFetchApp.fetch(LINE_URL, options);


      messages = []
    } 
  } 
}

3.作成したプログラムを定期実行させる設定

GASは設定すれば、定期実行が可能です。
GASのコードの左側のメニューの「トリガ」ーを選択し、
実行する関数の名前や、どんな時に関数を実行するのか選択できますので、毎週火曜日を選択して時間を設定するだけですね!
スクリーンショット 2021-12-04 8.58.04.png

実際に送られてくるLINE

trim.AFAE1D16-8B33-42B0-B194-47EBED30A329.gif

いい感じにできました!!
どんな商品なのか、値段はいくらなのか、いつぐらいから発売なのかが一目でわかります!

セブンイレブン大好きな人は是非ともこれGASで作って毎週火曜日に実行すれば、セブイレの新作を知れます!

セブイレ好きなので、作っていて案外楽しかったです!!

参考文献

GASでセブンイレブンの今週の新商品をスクレイピングして通知する

16
5
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
16
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?