セブンだけでなくファミマの今週の新商品もスクレイピングしてみた

はじめに

以下の記事を読んで面白そうだと思い、実際に組んでみました。
GASでセブンイレブンの今週の新商品をスクレイピングして通知する

GASは初めてだったので戸惑いましたが、何とか組むことができました。
生意気にも品数を出したりとちょっとしたカスタマイズまでしています。
スクリーンショット 2018-05-17 1.25.42.png

ここで私は思いました。
これ他のコンビニでもできるんじゃね?

そう思ってファミリーマートのサイトを見てみました。
http://www.family.co.jp/goods/newgoods.html

スクリーンショット 2018-05-17 22.21.12.png

セブンとほぼ同じようなサイトです。
これはいける!ファミマだってセブンに負けちゃられねぇぜ!

ということでファミマ版も組んでみましたので紹介します。

リファクタリング

まずはセブンのソースを使い回せるようにリファクタリングします。
Slackの通知部分は共通化できそうなので、セブン固有の処理と分離します。

セブン固有の処理

// ----------------------------------------------------------------------------
// Post Seven Products
//
// セブンイレブンの新商品をSlackに通知する
// 参考:https://qiita.com/khsk/items/a3d942a2d091abed7c4e
//   :http://www.sej.co.jp/i/products/thisweek/?page=1&sort=f&limit=100
// ----------------------------------------------------------------------------

// 定数
var MY_SEVEN_REGION = '関東'
var SEVEN_INCOMING_URL = 'https://hooks.slack.com/services/…'
var SEVEN_TERMS = {
  '今週': 'thisweek',
  '来週': 'nextweek'
}
var SEVEN_REGIONS = {
  '北海道': 'hokkaido',
  '東北': 'tohoku',
  '関東': 'kanto',
  '甲信越・北陸': 'koshinetsu',
  '東海': 'tokai',
  '近畿': 'kinki',
  '中国・四国': 'chugoku',
  '九州': 'kyushu'
}
var SEVEN_SORTS = {
  'おすすめ': 'f',
  '新商品': 'n'
}
var SEVEN_LIMITS = {
  '15件': 15,
  '50件': 50,
  '100件': 100
}
var SEVEN_COLORS = [
  '#f58220', // セブンオレンジ
  '#00a54f', // セブングリーン
  '#ee1c23' // セブンレッド
]
var SEVEN_BASE_URL = 'http://www.sej.co.jp'
var SEVEN_PRODUCTS_DIR = '/i/products/'

// メソッド
function postSevenThisWeekNewProducts() {
  postSevenProducts('今週')
}

function postSevenNextWeekNewProducts() {
  postSevenProducts('来週')
}

function postSevenProducts(term) {
  var query = sevenQuery(SEVEN_SORTS['おすすめ'], SEVEN_LIMITS['100件'])
  var url = sevenURL(SEVEN_TERMS[term], SEVEN_REGIONS[MY_SEVEN_REGION], query)
  text = term + 'の新商品です。(' + MY_SEVEN_REGION + ')'
  postSevenSlack(text, makeSevenAttachments(url))
}

function sevenQuery(sort, limit) {
  return '?page=1' + '&sort=' + sort + '&limit=' + limit
}

function sevenURL(term, region, query) {
  return SEVEN_BASE_URL + SEVEN_PRODUCTS_DIR + term + '/' + region + query
}

function postSevenSlack(text, attachments) {
  text += '\n全' + attachments.length + '品'
  postSlack(SEVEN_INCOMING_URL, '711', 'セブン新商品通知bot', ':711:', text, attachments)
}

function makeSevenAttachments(url) {
  var attachments = []
  var html = UrlFetchApp.fetch(url).getContentText()
  var items = Parser.data(html).from('<li class="item">').to('</div>\n</li>').iterate()
  for (var i = 0; i < items.length; i++) {
    var link = SEVEN_BASE_URL + items[i].match(/<a href="(.+)">/)[1]
    var image = items[i].match(/src="([^"]+)" alt="商品画像"/)[1]
    var name = items[i].match(/<div class="itemName">.+">(.+?)<\/a><\/strong>/)[1]
    var price = items[i].match(/<li class="price">(.+?)<\/li>/)[1]
    var launch = items[i].match(/<li class="launch">(.+?)<\/li>/)[1]
    var region = items[i].match(/<li class="region">(.+?)<\/li>/)[1].replace('<em>販売地域</em>', '')
    attachments.push(makeSevenAttachment(link, image, name, price, launch, region, i))
  }
  return attachments
}

function makeSevenAttachment(link, image, name, price, launch, region, i) {
  return makeAttachment(SEVEN_COLORS[i % SEVEN_COLORS.length],
                        'No.' + (i + 1),
                        name,
                        link,
                        image,
                        makeSevenFields(price, launch, region))
}

function makeSevenFields(price, launch, region) {
  return [makeField('値段', price, true),
          makeField('販売時期', launch, true),
          makeField('販売地域', region, true)]
}

postSevenThisWeekNewProducts() メソッドを実行すると今週の新商品がSlackに通知されます。

postSevenNextWeekNewProducts() メソッドだと来週の新商品が通知されます。

Slack通知処理

// ----------------------------------------------------------------------------
// Post Slack
// ----------------------------------------------------------------------------

function makeAttachment(color, pretext, title, titleLink, thumbURL, fields) {
  return {
    color: color,
    pretext: pretext,
    title: title,
    title_link: titleLink,
//    image_url: imageURL,
    thumb_url: thumbURL,
    fields: fields
  }
}

function makeField(title, value, short) {
  return {
    title: title,
    value: value,
    short: short,
  }
}

function postSlack(incomingURL, channel, username, iconEmoji, text, attachments) {
  var jsonData = {
    channel: channel,
    username: username,
    icon_emoji: iconEmoji,
    text: text,
    attachments: attachments,
  }
  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : JSON.stringify(jsonData)
  }
  UrlFetchApp.fetch(incomingURL, options)
}

実装

ここまでリファクタリングできたら、セブン固有の処理をコピーしてファミマ版とするだけです。
URLの構成や表示項目が異なるので、そこに気をつけて実装していきます。

本当はクラス化して継承したかったのですが、生のJSではできないようなので断念しました。

ファミマ固有の処理

// ----------------------------------------------------------------------------
// Post Famima Products
//
// ファミリーマートの新商品をSlackに通知する
// 参考:http://www.family.co.jp/goods/newgoods.html
// ----------------------------------------------------------------------------

var FAMIMA_INCOMING_URL = 'https://hooks.slack.com/services/…'
var FAMIMA_TERMS = {
  '先週': 'lastweek',
  '今週': 'newgoods',
  '来週': 'nextweek'
}
//var CATEGORIES = {
//}
var FAMIMA_COLORS = [
  '#36aa57', // ファミマグリーン
//  '#ffffff', // ファミマホワイト // 白だと線が見えないので使わない
  '#009ec6' // ファミマブルー
]
var FAMIMA_BASE_URL = 'http://www.family.co.jp'
var FAMIMA_GOODS_DIR = '/goods/'
var FAMIMA_NEW_GOODS_DIR = 'newgoods/'

// メソッド
function postFamimaThisWeekNewProducts() {
  postFamimaProducts('今週')
}

function postFamimaNextWeekNewProducts() {
  postFamimaProducts('来週')
}

function postFamimaLastWeekNewProducts() {
  postFamimaProducts('先週')
}

function postFamimaProducts(term) {
  var url = famimaURL(term)
  text = term + 'の新商品です。'
  postFamimaSlack(text, makeFamimaAttachments(url))
}

function famimaURL(term) {
  if (term == '今週') {
    return FAMIMA_BASE_URL + FAMIMA_GOODS_DIR + FAMIMA_TERMS[term] + '.html'
  }
  if (term == '来週'|| term == '先週') {
    return FAMIMA_BASE_URL + FAMIMA_GOODS_DIR + FAMIMA_NEW_GOODS_DIR + FAMIMA_TERMS[term] + '.html'
  }
}

function postFamimaSlack(text, attachments) {
  text += '\n全' + attachments.length + '品'
  postSlack(FAMIMA_INCOMING_URL, 'famima', 'ファミマ新商品通知bot', ':famima:', text, attachments)
}

function makeFamimaAttachments(url) {
  var attachments = []
  var html = UrlFetchApp.fetch(url).getContentText()
  var items = Parser.data(html).from('<div class="ly-mod-infoset4 js-imgbox-size-rel ">').to('</p> </a>').iterate()
  for (var i = 0; i < items.length; i++) {
    var link = FAMIMA_BASE_URL + items[i].match(/<a href="(.+?)" class="ly-mod-infoset4-link">/)[1]
    var image = FAMIMA_BASE_URL + items[i].match(/src="([^"]+)" alt="/)[1]
    var name = items[i].match(/<h3 class="ly-mod-infoset4-ttl">\n\s*(.+?)\n*\s*<\/h3>/)[1]
    var category = items[i].match(/<p class="ly-mod-infoset4-cate">\n\s*(.+?)<\/p>/)[1]
    var price = items[i].match(/<p class="ly-mod-infoset4-txt">\n\s*(.+?<br>\n\t*<span>.+?)<\/span>/)[1].replace(/<br>\n\t*<span>/, '')
    attachments.push(makeFamimaAttachment(link, image, name, category, price, i))
  }
  return attachments
}

function makeFamimaAttachment(link, image, name, category, price, i) {
  return makeAttachment(FAMIMA_COLORS[i % FAMIMA_COLORS.length],
                        'No.' + (i + 1),
                        name,
                        link,
                        image,
                        makeFamimaFields(category, price))
}

function makeFamimaFields(category, price) {    
  return [makeField('種類', category, true), makeField('値段', price, true)]
}

完成!

postFamimaThisWeekNewProducts() メソッドを実行します。

スクリーンショット 2018-05-17 1.26.10.png

ファミマの新商品もスクレイピングしてSlackに通知することができました。

ちなみに postFamimaNextWeekNewProducts() メソッドを実行すると来週の新商品、 postFamimaLastWeekNewProducts() メソッドを実行すると先週の新商品が通知されます。

トリガーの設定

スクリプトが完成したら、あとは定期的に実行させるよう指定するだけです。

各ページを手動でリロードして調べたところ、更新される時間は以下のようでした。

ページ 更新日時
今週の新商品 毎週火曜日0時頃
来週の新商品 毎週金曜日15時頃

これを踏まえ、私は以下のようにトリガーを設定しています。

スクリーンショット_2018-05-22_19_56_32.jpg

これで今週の新商品は毎週火曜日の6〜7時の間、来週の新商品は毎週金曜日の18〜19時の間に通知されます。
起床時間と就業時間に合わせたことにより、更新されてからのタイムラグが発生しているので、時間帯はお好みで変更してください。

トリガーは今週 or 来週のどちらか片方でもよさそうです。

おわりに

GASもJSも正規表現も全くの素人だったのでめちゃくちゃ苦戦しました。

特に正規表現で名前を取ってくるところがやっかいでした。
今週(2018/05/15〜05/21)だと「ふんわりしっとりチーズスフレ(デンマーク産クリームチーズ使用)」だけなぜか名前と </h3> タグの間に改行と半角スペースが入っていたので、それを考慮するために以下のコードとなっています。

- var name = items[i].match(/<h3 class="ly-mod-infoset4-ttl">\n\s*(.+?)<\/h3>/)[1]
+ var name = items[i].match(/<h3 class="ly-mod-infoset4-ttl">\n\s*(.+?)\n*\s*<\/h3>/)[1]

スクリーンショット_2018-05-17_10_15_54.jpg

スクリーンショット 2018-05-17 1.57.41.png

ふんわりしっとりチーズスフレめ!
ちょっと高いけどうまそうだな:custard:

今度買ってみようかな…笑

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.