Translate

Post Date:2021年11月9日 

Google Apps Script でWebスクレイピング

ゾウでもわかる Google Apps Script

GAS(Google Apps Script)でWebサイトをスクレイピングする方法の説明です。

matchメソッドで正規表現を駆使してもスクレイピングをできなくはないですが、Parserライブラリを使った方が100倍簡単です。

GASでスクレイピングすれば、毎日何時にとか何時間毎にといったようにWebサイトから定期的に情報収集することができます。


GASライブラリとは

簡単にいってしまえば、他の人が作った関数を利用できる機能がライブラリです。面倒臭い作業も賢い人が作ったライブラリを使えば自分でプログラミングする必要はなくなります。


Parserライブラリを導入する

Parserは、スクレイピングをするときにとても便利なライブラリで、下記で公開されています。

https://script.google.com/home/projects/1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw/edit

ライブラリを使用するときには、スクリプトIDが必要となりますので、

1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw

の部分をコピーしてください。

Parserライブラリ

ライブラリの追加方法

スクリプトエディタを開いてライブラリの「+」をクリックします。

スクリプトエディタでライブラリを追加する

ライブラリ追加画面となりますので、スクリプトIDをペーストして、検索をクリックして追加すればParserライブラリが利用可能となります。

  1. ParserのスクリプトIDを入力
  2. 検索をクリック
  3. 追加をクリック
スクリプトIDで検索してParserライブラリを追加する

HTMLデータを取得する

毎日データが変わる 12星座運勢ランキング - Yahoo!占い を使って説明していきます。

先ずは、HTMLデータを取り込みます。

HTMLを取り込むときの注意点は文字コードとして何が使われているかです。文字コードを指定しないと文字化けしてしまいます。

Yahoo!占いで使われている文字コードは "euc-jp” です。<head>から</head>に charsetの記載がありますので探してみてください。

<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">

ちなみに、このブログである「象と散歩」の文字コードは "UTF-8” です。

<meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>

GASでHTMLデータを取得するには UrlFetchAppクラスを使いますが、文字コードはgetContentTextで指定します。

function myFunction() {
  var url = "https://fortune.yahoo.co.jp/12astro/ranking.html" // Yahoo!占い
  var html = UrlFetchApp.fetch(url).getContentText('euc-jp')
  Logger.log(html)
}

これでhtmlに格納されたHTMLデータが出力されます。


Parserライブラリの使い方

Parserの書き方は簡単で、抽出したいデータの中にある開始文字列終了文字列に挟まれている文字列を抽出できます。

Parser.data(‘抽出データ').from(‘開始文字列').to(‘終了文字列').build()

開始と終了に指定する文字列はhtmlタグである必要はありません。

また最後のbuild()関数は、最初に見つけたひとつだけを抽出する場合で、複数のデータを抽出するのであれば、iterate()を使います。

Yahoo!占いのタイトルと更新日を抽出する

では、早速 Yahoo!占い 12星座運勢ランキングからタイトルと更新日を取得してみます。「12星座運勢ランキング」というのは他のところからも取得できますが、画像で指定されているところから抽出してみます。

Yahoo!12星座運勢ランキング

HTMLソースでは下記の部分となります。

<h2><img src="https://s.yimg.jp/images/fortune/images/common/yftn_tt01_txt08.gif" alt="12星座運勢ランキング"></h2>
<p class="txt">2021年11月6日(土)</p>

「12星座運勢ランキング」を取得するために、直前の文字列 alt=" と直後の文字列 "> を指定します。

更新日には、開始と終了に <p class="txt"> と </p>を指定します。

function myFunction() {
  var url = "https://fortune.yahoo.co.jp/12astro/ranking.html" // Yahoo!占い
  var html = UrlFetchApp.fetch(url).getContentText('euc-jp')
  var title = Parser.data(html).from('alt="').to('">').build()
  var lastUpdated = Parser.data(html).from('<p class="txt">').to('</p>').build()
  Logger.log('タイトル= ' + title)
  Logger.log('更新日 = ' + lastUpdated)
}

実行結果をみるとタイトルが正しく抽出されていません。

タイトル= Yahoo!占い" width="177" height="34
更新日 = 2021年11月6日(土)

前述したように build() は、最初に見つけたものとなるので、

<img src="https://s.yimg.jp/c/logo/f/2.0/fortune_r_34_2x.png" alt="Yahoo!占い" width="177" height="34">

が、先にマッチしてこの部分が抽出されたようです。

もう少し、開始文字列を多くして、他で一致しないようにします。

function myFunction() {
  var url = "https://fortune.yahoo.co.jp/12astro/ranking.html" // Yahoo!占い
  var html = UrlFetchApp.fetch(url).getContentText('euc-jp')
  var title = Parser.data(html).from('txt08.gif" alt="').to('">').build()
  var lastUpdated = Parser.data(html).from('<p class="txt">').to('</p>').build()
  Logger.log('タイトル= ' + title)
  Logger.log('更新日 = ' + lastUpdated)
}

今度は正しく取得できています。

タイトル= 12星座運勢ランキング
更新日 = 2021年11月6日(土)

複数の値を取得する

Yahoo!12星座運勢ランキングのソースをみると、1位から12位まで表形式(tableタグ)で組み立てられています。

1行毎に薄い色と濃い色とで変えているので、簡単に記載するとこんな形です。

<table>
  ランキング1位の項目
  <tr class="st01">
    <td>...</td>
  </tr>

  ランキング2位の項目
  <tr class="st02">
    <td>...</td>
  </tr>

  ランキング3位の項目
  <tr class="st01">
    <td>...</td>
  </tr>

  :

</table>

<tr class="st01">で始まっているのが奇数の順位のもので、<tr class="st02">が偶数順位ですが、iterate()を使うと一気に配列として取得できます。

list_odd[5]でランク11位、list_even[5]でランク12位の情報が取得できます。

function myFunction() {
  var url = "https://fortune.yahoo.co.jp/12astro/ranking.html" // Yahoo!占い
  var html = UrlFetchApp.fetch(url).getContentText('euc-jp')
  //奇数順位の星座
  var list_odd = Parser.data(html).from('<tr class="st01">').to('</tr>').iterate()
  //偶数順位の星座
  var list_even = Parser.data(html).from('<tr class="st02">').to('</tr>').iterate()
  Logger.log(list_odd[5])
  Logger.log(list_even[5])
}

Parserを複数回利用して値を取得する

Parserを繰り返し使用することで目的の値を簡単に取得することができます。

上で取得した各星座のデータからランク、星座名、コメントを取得します。

スクレイピングのプログラミングのコツは、共通点を見つけるということです。共通のロジックでデータが取得できるかという観点でHTMLデータを俯瞰的に見てください。


順位の取得

詳細項目の取得方法について順位を使って説明します。

<td class="st01"> ... </td> の中にランキング情報があり、alt=" ... "> 中に順位がありますので、Paserを2回使って順位を取得します。以下がGASのコードとなります。

function myFunction() {
  var url = "https://fortune.yahoo.co.jp/12astro/ranking.html" // Yahoo!占い
  var html = UrlFetchApp.fetch(url).getContentText('euc-jp')
  //奇数順位の星座
  var list_odd = Parser.data(html).from('<tr class="st01">').to('</tr>').iterate()
  //ランクを取得
  var rank = Parser.data(contents).from('<td class="st01">').to('</td>').build()
  rank = Parser.data(rank).from('alt="').to('">').build()
  Logger.log(rank)
}

上記で「1位」と表示されます。


順位、星座名、コメントを取得する

下記が、Yahoo! 12星座運勢ランキングから、順位、星座名、コメントを取得するGASのコードとなりますので参考にしてみてください。

function myFunction() {
  var url = "https://fortune.yahoo.co.jp/12astro/ranking.html" // Yahoo!占い
  var html = UrlFetchApp.fetch(url).getContentText('euc-jp')
  //奇数順位の星座
  var list_odd = Parser.data(html).from('<tr class="st01">').to('</tr>').iterate()
  //偶数順位の星座
  var list_even = Parser.data(html).from('<tr class="st02">').to('</tr>').iterate()
  for(var i=0; i<list_odd.length; i++) {
    //奇数順位の星座
    results_odd = parse(list_odd[i])
    Logger.log(results_odd)
    //偶数順位の星座
    results_even = parse(list_even[i])
    Logger.log(results_even)
  }
}
function parse(contents) {
  //ランクを取得
  var rank = Parser.data(contents).from('<td class="st01">').to('</td>').build()
  rank = Parser.data(rank).from('alt="').to('">').build()
  //星座名を取得
  var seiza = Parser.data(contents).from('<p class="seiza">').to('</p>').build()
  seiza = Parser.data(seiza).from('alt="').to('">').build()
  //コメントを取得
  var text = Parser.data(contents).from('<p class="ft01">').to('</p>').build()
  text = Parser.data(text).from('">').to('</a>').build();
  var results =[rank, seiza, text]
  return results
}

GASの基礎を学べる参考図書

ある程度プログラミンがわかっていれば、WEBやYoutubeでも十分に調べられると思いますが、初歩的なところからであれば参考図書は有効な学習手段です。

詳解! Google Apps Script完全入門 [第3版]」は、プラグラミング初心者にわかりやすく説明されています。GASを最初に学ぶ一冊として良書です。

Udemy オススメ講座

【新IDE対応】Google Apps Script(GAS)の基礎を完全習得

【新IDE対応】Google Apps Script(GAS)の基礎を完全習得

講師:事務職たらこ

印象に残りやすい手書き風スライドを用いGASの基本的なプログラミングを気軽に学ぶことができる。本講座でGASを活用した自動化ができるレベルにはなれないが、基礎としては十分。

Post Date:2021年10月31日 

発酵あんこを発酵機でつくる

豆乳ヨーグルトと発酵あんこ

甘味のバリエーションを増やそうと、巷で流行っている『発酵あんこ』をヨーグルトメーカーで作ってみました。食べる甘酒は玄米と米麹で作りますが、発酵あんこも小豆と米麹と、シンプルな材料で作れます。

『発酵あんこ』は、通常の『あんこ』と違って砂糖(=ショ糖)を使っていません。米麴が発酵する過程で、デンプンがブドウ糖になり甘くなるので、身体に優しい甘さです。


あずきは糖質を代謝する

あずきは、低脂質、高タンパクで、食物繊維を多く含む健康食品です(豆の栄養成分表 | 公益財団法人 日本豆類協会 参照)。

また、あずきに含まれるビタミンB1は、糖質の代謝に欠かせない栄養素です。発酵あんこにすることで、糖質が分解されると聞くと甘味に対する欲求への罪悪感が薄れます。

その他あずきには、

  • 分泌解消
  • アンチエイジング
  • 貧血予防
  • コレステロール低下
  • 冷え性改善
  • 高血圧予防

などの効果があるそうです。詳しくは、あずきのチカラで健康に | 井村屋株式会社 を参照してください。


あずきを茹でるのは面倒

あずきを茹でるのには、最初にアク抜きのための茹でこぼし、そのあとに柔らかくなるまでコトコトと煮込まなければなりません。

土鍋を使えば余熱で調理できるので火にかけている時間を短縮できます。あずきを茹でるのに使っている土鍋は長谷園「かまどさん」です。

"かまどさん” の内蓋は使いません。指で豆が潰れるようであればOKです。小豆の大きさによっても茹で時間が異なるので、まだ硬いようであれば水加減をみて更に火にかけてください。

あずきの茹で方
 ① あずき 200g、水 600ml(あずきの3倍)
 ② 強火で沸騰させ煮汁を捨てる(茹でこぼし)
 ③ 水 600ml を加えて 強火で沸騰させる
 ④ 沸騰したら弱火で20分
 ⑤ 火を止めて余熱で20分

あずきを茹でた煮汁は使うので捨てません。


無糖 茹であずきを使う

「面倒なのは嫌い!」というのであれば、砂糖を使っていない "茹であずき” を使うという方法があります。しかし”ゆであずき” で検索しても殆どが砂糖が使われているものです。

”あずき”といえば井村屋ですが、ゆであずき | 井村屋株式会社 の商品一覧には無糖の”茹であずき”はありません。

そもそも「砂糖で茹でた”あずき”なら”あんこ”では?」と思い調べてみると、あんこの缶詰を「ゆであずき」と呼ぶのはどうして? | 日本あんこ協会 に回答がありました。

大正から昭和初期に汁気のあるゆであずきを缶詰にするのが技術的に難しく、汁気のないトロリとした茹であずきになったことから、

ゆであずき ≒ あんこ

となったそうです。

”小豆の水煮” という商品がありますが、これが本来の"茹であずき"かもしれません。


小豆の水煮

茹で小豆(小豆の水煮)ってどうなんだろうと、オーサワの有機小豆の水煮を試しに購入してみました。材料は、有機小豆(東北産)、食塩(海の精)とシンプルで価格も手頃です。

オーサワ 有機小豆 水煮

しかし、小豆200gを茹でると2.5〜3倍近くになるので、茹でた状態で一袋 200g(固形量)だと、小豆がかなり少ない、、、。

オーサワ 有機小豆 水煮

2袋を使っても米麹200gだと麹がちょっと多い感じです。

小豆を茹でる手間は省けますが、発酵あんこを作るならコスパが悪い。


発酵あんこをヨーグルトメーカーで作る

発酵あんこは、発酵温度と時間が設定できるヨーグルトメーカー(発酵器)で作れば、失敗することはありません。

注意点はひとつだけです。茹でたてのあずきはとても熱いので、そのまま米麹と混ぜてしまうと麹菌が死んでしまいます。60度以下にしてから小豆と米麹と混ぜてください

かまどさんで茹でた小豆

使っている米麹はスーパーでも購入できる『みやこ こうじ』です。

みやここうじ

『みやこ こうじ』は、麹を乾燥させて板状にしたものです。開封する前に袋の中で手で砕いて細かくしてください。

小豆と米麹を混ぜます。

茹で小豆に米麹を混ぜる

麹の発酵には水分が必要なので、小豆の茹で汁で水気が足りなければ、水(ぬるま湯)を足してください。

水分量は好みですが、水分が多くてもヨーグルトソースやデザートのトッピングに使うのであれば問題ありません。


発酵温度と発酵時間

米麹の発酵温度は60度前後です。10-12時間、発酵させるとコクと甘味がでます。

発酵温度を60度に設定します。

Kuvings(クビンス)発酵機 発酵温度

発酵時間は10時間で作っています。

Kuvings(クビンス)発酵機 発酵時間

これであとは、完成を待つだけです。


ヨーグルトメーカー(発酵器)

発酵器があると、ヨーグルト、甘酒、チーズ、味噌、酵素ドリンクなど色々な発酵食品が作れます。しかし、カスピ海ヨーグルトは低温発酵、麹は高温発酵と発酵温度も異なれば、酵素ドリンクなどは72時間の長時間発酵が必要です。

色々な発酵食品をつくるために、下記の3点を満たす発酵器を選びましょう。

  • 発酵時間が短時間から長時間まで設定できる
  • 発酵温度が低温から高温まで設定できる
  • 容量が大きい

Kuvings(クビンス)のヨーグルト&チーズメーカーは、

  • 温度設定 20℃~65℃
  • タイマー設定 1~99時間
  • 最大容量2リットル(適正容量 1.4L

と、色々な発酵食品をたっぷり作れます。


発酵あんこレシピ

発酵あんこの作り方のまとめです。

発酵あんこ 材料
 ① 小豆 200g(茹でる前)
 ② 米麹 200g

小豆(あずき)の茹で方については上を参照してください。「小豆の水煮」を使う場合は米麹と混ぜて発酵機にセットするだけです。

発酵あんこ 作り方
 ① 小豆 200g を 水 600ml で茹でこぼす
 ② 水 600ml を加えて小豆を茹でる
 ③ 米麹200gと茹でた小豆を混ぜる
 ④ 発酵温度 60℃ 発酵時間 10時間

これで優しい甘さの発酵あんこの出来上がりです。

Post Date:2021年10月10日 

VLOOKUPを使わない方法(INDIRECT+MATCH)

ゾウでもわかるGoogleスプレッドシート

GoogleスプレッドシートやEXCELで、他の表とキーが一致する行の値を取得するときにはVLOOKUP関数を使うのが定石です。

VLOOKUPはとっても便利な関数なんですが、不便なところもあります。

  • 検索できるのは指定した範囲の先頭列のみ
  • 取得する列は列番号で指定する

一番の問題は、VLOOKUP関数で検索できるのは先頭列のみで、取得する値がある列は検索する列よりも右側になければなりません。

もちろん表の列を移動させれば解決できますが、Google Sheetsで IMORTRANGE関数 で他のスプレッドシートを読み込んでいる場合などは致命的です。

また取得する値を列番号で指定するのも、AD列なら30と指定しなければならないので、列数が多くなるとわからなくなってしまいます。

ということで、VLOOKUP の代わりとなる方法(関数)の紹介です。


サンプルで使用するデータ

サンプルの表は「国番号一覧」と「国コード一覧」2つのシートです。

国番号一覧に2桁の国コードを取得するために、「国番号一覧」シートの「国名(日本語)」で「国コード一覧」シートの「日本語名」を検索して国名が一致する「2文字」から2桁の国コードを取得します。

下記の2表をコピペしてシートに貼り付けてください。

国番号一覧
国番号(電話) エリア 国名(日本語) 国名(英語) 国コード
60 アジア マレーシア Malaysia  
61 太平洋諸国 オーストラリア Australia  
62 アジア インドネシア共和国 Indonesia  
63 アジア フィリピン共和国 Philippines  
64 太平洋諸国 ニュージーランド New Zealand  
65 アジア シンガポール共和国 Singapore  
66 アジア タイ王国 Thailand  
81 アジア 日本国 Japan  
82 アジア 大韓民国 Republic of Korea  
84 アジア ベトナム社会主義共和国 Viet Nam  
86 アジア 中華人民共和国 Chaina  
90 アジア トルコ共和国 Turkey  
91 アジア インド India  
92 アジア パキスタン・イスラム共和国 Pkistan  
93 アジア アフガニスタン・イスラム共和国 Afghanistan  
94 アジア スリランカ民主社会主義共和国 Srilanka  
95 アジア ミャンマー連邦 Myanmar  
98 アジア イラン・イスラム共和国 Iran  

国コード一覧
2文字 3文字 英語名 日本語名
AF AFG Afghanistan アフガニスタン・イスラム共和国
AU AUS Australia オーストラリア
CN CHN China 中華人民共和国
IN IND India インド
ID IDN Indonesia インドネシア共和国
IR IRN Iran イラン・イスラム共和国
JP JPN Japan 日本国
KR KOR Republic of Korea 大韓民国
MY MYS Malaysia マレーシア
MM MMR Myanmar ミャンマー連邦
NZ NZL New Zealand ニュージーランド
PK PAK Pakistan パキスタン・イスラム共和国
PH PHL Philippines フィリピン共和国
SG SGP Singapore シンガポール共和国
LK LKA Sri Lanka スリランカ民主社会主義共和国
TH THA Thailand タイ王国
TR TUR Turkey トルコ共和国
VN VNM Viet Nam ベトナム社会主義共和国

EXCELならXLOOKUPを使う

Microsoft365(EXCEL)であれば VLOOKUP の上位互換である XLOOKUP があります。XLOOKUP関数を使えば、VLOOKUP の不便さが解消できます。

XLOOKUP 関数 に詳細の説明がありますが、今回のケースであれば、とってもシンプルに記載できます。

XLOOKUPの使い方

上図の国番号一覧シートの ”E2” に下記のように記載します。

=XLOOKUP(C2, 国コード一覧!D:D, 国コード一覧!A:A)

上式で国番号一覧シートのC2(国名=マレーシア)で、国コード一覧のD列(日本語名)を検索して、国名が一致する国コード一覧のA列の値が取得できます。

検索する値(C2)、検索する列(国コード一覧!D:D)、取得する値の列(国コード一覧!A:A)と、理解しやすい表記ができます。

国コード一覧シートは下記のようになっています。

国コード一覧シート(サンプル)

優れもののXLOOKUPですが、残念ながらMicrosoft365(旧Office365)でしか使えません。

Excel2019/2016では、後述するGoogleスプレッドシートで説明する方法を使ってください。


Google Sheets なら関数を組み合わせる

GoogleスプレッドシートにはXLOOKUP関数に対応する関数はありません。

しかし、関数を組み合わせることによってVOOKUPより便利にキーが一致する別表の値を取得することができます。


INDEX関数+MATCH関数

INDEX関数とMATCH関数の組み合わせる方法については、INDEX - ドキュメント エディタ ヘルプ にも記載されています。

VLOOKUPではできなかった検索する列が参照範囲の左端(1列目)になくても検索が可能になります。しかし、キーが一致する行の値を取得する列については列番号(数字)で指定する必要があります。

INDEX関数とMATCH関数を組み合わせる

上図の国番号一覧シートの ”E2” に下記のように記載します。

=INDEX('国コード一覧'!A:D, MATCH(C2, '国コード一覧'!D:D,0), 1)

先ずは、INDEX関数 の説明です。

=INDEX(参照範囲, 行番号, 列番号)

上の式を分解すると、

参照範囲 '国コード一覧'!A:D
行番号 MATCH(C2,'国コード一覧'!D:D,0)
列番号 1

となります。

行番号で指定しているMATC関数については後述しますが、'国コード一覧'!A:Dの中から1列目つのデータ(A列「2文字」の国コード)の値を取得するということを意味します。

続いて MATCH関数 の部分です。

構文は、

=MATCH(検索キー, 検索範囲, 検索種類)

です。検索キーで検索範囲の中で一致するものを探します。

MATCH(C2, '国コード一覧'!D:D,0)を分解すると、

検索キー C2 マレーシア
検索範囲 国コード一覧'!D:D 国コード一覧のD列
検索種類 0 完全一致

国コードシートのD列から「マレーシア」と完全一致するものを探してきます。戻り値は、一致するものが何番目に見つかったです。

=MATCH(C2, '国コード一覧'!D:D, 0)

とすると、D1から10個目で「マレーシア」と一致するので戻り値は10となります。

検索範囲は、'国コード一覧'!D:Dの部分を、D1:D19 としても戻り値は ”10” で表の行番号と一致します。

しかし、データが入っているのがD2からだといって

=MATCH(C2, '国コード一覧'!D2:D19, 0)

とすると、D2からマレーシアは9番目となるので戻り値が “9” となってしまいます。これだと行番号と一致しなくなってしまうので、INDEX関数で違う場所を参照してしまいます。

行番号は指定せずに D:D のように検索範囲を列だけにすれば必ず行番号と一致します。

国コード一覧シート(サンプル)

検索種類は、完全一致である 0 を必ず指定してください。省略してしまうと正しい答えが求められません。詳細は INDEXのヘルプ を参照してください。


INDIRECT関数+MATCH関数

INDEX+MATCH関数では、値を取得する列を列番号で指定しなければなりませんが、INDIRECT関数を使うと文字列として列を指定することができます。

百聞は一見に如かずです。

=INDIRECT("A" & 10)

これで、A10セルの値を取得できます。

INDIRECT関数を使って、国番号一覧シートの ”E2” に下記のように記載します。

INDIRECT関数とMATCH関数を組み合わせる

列番号は、上で説明したMATCH関数で取得します。

=INDIRECT("'国コード一覧'!A" & MATCH(C2,'国コード一覧'!D:D,0))

このように書くと、国コード一覧シートのD列の中でC2と完全一致する行の A列の値を取得することができます。

INDIRECTを分解すると下記のようになります。

行名 "'国コード一覧'!A" 文字列として指定
結合記号 &  
列番行 MATCH(C2,'国コード一覧'!D:D,0) 戻り値は列番号

国コード一覧のA列という指定は文字列なので、国コード一覧シートで列が変更されると式も変更しなければなりません。

それは、INDEX+MATCH関数の列番号でも同じなので、INDIRECTを使った方が、視覚的に理解しやすい式となります。

VLOOKを使わずにINDIRECT関数とMATCH関数を使ってみてはいかがでしょうか。

象と散歩:人気の投稿(過去7日間)