Yahooの新着情報をgoでスクレイピング(定期処理)してjsonをAWS Lambdaで出力する

ECDOの新着情報の自動生成機能を作成中、APIで新着情報を取得する前提でいましたが、YahooショッピングAPIにそんなものは提供されていませんでした😢 (普段は社内の機関システムから新着商品を取得しているし、当然あるものだと思いこんでいた)

プライマリのidとか登録日みたいなデータでソートして最新n件くれるだけでいいのに!と泣きたいところです。うーん、じゃあどうやって新着情報を取得しよう。と色々見ていると、ショップ内検索に新着順というソートを発見。このページをスクレイピングして、扱いやすいjsonにすれば使えそうです。

というわけで、さっそくスクレイピング。ですが、本番サーバーにworkerやジョブ処理環境をまだ作っていないし、ここは気になっていたAWS Lambdaを使って、サクっと定期処理をしてみる事にしてみました。

たまに遊ぶ程度でgoを触っていますが、go楽しいですよね。rubyを触っている時とはまた別の楽しさがあります。

htmlのスクレイピングにはgoqueryを使わせていただきました。とりあえずサクっとスクレイピングするコードがこちら。

package main

import (
  "github.com/PuerkitoBio/goquery"
)

// Item 各商品ブロックのデータ
type Item struct {
  URL   string `json:"url"`
  Image string `json:"image"`
  Title string `json:"title"`
}

// OutputJSON 出力データ
type OutputJSON []Item

func scrape(target string) (OutputJSON, error) {
  var url, image, title string
  var outputs OutputJSON

  html, _ := goquery.NewDocument(target)
  html.Find(".elWrap").Each(func(_ int, block *goquery.Selection) {
    block.Find(".elImage").Find("a").Each(func(_ int, s *goquery.Selection) {
      url, _ = s.Attr("href")
    })
    block.Find(".elImage").Find("img").Each(func(_ int, s *goquery.Selection) {
      image, _ = s.Attr("src")
    })
    block.Find(".elImage").Find("img").Each(func(_ int, s *goquery.Selection) {
      title, _ = s.Attr("alt")
    })
    item := Item{
      URL:   url,
      image: /images/image,
      Title: title,
    }
    outputs = append(outputs, item)
  })

  return outputs, nil
}

func main() {
  url := "https://store.shopping.yahoo.co.jp/YOUR_SHOP_NAME/search.html?p=&ei=UTF-8&x=59&y=8&X=99#CentSrchFilter1"
  fmt.Println(scrape(url))
}

YOUR_ASHOP_NAMEの部分は実際の店舗に合わせてくださいね。

ひとまずこれで走らせてみると、思い通りの結果が出力されました。

これをAWS Lambdaで走らせてみます。そのために一部コードを変更します。

  1. 必要なパッケージ("github.com/aws/aws-lambda-go/lambda")をgo getしてimport
  2. urlを実行時に渡すように変更。そのための構造体を追加
  3. scrape関数の引数を構造体に変更し、内部での呼び出しも変更
package main

import (
  "github.com/PuerkitoBio/goquery"
  "github.com/aws/aws-lambda-go/lambda"
)

// InputJSON 入力データ
type InputJSON struct {
  URL string `json:"url`
}

// Item 各商品ブロックのデータ
type Item struct {
  URL   string `json:"url"`
  Image string `json:"image"`
  Title string `json:"title"`
}

// OutputJSON 出力データ
type OutputJSON []Item

func scrape(target InputJSON) (OutputJSON, error) {
  var url, image, title string
  var outputs OutputJSON

  html, _ := goquery.NewDocument(target.URL)
  html.Find(".elWrap").Each(func(_ int, block *goquery.Selection) {
    block.Find(".elImage").Find("a").Each(func(_ int, s *goquery.Selection) {
      url, _ = s.Attr("href")
    })
    block.Find(".elImage").Find("img").Each(func(_ int, s *goquery.Selection) {
      image, _ = s.Attr("src")
    })
    block.Find(".elImage").Find("img").Each(func(_ int, s *goquery.Selection) {
      title, _ = s.Attr("alt")
    })
    item := Item{
      URL:   url,
      image: /images/image,
      Title: title,
    }
    outputs = append(outputs, item)
  })

  return outputs, nil
}

func main() {
  lambda.Start(scrape)
}

このコードをscrape.goというファイル名で保存します。ハンドラーとして実際に叩く関数名とファイル名をあわせておかないとでエラーになるので注意が必要です。

これをビルドします。

GOOS=linux GOARCH=amd64 go build -o scrape

完成したバイナリをアップロードするためにzipにします。

zip handler.zip ./scrape

完成したら、いよいよAWS Lambdaで動かしてみます。AWS Lambdaの関数を新しく作成をし、アップロードしてハンドラを設定したら保存をします。

そしたら、さっそくテストをしてみます。上部にあるテストボタンをクリックし、

適当なテスト名とjsonを書いて保存をします。

再度上のテストボタンを押してみると、

無事テストが成功しました!うれしい😭

これを定期的に回して、jsonを適当な場所に保管しておけば良さそうです。

しかし、こういう定期処理だったら、AWS Lambdaを使えば超ラクチン。かつgoでバイナリを作るからスピードも速い。本当にすごい時代になりました。