Translate

ラベル :プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル :プログラミング の投稿を表示しています。 すべての投稿を表示
Post Date:2023年3月26日 

Markdown(Typora)でMindmapを描く!

Typoraで描いたマインドマップ

Typoraは優れたMarkdownエディターで、Mac、Windows、Linuxで使えます。有料アプリですが、$14.99(約2,000円)を払う価値のあるMarkdownエディターです。

Mermaidは、Markdown形式のテキストで図を描くことができるオープンソースのJavaScriptツールですが、Typoraは、Mermaidの機能を組み込んでいるので、Mermaidのコードを書くと図形描画ができます。

Mermaidでは、シンプルなテキストを使って複雑なフローチャート、シーケンス図、ガントチャート、クラス図、そしてマインドマップなどを作成することができます。

マインドマップ自体が、階層構造になっているので、Mermaidでの構文もとても簡単です。


Mermaidを使ってマインドマップを作成する

```mermaidでMermaidのコードが書けるようになります。

```mermaid

最初の行にmindmapと書いて、次の行からがマインドマップの構文になります。

mindmap

マインドマップの各ノードを描く

マインドマップは中心のトピックから放射状にアイデアが階層構造で配置されている図です。Mermaid でマインドマップを描くときも、階層構造で記載するので。マインドマップをイメージしながら書くことができます。

下記はrootから3つのメインブランチ(a1, a2, a3)があるマインドマップの例です。階層構造で示せば下記のようになります。

  • Root
    • a1
    • a2
    • a3

mermaid でも同じようにRootから1階層下げてa1, b1, c1 と記載します。

mindmap
	root
		a1
		b1
		c1

下記のように描画されます。

Typoraでマインドマップ

次にa1の下にサブブランチを2つ、a2_1とa2_2を追加します。a1から1階層下げてa2_1, a2_2を記載します。

mindmap
	root
		a1
			a2_1
			a2_2
		b1
		c1

下記のように描画されます。

Typoraでマインドマップ

上記のマインドマップに、a2_1の下にサブブランチ、a3を追加します。

mindmap
	root
		a1
			a2_1
				a3
			a2_2
		b1
		c1

下記のように描写されますが、root --> a1 --> a2_1 --> a3 と階層が下がるとブランチが段々と細くなっていきます。

Typoraでマインドマップ

各ノードの形を変える

各ノードに使える形状には、

  • cloud(雲吹き出し)
    ) ( で括る
  • bang(爆弾吹き出し)
    )) (( で括る
  • circle(円形)
    (( ))で括る
  • round square(角丸四角形)
    ( )で括る
  • square(四角形 )
    [ ] で括る
  • default(デフォルト)

の6種類がありますが、デフォルトは何も指定しない場合です。<br/>は改行です。

mindmap
	)cloud<br/>雲吹き出し(
	))bang<br>星吹き出し((
	((Circle<br>円形))
	(rounded square<br>角丸四角形)
	[square<br>四角形]
	default shap<br>デフォルト

描画すると下記のようになります。squareとdefaulとの違いはよく見るとデフォルトは下に線が入っています。

Typoraでマインドマップ(Shape)

アイコンを使う

ノードの中にアイコンを記載することもできます。Font Awesomrのアイコンが使用でき、class名を指定します。

Font Awesomr のアイコン

Font Awesomrのclass名は、Font Awesome4.7フォント一覧で調べられます。

アイコンを記載したノードの下に同じ階層で

::icon(fa class名)

という形式で指定します。

a1に音符、b1に封筒、c1にユーザのアイコンをそれぞれ指定する場合には下記のように記載します。

mindmap
	)Root(
		music
		::icon(fa fa-music)
		envelope
		::icon(fa fa-envelope-o)
		user
		::icon(fa fa-user)

こんな感じです。Font Awesomrのclass名で記載するので少々面倒ですが、アイコンを使うとマインドマップらしくなります。

Typoraでマインドマップ(Icon)

色の指定と配置はできない

ブランチの色を変更することはできません。またブランチの配置を変更することもできません。しかし、マインドマップ描画ソフトを使わなくてもMarkdownでマインドマップが簡単に描けるのは便利です。


円グラフを書く(おまけ)

最後に Mermaid を使った円グラフの描き方です。こちらも簡単に描けます。

```mermaid コードを記載します。

  • 1行目:pie円グラフの宣言
  • 2行目:titletitleの後が円グラフの表題
  • 3行目以降:円グラフの要素
    ”グラフの項目名”:比率

下記がサンプルコードです。

pie
	title pie chart
	"50%" : 50
	"30%" : 30
	"20%" : 20

下記が、Mermaidで描いた円グラフになります。

Mermaid で描いた円グラフ

bar chart(棒グラフ)もMermaidで描けるともっと便利になるのですが、、、。

Post Date:2022年8月13日 

GASで複数のPDFを結合する

ゾウでもわかる Google Apps Script

Google Apps Script で複数のPDFを結合できないかと探していたら、officeの杜 | PDFを結合するという記事がありました。

officeの杜で共有されているこちらのコード にある mergePdfs() を活用させていただきました。元ネタは、stack overflowにある Merge Multiple PDF's into one PDF です。


結合できるPDFファイル

stack overflowのコメントにPDFのバージョンが1.5以上である場合には大きな修正が必要(結合できない)と記載されていますが、Googleの機能(印刷)やChromeの印刷でPDFにしたPDFはマージ可能でした。

対象 PDFバージョン マージ可否
Googleドキュメント 1.4
Googleプレゼンテーション 1.7
Googleスプレットシート 1.7
Google Chrome 1.4
Office 365 1.7

PDFのバージョンはAdobe Acrobat Readerで確認できます。

下記は、Googleドキュメントの印刷からPDFを保存した場合です。バージョンは1.4となっています。

Googleスプレッドシート、プレゼンテーションは、バージョンが1.7と表示されますが、

前述したように問題なく結合することができます。

Office 365でPDFに変換したファイルは結合できませんでしたが、Googleを中心としたサービスを利用しているのであれば問題なさそうです。


mergePdfs()関数を使う

mergePdfs()関数の説明と引数をみると

mergePdfs(directory, name, pdf1, pdf2, pdf3, ....)
drirectory PDFが格納されているディレクトリID
name 結合したPDFのファイル名
pdf1, ... 結合するPDFファイル

結合するPDFファイルは2つ以上複数指定できますが、引数でファイル名をひとつづつ指定するのは煩わしい。

ということで、フォルダーにあるPDFをリスト(配列に格納)して引数として渡せるように変更してみました。

またPDFファイルの結合順番はファイル名で昇順にソートした順番にします。ファイル名の先頭に数字を入れておけば番号順で結合されます。

例えば、ファイル名を

0.test.pdf
1.test.pdf
2.test.pdf

としておけば、0.test、1.test、2.testの順番で結合されます。


mergePDFs()関数の修正

先ずは、office杜のこちらのリンクからmergePdfs()のプログラムを取得してください。

修正箇所は3箇所です。

12行目

関数の引数をファイル名から、配列を1つ渡すように変更します。

修正前:
function mergePdfs(directory, name, pdf1, pdf2, opt_pdf3) {
修正後:
function mergePdfs(directory, name, pdfList {
21行目

関数の引数ループで取得しているところを、引数に格納されているファイル数分(配列の長さ分)で処理するように変更します。

修正前:
for (var argumentIndex = 2; argumentIndex < arguments.length; argumentIndex++) {
修正後:
for (var i=0; i<pdfList.length; i++) {
23行目

マージするPDFのファイルサイズ取得を引数の配列からに変更します。

修正前:
var bytes = arguments[argumentIndex].getBlob().getBytes()
修正後:
var bytes = pdfList[i].getBlob().getBytes()

また、475行目移行はPDFを分割する関数のコードになっていますので削除してしまっても問題ありません。


mergePDFs()関数を呼び出す

次は、修正したmergePDFs()関数を呼び出すプログラムになります。指定したフォルダIDにあるPDFファイルを配列(pdfList)に格納して、ファイル名順にソートしてから mergePDFsに引き渡します。

<フォルダID>にはGoogleドライブのフォルダIDを、<ファイル名>には結合して作成するファイル名にしてください。

フォルダIDは、フォルダを開いたときのURL、https://drive.google.com/drive/folders/XXXXX XXXX部分になります。

function merge(srcFolderId, fileName){
  var srcFolderId = '<フォルダID>' //フォルダ ID
  var fileName = '<ファイル名>' //結合後のファイル名

  //フォルダ内のファイルを取得
  var srcFolder = DriveApp.getFolderById(srcFolderId)
  var srcFiles = srcFolder.getFiles()

  //PDFファイルだけを配列に格納
  var pdfList = []
  while(srcFiles.hasNext()) {
    var srcFile = srcFiles.next()
    if (srcFile.getMimeType()==='application/pdf') {
      pdfList.push(srcFile);
    }
  }
  pdfList.sort()  //照準でソート
  mergePdfs(srcFolder, fileName, pdfList)
}

13行目のgetMimeType()でPDFファイルだけを対象にしています。また17行目のsort()で配列に格納した各ファイルをファイル名でソートしています。


スプレッドシートのUI機能で汎用的にする

もう少し汎用的に使えるように、スプレッドシートのUI機能を使って、フォルダIDと結合して作成するファイル名をUI上で入力するようにします。

またスプレッドシートのメニューからGASの実行を可能にします。

スプレッドシートの機能を使うので、Googleスプレッドシートの拡張機能から Apps Script を作成する必要があります。


スプレッドシートにメニューに追加する

スプレッドシートが開かれたときにメニューを追加します。こんな感じです。

「mergePDFs」 > 「PDFを結合」 で diaLog関数を実行します。

function onOpen(){ 
  SpreadsheetApp
    .getActiveSpreadsheet()
    .addMenu('mergePDFs', [
      {name: 'PDFを結合', functionName: 'diaLog'}
    ])
}

ダイアログ

詳細の説明は省きますが、「フォルダーID」と「ファイル名」の入力の後に確認画面を出してが簡単なチェックと確認を出力して merge()関数にフォルダIDとファイル名を引き渡します。

ダイアログでフォルダーIDを指定する

またPDFの結合でエラーとなった場合にもエラー出力をするようにしています。

ダイアログでエラーメッセージを出力

下記がサンプルコードとなります。

function diaLog() {
/********************
PDFがあるフォルダを指定 
********************/
  var srcFolderId = Browser.inputBox("結合したいPDFがあるフォルダを指定してください(FolderID)",Browser.Buttons.OK_CANCEL)
  //キャンセルが押下されたら終了
  if (srcFolderId == 'cancel')  {
    return
  }
  try {
    var srcFolder = DriveApp.getFolderById(srcFolderId)
  }
  catch(e) {
    Browser.msgBox('ERROR', srcFolderId + ' is not available', Browser.Buttons.OK)
    return
  }
/********************
ファイル名の指定 
********************/
  var fileName = Browser.inputBox("結合後のファイル名を指定してください(FolderID)",Browser.Buttons.OK_CANCEL)
  //キャンセルが押下されたら終了
  if (fileName == 'cancel')  {
    return
  } else if (fileName == '') {
    Browser.msgBox('ERROR', 'File name is not defined', Browser.Buttons.OK)
    return
  }
/********************
開始の確認 
********************/
  var srcFolderName = srcFolder.getName()

  var result = Browser.msgBox('Confirm', '「' + srcFolderName + '」にあるすべてのPDFを\\n「' + fileName + '」として結合します', Browser.Buttons.OK_CANCEL)
  if (result == 'cancel')  {
    return
  }

  try {
    merge(srcFolderId, fileName)  //PDF格納フォルダ,ファイル名
  }
  catch(e) {
    Browser.msgBox('ERROR', 'Error Occurred during merging', Browser.Buttons.OK)
    return
  }
  
  Browser.msgBox('Complete', 'PDFの結合が完了しました', Browser.Buttons.OK)
}

これで完成です。

スプレッドシートのメニューから dialog()関数を呼び出し、merge()関数でフォルダ内のPDFをリスト化して、mergePDFs()関数でPDFを結合します。

先人の知恵と努力に感謝です。


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

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

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

Udemy オススメ講座

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

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

講師:事務職たらこ

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

Post Date:2022年6月17日 

gasの変数について考えてみる

ゾウでもわかる Google Apps Script

Google Apps Scrit (GAS) で変数を定義するキーワードには、var, let, const の3種類があります。変数と定数の違いは明確ですが、varlet の違いはどのようなところにあるのかをサンプルプログラムを実行しながら確認してみました。


変数と定数

var, let は、再代入(値を入れ替えること)ができる変数で、const は定義した値を変更できない定数です。

再代入とは、下記の例のように、最初に x に 1を代入して、次に x に x+1(=2) を代入するということです。

function argument() {
  let x = 1
  x = x + 1
}

定数の場合には、x に 1を代入したあとに変更ができません。下記を実行するとエラーとなります。

function argument() {
  const x = 1
  x = x + 1
}

変数の利用可能範囲(スコープ)

では、varlet の違いは何でしょうか?

GoogleのV8ランタイムの説明を読むと

let および const キーワードは、ブロックスコープのローカル変数/定数を定義することができます。(和訳)

【引用】 let and const - Google Apps Script

と記載されています。

ブロックとは、{}で括られた処理のことで、例えば、if分も{}で括られた処理です。ローカル変数というのは、そのブロック内でしか使えない変数/定数ということです。

letconst は、同じ関数内であってもif文内で宣言した関数は使えないということになります。ちなみに var の場合は、同じ関数内であれば if文内で宣言した変数を利用することができます。

let and const - Google Apps Script にあるサンプルプログラムに constvar を追加してスコープについて確認してみます。

function myFunction() {
  let x = 'hello'
  const y = 'hello'
  var z = 'hello'
  if (x === 'hello') {
    let x = 'World'
    const y = 'World'
    var z = 'World'
    console.log('let x=', x)      // Prints 'world'
    console.log('const y=', y) // Prints 'world'
    console.log('var z=', z)     // Prints 'world'
  }
  console.log('let x=', x)      // Prints 'hello'
  console.log('const y=', y) // Prints 'hello'
console.log('var z=', z)     // Prints 'world' }

let, const の有効範囲はブロック内です。5行目からのif文のブロックの中では、6,7行目で変数の宣言と代入がされている 'world' が出力されます。

しかし、13,14行目のログ出力では、myFunction() として、2,3行目で変数の宣言と代入がされている 'hello' が有効な変数となります。

一方、var で定義した変数 xは、15行目のログ出力で 'world' と出力されます。これは var 変数は同一関数内で有効であり、かつ次に説明する再宣言ができるので、console.log が実行される直前(11行目)に宣言と代入がされた変数の値が出力されます。


関数で定義した変数はif, for のブロック内でも使える

上位のブロックで let, const で宣言された変数は、下位のブロックでも参照できます。

下記の例で説明します。

function checkScope() {
  let x = 0
  if (x==0)  {
    let y = 0
    for (let i=0; i<10; i++) {
      x = x + 1
      y = y + 1
    }
    console.log('y=', y)
  }
  console.log('x=', x)
}

変数 x は、checkScope() 関数で定義しているので、下位ブロックのif文、さらにその下位にあたる forループでも参照することができます。なので、x=x+1 が10回実行され、forループ、if文を抜けたあとのログ出力で x=10 と出力することができます。

変数 y は、if文の中で宣言されているので、下位ブロックのforループで参照することができます。forループでの処理で再代入された値が、if文内のログ出力で可能です。

しかし、if文を出た後にログ出力をしようとすると y は定義されていないとエラーとなります。

function checkScope() {
  let x = 0
  if (x==0)  {
    let y = 0
    for (let i=0; i<10; i++) {
      x = x + 1
      y = y + 1
    }
    console.log('y=', y)
  }
  console.log('x=', x)
  console.log('y=', y)
}

また forループで使っている変数 i も let で定義しているので forループ内だけで有効です。これを for (var i=0; ....) とすると、変数 i は、関数内で参照できる変数となります。

また 変数 i を宣言するときに、let, var のキーワードを付けないと(忘れると)、変数 i は、グローバル変数となり、変数が宣言された以降に実行されるどの関数でもアクセスできるようになってしまいます。

function checkScope() {
  let x = 0
  if (x==0)  {
    let y = 0
    for (i=0; i<10; i++) {
      x = x + 1
      y = y + 1
    }
    console.log('y=', y)
  }
  console.log('x=', x)
  print()
}
function print()	{
  console.log('i=', i)
}

再宣言

変数のスコープで var の再宣言についてふれましたが、もう少し簡単な例で説明します。下記のように var は、同じ関数内で何度でも宣言できます。

function redeclaration() {
  var x = 1
  x = x + 1
  console.log('x=', x)
  var x = 'Hello'
  console.log('x=', x)
}

let, const は同一ブロック内では1回しか宣言できません。下記のプログラムは構文エラーとなり保存できません。

function test() {
  let x = 1
  x = x + 1
  console.log('x=', x)
  let x = 'Hello'
  console.log('x=', x)
}

var, let, constの違い(まとめ)

var, int, const の違いを表にまとめます。

キーワード 再代入 再宣言 スコープ
var 可能 可能 同一関数内
let 可能 不可 ブロック内
const 不可 不可 ブロック内

参照できる範囲が少なく、再宣言ができないということは、より明示的に変数を使うということになるので、「varではなくlet を利用しましょう」というのも納得できます。


関数間で値の引き渡し

関数の中で関数を呼び出す場合には、引数として関数に値を引き渡すことができます。また、関数での実行結果を戻す場合には、戻り値として結果を返却することができます。

function argument() {
  let x = 1
  let y = 2
  let z = cal(x, y)
  console.log('z=', z)
}
function cal(a, b)	{
  let c = a + b
  return c
}

cal() を呼び出すときに引数として x, y をセットしています。7行目の function cal(x, y)では、4行目で設定した引数 x, y をそれぞれ a, b にセットしています。この関数の中では、合算した値(1+2)を cに代入して、cを戻り値としています。

関数の呼び出し側は、戻り値を変数zに代入するために let z = cal(x, y) としています。


グローバル変数

最後にグローバル変数についてです。関数内ではなくプログラムのトップレベルで定義された変数/定数は、プログラ内のどこから(同じプロジェクト内のどの関数から)でもアクセスすることができます。

const x = 1
const y = 2
function main()	{
  sub1()
  sub2()
  console.log('x=', x)
  console.log('y=', y)
}
function sub1()	{
  console.log('x=', x)
}
function sub2()	{
  console.log('y=', y)
}

変数も奥が深いですね。


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

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

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

Udemy オススメ講座

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

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

講師:事務職たらこ

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

Post Date:2022年5月7日 

Googleスプレッドシートの更新された範囲をURLとして通知する

ゾウでもわかる Google Apps Script

シートの更新通知を使うと確認が必要のない更新通知も送られてくるので、自分に必要な項目が更新されたときだけに通知が欲しい。

また、どこが変更されたのかが分かると、通知を受けた人に優しい仕様となります。

象と散歩: Googleスプレッドシートの更新された値を知るで、変更された箇所の行番号と列番号を取得する方法を紹介しましたが、変更範囲をセル範囲が指定されたリンクとして作成するGASプログラムを作ってみました。


URLの取得

onEdit()関数については、象と散歩: Googleスプレッドシートの更新された値を知る で説明していますが、スプレッドシートが更新されたときに実行される関数です。

onEdit(e)	{
  var ssUrl = e.source.getUrl()	//スプレッドシートのURL
  console.log(ssUrl)
}

セルの内容が更新されると、上記の関数が実行されます。マニュアルで実行するとエラーとなりますので、セルの値を更新してログを確認してください。

onEdit()関数の引数に e を指定していますが、e.source でスプレッドシートオブジェクトが取得でき、getUrl()でスプレッドシートのURLが取得できます


シートはGIDパラメータで指定する

上記で取得したスプレッドシートのURLは、下記のような構成になっています。

https://docs.google.com/spreadsheets/d/xxxxx/edit

複数シートがある場合には、URLにGIDパラメータが付与されています。

https://docs.google.com/spreadsheets/d/xxxxx/edit#gid=749420395

最初のシート(シート 1)は、必ず gid=0 ですが、getSheetId() で GIDを取得することができます。

onEdit(e)	{
  var sheet = e.source.getActiveSheet()
  var sheetId = sheet.getSheetId()	//シートID
  console.log(sheetId)
} 

セル範囲はRANGEで指定する

セルを右クリックして"このセルへのリンクを取得"をクリックすると

範囲を指定してリンクを取得する

下記のようなURLが取得できます。

https://docs.google.com/spreadsheets/d/xxxxx/edit#gid=0&range=C2

また複数セルを選択した状態で右クリックして"この範囲のリンクを取得"をクリックすると下記のようなURLが取得できます。

https://docs.google.com/spreadsheets/d/xxxxx/edit#gid=0&range=A3:C3

"range=" で範囲を指定したURLを取得できます。


A1形式で範囲を取得する

onEdit()で実行されたときにrangeで変更範囲が取得できますが、A3:C3のようなA1形式で範囲を取得するには、range.getA1Notation() を使います

更新範囲がひとつのセルならA3、範囲ならA3:C3のように取得することができます。

onEdit(e)	{
  var sheetRange = e.range.getA1Notation()	//変更範囲
  console.log(sheetRange)
}

シートの値を変更して、ログを確認してください。複数セルの範囲は、シート上で範囲選択をしてコピペをすることで確認できます。


シートと範囲を指定したURLを作成

シートID(GID)と範囲(RANGE)を指定したURLを作成します。

function onEdit(e) {
  var ssUrl = e.source.getUrl()	//スプレッドシートのURL
  var sheet = e.source.getActiveSheet()
  var sheetId = sheet.getSheetId()	//シートID
  var sheetRange = e.range.getA1Notation()	//変更範囲
  var url = ssUrl + '#gid=' + sheetId + '&range=' + sheetRange
  console.log(url)
}

ログに出力されたURLをコピペして、範囲選択がされた状態でスプレッドシートが開くかを確認してください。


変更範囲の確認

特定の範囲が更新されたときだけ通知があるように、更新されたセルが対象としたいセル範囲内にあるかを確認します。

下記のサンプルプログラムでは、targetStartColumn, targetEndColumn で対象列を、targetStartRow, targetEndRow で対象行を指定しています。

下例では、C列の2行目から1000行目が更新されたかを確認するサンプルコードとなります。

function onEdit(e)  {
  var targetStarColumn =3 //対象セル(開始列)
  var targetEndColumn = 3 //対象セル(終了列) 
  var targetStartRow = 2  //対象セル(開始列)
  var targetEndRow = 1000 //対象セル(終了列)

  var rowStart = e.range.rowStart //更新開始行の取得
  var rowEnd = e.range.rowStart   //更新終了行の取得
  var columnStart = e.range.columnStart //更新開始列の取得
  var columnEnd = e.range.columnEnd     //更新終了列の取得

  var ssUrl = e.source.getUrl() //スプレッドシートのURL
  var sheet = e.source.getActiveSheet()
  var sheetId = sheet.getSheetId()  //シートID
  var sheetRange = e.range.getA1Notation()  //変更範囲

  var text = '変更箇所の通知\n'
  if (targetStarColumn <= columnStart &&  columnEnd <= targetEndColumn
      &&  targetStartRow <= rowStart &&  rowEnd <= targetEndRow) {
    text = text + ssUrl + '#gid=' + sheetId + '&range=' + sheetRange
  } else  {
    text = '対象範囲外の更新'
  }
  console.log(text)
}

対象範囲が更新された場合に "更新されたシートとセル範囲を指定したURL" を作成します。シートを更新して実行結果をログで確認してください。


メールを送信する

シンプルトリガーのonEdit()ではメール送信ができないので、function名を変更して、トリガーを設定します。

トリガーの設定方法については、

を参考にしてください。

メール送信は GmailAppクラスのsendEmailメソッドを使います。

GmailApp.sendEmail(宛先, 件名, 本文, {オプション})

オプションは省略可能です。(詳細はClass GmailAppを参照してください)

mailTo で指定しているメールアドレスは、自分のメアドに変更してください。

function updateNotice(e) {
  var ssUrl = e.source.getUrl()	//スプレッドシートのURL
  var sheet = e.source.getActiveSheet()
  var sheetId = sheet.getSheetId()	//シートID
  var sheetRange = e.range.getA1Notation()	//変更範囲
  var url = ssUrl + '#gid=' + sheetId + '&range=' + sheetRange

  // メール送信
  var mailTo   = 'sample@gmail.com'
  var subject  = '更新通知'
  var bodyText = '変更箇所の通知\n'
  bodyText     = bodyText + url
	
  GmailApp.sendEmail(
    mailTo,
    subject,
    bodyText
  )
}

特定のセルが更新されたときにメールを出す場合には下例を参考にしてください。

function updateNotice(e) {
  var targetStarColumn = 3	//対象セル(開始列)
  var targetEndColumn = 3	//対象セル(終了列) 
  var targetStartRow = 2	//対象セル(開始列)
  var targetEndRow = 1000	//対象セル(終了列)

  var rowStart = e.range.rowStart //更新開始行の取得
  var rowEnd = e.range.rowStart   //更新終了行の取得
  var columnStart = e.range.columnStart //更新開始列の取得
  var columnEnd = e.range.columnEnd     //更新終了列の取得

  var ssUrl = e.source.getUrl() //スプレッドシートのURL
  var sheet = e.source.getActiveSheet()
  var sheetId = sheet.getSheetId()  //シートID
  var sheetRange = e.range.getA1Notation()  //変更範囲

  var text = '変更箇所の通知\n'
  if (targetStarColumn <= columnStart &&  columnEnd <= targetEndColumn
      &&  targetStartRow <= rowStart &&  rowEnd <= targetEndRow) {
    text = text + ssUrl + '#gid=' + sheetId + '&range=' + sheetRange
    sendMail(text)
  }
}
function sendMail(text)	{
  // メール送信
  var mailTo   = 'sample@gmail.com'
  var subject  = '更新通知'
  var bodyText = text

  GmailApp.sendEmail(
    mailTo,
    subject,
    bodyText
  )
}

上記例では更新されたセル範囲でURLを作成していますが、range=開始行番号:終了行番号とすると、更新された行をフォーカスしたURLが作成できます。

https://docs.google.com/spreadsheets/d/XXXXX/edit#gid=0&range=3:3

上記も、rowStart と rowEnd を使って簡単に作成できます。


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

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

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

Udemy オススメ講座

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

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

講師:事務職たらこ

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

Post Date:2022年3月6日 

GASでExcelをGoogleスプレッドシートに変換する

ゾウでもわかる Google Apps Script

ExcelファイルのデータをGASで処理するには、GoogleDrive上でExcelファイルを開いてGoogleスプレッドシートで保存する必要があります。

ExcelをGoogleスプレッドシートとして保存

小さなExcelファイルなら上記の方法でも構いませんが、大きなExcelファイルだと ”Googleスプレッドシートとして保存” で時間がかかりすぎて保存できない、、、。

また複数ファイルがあるときは一括変換できれば楽ちんです。

ということで、GASでExcelファイルをGoogleスプレッドシートに変換する2つの方法について説明します。

  1. Excelファイルを指定してスプレッドシートに変換する
  2. フォルダにあるExcelファイルを一括変換する

Excel to Google Sheets には Drive APIが必要

Google Apps Script でExcelファイルを開く場合には、Googleドライブのファイルやフォルダを操作できる DriveAppクラス を使います。

但し、そのまま、DriveAppのコードを書いて実行しても

エラー ReferenceError: Drive is not defined

と、実行できません。


Drive APIサービスを追加する

DriveAppクラスを使うためには、Apps Scriptエディターの左側のメニューにある「サービス」からDrive APIを追加する必要があります。

① サービスの右側にある+をクリック

サービスの追加

② 一覧から Drive API を選択して追加

Drive APIを選択

Drive APIが追加されると左メニューのサービスの下にDriveが表示されます。

サービスにDriveが追加

これで DriveAppクラスが利用できます。


Excelファイルを指定してスプレッドシートに変換する

Excelファイルを指定して、指定したフォルダにGoogleスプレッドシートを出力するサンプルコードとなります。

Excelファイルと出力先のフォルダはIDで指定します。


ExcelファイルとフォルダのID

GoogleドライブからExcelファイルを開いたURLのXXXXXの部分がIDとなります。

https://docs.google.com/spreadsheets/d/XXXXX/edit

ExcelのファイルID

GoogleドライブのフォルダIDは、XXXXXの部分がIDになります。

https://docs.google.com/drive/u/0/folder/XXXXX

フォルダID


サンプルコード(Excelファイル指定)

上記で説明したExcelファイルと出力先のフォルダのIDを指定する、簡単なサンプルコードです。

12行目で取得したExcelファイル名を13行目でログに出力していますが、変換したGoogleスプレッドシート名も同じファイル名にしています。

function convertExcel()  {
  //変換するExcelファイル
  let excelFileId = 'XXXXX'

  //スプレッドシート出力先フォルダー
  let destFolderId = 'XXXXX'

  //ExcelファイルをファイルIDで取得
  var excelFile = DriveApp.getFileById(excelFileId)

  //Excelファイル名取得
  var fileName = excelFile.getName()
  Logger.log(fileName)

  var option =  {
    mimeType:MimeType.GOOGLE_SHEETS,   //Google sheets
    parents:[{id:destFolderId}],                       //出力先フォルダー
    title:fileName                                          //出力先ファイル名
  }
  Drive.Files.insert(option,excelFile)
}

20行目の

Drive.Files.insert(option,excelFile)

で、変換を実施していますが、15〜19行目のオプションで、

  • MIMEタイプ
  • 出力先フォルダー
  • 出力先ファイル名

を指定しています。

スプレッドシートへの変換なのでMIMEタイプをGOOGLE_SHEETSとしていますが、Enum MimeType | Apps Script | Google Developers に全てのMIMEタイプが記載されています。


変換後に処理を続ける

Googleスプレッドシートに変換してから、そのままシートをGAS処理する場合には、20行目からを下記のように変更してください。

取得したシート名をログに出力する簡単なサンプルです。

function convertExcel()  {
  //変換するExcelファイル
  let excelFileId = 'XXXXX'

  //スプレッドシート出力先フォルダー
  let destFolderId = 'XXXXX'

  //ExcelファイルをファイルIDで取得
  var excelFile = DriveApp.getFileById(excelFileId)

  //Excelファイル名取得
  var fileName = excelFile.getName()
  Logger.log(fileName)

  var option =  {
    mimeType:MimeType.GOOGLE_SHEETS,   //Google sheets
    parents:[{id:destFolderId}],                       //出力先フォルダー
    title:fileName                                          //出力先ファイル名
  }
  var sheetObj = Drive.Files.insert(option,excelFile)

  //スプレッドシートIDを使って、スプレッドシートオブジェクトを取得
  var sheet = SpreadsheetApp.openById(sheetObj.id).getActiveSheet()
  Logger.log(sheet.getName())  
}

変換したスプレッドシートにシートが複数ある場合には、getSheetByName() でシート名を指定します。


フォルダにあるExcelファイルを一括変換する

次は、フォルダにある複数のExcelファイルを一括でGoogleスプレッドシートに変換する方法です。

フォルダにExcelファイル以外がある場合には処理されないようにしています。

function convertExcels() {
  //Excelがあるフォルダ
  let sourceFolderId = 'XXXXX'
  //スプレッドシート出力先フォルダー
  let destFolderId = 'XXXXX'

  // フォルダ配下のファイルを取得
  var sourceFolder = DriveApp.getFolderById(sourceFolderId)
  var folderFiles  = sourceFolder.getFiles()

  // Excelファイルをイテレートして順にスプレッドシートに変換
  while(folderFiles.hasNext()) {
    var file = folderFiles.next()
    //Excelファイルの判断   
    if (file.getMimeType() == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')  {
      var fileName  = file.getName()
      Logger.log(fileName)
      Logger.log(file.getMimeType())
      var option =  {
        mimeType:MimeType.GOOGLE_SHEETS,  //Google sheets
        parents:[{id:destFolderId}],      //出力先フォルダーを指定
        title:fileName                    //出力先ファイル名
      }
      Drive.Files.insert(option,file)
    }
  }
}

フォルダにあるファイル一覧を取得しているのが、8〜9行目です。

そして、12行目の while文のhasNext() で13行目の next()で取得したファイル一覧から取り出せるファイルが残っているかを調べて、順番にファイルを処理していきます。

Excelファイルだけを変換するために、取り出したファイルがEXCELファイルかを15行目で調べています。

18行目でgetMimeType()でファイルのMIMEタイプを出力していますが、application/vnd.openxmlformats-officedocument.spreadsheetml.sheet が、拡張子xlsのExcelファイルとなります。


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

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

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

Udemy オススメ講座

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

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

講師:事務職たらこ

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

Post Date:2022年1月3日 

GASでシートを処理するなら配列を使え!

ゾウでもわかる Google Apps Script

GoogleスプレッドシートをGAS(Google Apps Script)で処理するのであれば配列を覚えましょう。配列を使ってシートの読み書きを一括で行うと処理が格段に速くなります。


なぜ配列を使うのか

GASには、

  • Google Sheets のアクセスに時間がかかる
  • 6分で処理が強制停止される

という制約があります。

Googleスプレッドシートを操作するときに

  • getRange()
  • getValue()/getValues()
  • setValue()/setValues()
  • getLastRow()/getLastColmun()

などのメソッドを使いますが、これらの呼び出しには時間がかかります。

行数が多いシートでは、1行づつシートを読み書きするのと、配列で一括で読み書きするのでは分単位で処理時間が違ってきます。


二次元配列とは

「でも、配列って難しそう、、、」

と、思われがちですが、二次元配列はシートと同じと考えれば簡単です。下記のように行と列で表記されます。

values[行][列]

valuesは変数名です。後ろに続く最初の[]が行で、次の[]が列です。シートの"A1"という表記はA列1行目と列行の順番ですが、配列は行列の順番です。

注意点としては配列のインデックスは0から始まるので、 A1と同じ1行1列目のデータはvalues[0][0]となります。

  • 二次元配列はシートと同じ行と列
  • 表記の順番は[行][列]
  • インデックスは0から始まる

これだけ理解できれば配列を自在に扱うことができます。

では、下記がシートで考えた場合にどのデータを指しているか考えてみてください。

  • values[1][2]
  • values[2][0]

答えは、

  • 2行3列目のデータで C2
  • 3行1列目のデータで A3

です。

また、values[0]と記載した場合は1行目のデータとなり、values と[]を付けなければ配列全体となります。


サンプルデータで配列の中身を確認する

実際に、4行3列のサンプルデータで二次元配列を確認していきます。

二次元配列のサンプル

下記のデータをGoogleスプレッドシートにコピーしてください。

グループ男性女性
A1020
B2010
C1515

シートをシートIDとシート名から取得していますので、下記のコードの sheetId と sheetName の値を変更してください。

シートIDはURLの赤字の部分となります。

https://docs.google.com/spreadsheets/d/abcd1234/edit

シート名は、シートに付けている名前です。

シート名:シート1

下記のコードを実行します。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
	
  Logger.log(values[1][2])
  Logger.log(values[2][0])
  Logger.log(values[0])
  Logger.log(values)
}

実行結果は下記の通りです。

20.0
B
[グループ,男性,女性]
[[グループ,男性,女性],[A,10.0,20.0],[B,20.0,10.0],[C,15.0,15.0]]

最後の配列全体の出力結果はカンマ区切りでデータが続いているので一見難しそうです。しかし、各要素を見ると[]が各行のデータで、その中に各列のデータがカンマで区切られていることがわかります。


シートのデータを配列に読み込む

上記サンプルコードの8行目でシート全体(データ範囲)を読み込んでいます。

var values = sheet.getDataRange().getValues()

getRange()で、下記のように範囲を指定してシートを読み込むこともできます。

getRange(開始行、開始列, データを読み込む行数, データを読み込む列数)

データを読み込む行数と列数はgetLastRow()、getLastCloumn()でそれぞれ取得できます。

//範囲を指定して読み込む
var lastRow	= sheet.getLastRow()	//最終行の取得
var lastColumn	= sheet.getLastColumn()	//最終列の取得
var values = sheet.getRange(1, 1, lastRow, lastColumn).getValues()

しかし、シート全体を読み込むのであれば、getDataRange() で範囲を指定しない方が簡単です。


ヘッダー行の削除 shift()メソッド

計算処理などをする場合にはヘッダー行は不要です。

範囲指定で2行目から最終行を読み込むこともできますが、シート全体を配列に読み込んでから1行目を削除した方が簡単です。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
	
  //ヘッダー行の削除
  values.shift()

  Logger.log(values)
}

shift()メソッドは、二次元配列先頭の一次元配列を削除します、、、。簡単にいえば、1行目のデータの削除です。

配列の全行処理は for ... in が便利

配列を全行処理するには、for...in 文が便利です。

下記の11-13行目が for...in の記載となります。これで、in の後ろに記載した配列(values)を1行づつ処理することができます。変数 i には現在処理をしている行数が格納されます。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
  
  //各行の出力
  for (var i in values) {
    Logger.log(values[i])
  }
}

前述したように配列のインデックスは0から始まりますので、変数 i の出力結果は、0, 1, 2 となります。

普通にforループで記載するならlengthプロパティで配列の要素数(行数)を求めてループさせます。

下例では要素数(length)は3です。0行目、1行目、2行目と3回ループを回すので配列の要素数未満という条件でループさせます。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
  
  //各行の出力
  numOfRow = values.length
  for (var i=0; i < numOfRow; i++) {
    Logger.log(values[i])
  }
}

for ... in文で記載した方が簡単です。

末尾に列を追加する push()メソッド

次に男性と女性の数を合算して列の最後(D列)に追加します。

繰り返しとなりますが、配列のインデックスは0から始まりますので、男性は values[i][1] で女性は values[i][2]です。

行の最後に列を追加するには、push()メソッドを使います。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
  
  //各行の出力
  for (var i in values) {
    values[i].push(values[i][1] + values[i][2])
  }
  Logger.log(values)
}

i行目の最後に男性と女性の合算値を追加して、最後に配列全体を出力しています。


ヘッダー行の追加 unshift()メソッド

合算値を追加した新しいヘッダー行を unshift()メソッドで先頭行に追加します。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
  
  //男性と女性を合算して行の末尾に列を追加
  for (var i in values) {
    values[i].push(values[i][1] + values[i][2])
  }

  //ヘッダー行の追加
  values.unshift('グループ','男性','女性','合計')

  Logger.log(values)
}

配列のデータをシートに書き戻す

最後にシートに書き戻します。シートにデータを書き込むには、書き込む範囲を getRange() で指定する必要があります。

getRange(開始行, 開始列, 行数, 列数)

開始行と開始列は、それぞれ1行目と1列目です。

行数は、values.length です。列数は配列の最初の行の長さ values[0].length で求まります。

指定した範囲に 配列 values を書き込みます。

function myFunction() {
  //シートオブジェクトの取得
  var sheetId = 'abcd1234'
  var sheetName = 'シート1'
  var sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName)

  //配列へシート全体を読み込む
  var values = sheet.getDataRange().getValues()
  
  //男性と女性を合算して行の末尾に列を追加
  for (var i in values) {
    values[i].push(values[i][1] + values[i][2])
  }

  //ヘッダー行の追加
  values.unshift('グループ','男性','女性','合計')

  //配列をシートに書き込む
  var values = sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}

シートを確認すると、配列で計算した結果が書き込まれているのがわかります。

配列をシートに書き込んだ結果

以上が、

  1. シートから配列に読み込む
  2. 配列で処理をする
  3. 配列をシートに書き込む

という、一連の処理の簡単な説明となります。


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

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

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

Udemy オススメ講座

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

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

講師:事務職たらこ

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

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年5月20日 

Googleドキュメントを使ってGASで差し込みメールを出す(HTMLメールも可)

ゾウでもわかる Google Apps Script

GASでメールを送信をするのは簡単ですが、メール本文を整形するのが面倒です。

しかし、Googleドキュメントでメールのテンプレートを作れば簡単です。またメール本文の一部を変数として、送付する人の名前などを書き換える差し込み機能を使うこともできます。

テキストメールだけでなくHTMLメールでも同じようにGoogleドキュメントでテンプレートを作成して差し込みメールを出すことも可能です。

今回説明するのは、

  • Googleドキュメントでメールのテンプレートを作る
  • メール本文の一部を送信する人によって書き換える
  • HTMLメールの送信

についてです。

実際に送付されるHTMLメールはこんな感じです。赤字にしている部分が差し込みです。画像も添付しています。

HTMLメールサンプル

テキストメールのサンプルは下記となります。テキストメールなので画像はなく、リンクも使えないのでURLの記載となります。

テキストメールサンプル

メールのテンプレート作成する

先ずは、Googleドキュメントでメールのテンプレートを作成します。


テキストメールのテンプレート

テキストメールは、見たまんまなのでとても簡単です。

赤字が差し込み用の変数です。

面談日のお知らせ

[value1]さん、面談の日程が確定しました。

日付 :[value2]
時間 :[value3]

詳細については、下記URLからご確認ください。

差し込みとするのが赤字の部分です。プログラムで判断できる文字列であれば何でも構いませんが、

  • [value1] ・・・ 氏名
  • [value2] ・・・ 日付
  • [value3] ・・・ 時間

としています。


HTMLメールのテンプレート

続いて、HTMLメールのテンプレートとなります。橙色がHTMLタグです。

<h2>面談日のお知らせ<h2>

<p><strong>[value1]</strong>さん、面談の日程が確定しました。</p>

<ui>
    <li>日付 :<span style="color: red">[value2]</span></li>
    <li>時間 :<span style="color: red">[value3]</span></li>
</ui>
 
<p>詳細については、<a href="https://walking-elephant.blogspot.com/2021/05/shower-head.html">こちら</a> からご確認ください。</p>

<img alt="ゾウでもわかる Google Apps Script" height="180px;" src="https://lh3.googleusercontent.com/StoB14GWX9gSlf6HgsiRuWEn9TyN7XIejqUeWh9GCxQmrGVVxMwXE19xThkj_VvgVOIAdF_3iACbSJyJYPG1hro-bnl9G7yEqMoCWHxgP43oCe-0RHSMTt3fDjoOcLKVSkGbeerL9vI=s16000" title="ゾウでもわかる Google Apps Script" width="320px;" />

GASでメールを送信する

サンプルプログラムは、下記の3パートから構成されています。

  1. Googleドキュメントを読み込む
  2. 読み込んだコンテンツから特定文字列を置換する
  3. HTMLメールを送信する

HTMLメールで送信しますが、HTMLメールが受信できない環境であればテキストメールが表示されます。

サンプルコードは下記になります。

function testMail() {
/***************************************************
* Googleドキュメントを読み込む
***************************************************/
  //GoogleドキュメントのID
  var textDocId = '...1234567890...'
  var htmlDocId = '...abcdefghij...'

  //テキストメール テンプレートの取得
  var txtDoc = DocumentApp.openById(textDocId)
  var txtTemplate = txtDoc.getBody().getText()

  //HTMLメール テンプレートの取得  
  var htmlDoc = DocumentApp.openById(htmlDocId)
  var htmlTemplate = htmlDoc.getBody().getText()

/***************************************************
* メール本文の特定文字列を置換する
***************************************************/
  //変数の設定
  var var1 = 'タカハシ'
  var var2 = '2012年5月14日(金)'
  var var3 = '13:00'

  //プレーンテキスト内の変数を置換
  var bodyText = txtTemplate.replace('[value1]',var1).replace('[value2]',var2).replace('[value3]',var3)

  //HTML内の変数を置換
  var bodyHtml = htmlTemplate.replace('[value1]',var1).replace('[value2]',var2).replace('[value3]',var3)
/*************************************************** * HTMLメールを送信する ***************************************************/ var mailTo = 'sample_mail@gmail.com' var subject = 'テストメール' GmailApp.sendEmail( mailTo, subject, bodyText, { htmlBody:bodyHtml } ) }

Googleドキュメントを読み込む

Googleドキュメントで作成したテンプレートをIDを指定して読み込みますが、6行目がテキストメール、7行目がHTMLメールのGoogleドキュメントのIDとなります。

5. //GoogleドキュメントのID
6. var textDocId = '...1234567890...'
7. var htmlDocId = '...abcdefghij...'

Googleドキュメントの編集URLが下記の場合、赤字の部分がIDです。

https://docs.google.com/document/d/...1234567890.../edit

Googleドキュメントで作成したテンプレートの値に変更してください。

10行目でGoogleドキュメントで作成したテキストメールのテンプレートを上記のIDを指定して開いて、11行目で内容をテキストとして変数 txtTemplate に代入しています。

  9. //テキストメール テンプレートの取得
10. var txtDoc = DocumentApp.openById(textDocId)
11. var txtTemplate = txtDoc.getBody().getText()

HTMLメールのテンプレートも同じように14-15行目で変数 htmlTemplate に代入します。

これで、Googleドキュメントの読み込みは完了です。


メール本文の特定文字列を置換する

差し込み部分の説明です。

21-23行目でメールに差し込む値を定義しています。

20. //変数の設定
21. var var1 = 'タカハシ'
22. var var2 = '2012年5月14日(金)'
23. var var3 = '13:00'

26行目がテキストメール・テンプレートから読み込んだ値の置換、29行目がHTMLメール・テンプレートから読み込んだ値の置換です。

25. //プレーンテキスト内の変数を置換
26. var bodyText = txtTemplate.replace('[value1]',var1),.....省略.....
27.
28. //HTML内の変数を置換
29. var bodyHtml = htmlTemplate.replace('[value2]',var1)......省略.....

replaceを使って文字列を置換しています。

Strings.replace('置換前の文字列','置換後の文字列')

変数をひとつにして説明すると、下記の例では、txtTemplate にある [value1] を探して 細野 に置換した内容を bodyText に代入しています。

var bodyText = txtTemplate.replace('[value1]','細野')

差し込みが多い場合には下記のように分割することもできます。

var bodyText = txtTemplate.replace('[value1]',var1)
var bodyText = bodyText.replace('[value2]',var2)
var bodyText = bodyText.replace('[value3]',var3)

HTMLメールを送信する

メールを送信するのには GmailApp.sendEmail を使います。

GmailApp.sendEmail(送付先メアド, メールタイトル, 本文, {オプション})

34-35行目で mailTo に送信先アドレスを、subject にメールタイトルを代入しています。

自分のメールアドレスに変更してください。

34. var mailTo = 'sample_mail@gmail.com'
35. var subject = 'テストメール'

37-44行目がメール送信部分です。

HTMLメールを送信するので、オプション部に htmlBody:'HTMLメール本文' を指定し、本文 にはHTMLが表示できない場合のテキスト文を指定しています。

こうしておけば、HTMLメールを表示できない環境の場合には、テキストメールが表示されます。

37. GmailApp.sendEmail(
38.   mailTo,
39.   subject,
40.   bodyText,
41.   {
42.     htmlBody:bodyHtml
43.   }
44. )

テキストメールを送信する

テキストメールとして送信する場合は、サンプルプログラムのオプション部を削除して下記のように変更します。

GmailApp.sendEmail(
  mailTo,
  subject,
  bodyText
)

シートの文字装飾された文字列をHTMLメールで送信する

コメントに質問を受けたのでスプレッドシートから値と文字装飾(色、太文字)を読み込んでHTMLメールを送信するサンプルプログラムを作成しました。

読み込むシートには、A1セルに赤字太文字で"象と散歩"と入力しています。

サンプル スプレッドシート

プログラムでHTMLタグを生成する

セルの文字色は、.getFontColor()で取得できます。太字かどうかは、.getFontWeight()でわかります。戻り値が、boldなら太字、太文字でなければnormalです。

次にHTMLの生成ですが、セルの文字が太文字かを判断して、太文字であれば<strong>タグで囲みます。文字色は必ずセットされているので、<p style='color:#カラーコード>で括ります。

文字色は必ずセットされるので、装飾文字のパターンは太字の有無の2パターンですが、文字色が「黒」か「他の色」かの4パターンで生成されるHTMLを下表に示します。

字幅太字文字色HTML
象と散歩なし<p style='color:#000000'>象と散歩</p>
象と散歩なし<p style='color:#ff0000'>象と散歩</p>
象と散歩太字<p style='color:#000000'><strong>象と散歩</strong></p>
象と散歩太字<p style='color:#0000ff'><strong>象と散歩</strong></p>

下記がサンプルコードとなります。

function sendFormattingEmail() {
  // シート名を指定
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート1")
  // セルの内容を取得
  let cellValue = sheet.getRange("A1").getValue()
  // セルの文字色を取得
  let cellFontColor = sheet.getRange("A1").getFontColor()
  // セルの文字が太字かどうかを判定
  let cellIsBold = sheet.getRange("A1").getFontWeight() === "bold"

  Logger.log(cellValue)
  Logger.log(cellFontColor)
  Logger.log(cellIsBold)

  // 宛先メールアドレス
  let mailTo  = "sample_email@gmail.com"
  // メールの件名
  let subject = "文字色と太文字を指定したメール"

  // セルの値を取得
  let body = cellValue
  // 太字の場合はstrongタグで囲む
  if (cellIsBold) {
    body = "<strong>" + body + "</strong>"
  }
  // 文字色をstyle属性で指定
  body = "<p style='color:" + cellFontColor + "'>" + body + "</p>"

  Logger.log(body)

  // HTML形式のメールを送信
  GmailApp.sendEmail(mailTo,subject,'',{htmlBody:body})
}

上記を実行すると、セルに設定された文字装飾(文字色と太字有無)で、HTMLメールを送信することができます。(2023.7.23追記)


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

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

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

Udemy オススメ講座

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

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

講師:事務職たらこ

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

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