Google FormをとあるWebサイトから情報を取得して、それを元に更新させたい!結果は、満足な出来栄えとなった。
スプレッドシート(Google Spreadsheets)でGoogle Apps Scriptをよく使っているが、フォーム(Google Form)でも普通に使える。
Google Apps ScriptでのWebスクレイピング
そもそもGoogle Apps ScriptからWebサイトのドキュメントを抽出できるのか?と思いきや、あたり前のよっちゃんイカで出来た。↓参考ページ。
- Google Apps Script(GAS)を使ったwebスクレイピング - Qiita
- GASで簡単WEBスクレイピング!HTMLを簡単にパースできるライブラリParserを使ってみた - Binary Diary
var url = "http://スクレイピングするページ/";
var html = UrlFetchApp.fetch(url).getContentText('UTF-8');
上記ページでこぞってとりあげてるのが Parserというライブラリーだけど、確かにこれは便利だ。
ライブラリーの設定は、リソース -> ライブラリー...
library’s script id | バージョン | Identifier | Development |
---|---|---|---|
M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV1 | 7 | Parser | 無効 |
Parser 使えば、特定の範囲をガバっと取れる。
<a href="javascript:void(0)" onclick="function('param'); return false;">アンカー</a>
Parser.data(html).from('onclick="function(').to(')').build();
なんて書き方をしても、そのfunction括弧の間のパラメーター'param'
とか取れちゃうわけだ。
ただ、
Parser_.prototype.from = function(pattern,offset) {
pattern とあるが、正規表現パターンでなく、ただの文字列なので、そこまで柔軟な抽出が出来るわけではない。
また、どうしてか、
<input type="hidden" id="special_name" value="arimasou16" />
のvalueを抽出しようとして、
Parser.data(html).from('<input type="hidden" id="special_name" value="').to('"').build();
という書き方をしたのだけど、どうしてか上手く行かないことがあった。抽出できないわけでなく、別の部分を抽出してしまった。
またどうしても抽出する要素の隣を取得したい、getNextSibling的なことをしたい場合がある。
<div>nexttodoor</div><input name="purpose" value="abc" />
Google Apps Scriptは document がないのでgetElementsByTagName()
なことはできない。
そうなってくると
var ele = Parser.data(html).from('<div>nextodoor</div>').to('/>').build();
という書き方で、<input name="purpose" value="abc"
まで取得する。そして、それを自分はXML変換して、そこから属性値なり、テキストノードを取得する力技?で乗り気った。
var xml = XmlService.parse(ele + '/>');
var input = xml.getRootElement();
var value = input.getAttribute('value').getValue();
みたいな。一時期まではXMLのルールで記述されたHTML、XHTMLなんてものが開発されたこともあったので、HTMLをXML化するのはそんなにシンドいわけでもない。一度、XML化してしまえば、 Class Element | Apps Script | Google Developersを見てのとおり、兄弟要素、子要素など取得しやすくなる。
Google Formの取得
そもそも form の取得はフォームのGoogle Apps Scriptとして実装するなら以下↓。
var form = FormApp.getActiveForm();
もしくはスプレッドシートや他のから取得する場合は、ID指定で取得する。IDはフォームの編集ページのURL https://docs.google.com/forms/d/1234567890abcdefghij1234567890klmnopqrst1234/edit
のd/
と/edit
の間の値。
var form = FormApp.openById('1234567890abcdefghij1234567890klmnopqrst1234');
Class FormApp | Apps Script | Google Developers
Google Formでの質問項目削除や追加など
それからは Class Form | Apps Script | Google Developersを眺めていれば何となくやりたいことは実現できるはず。
自分のケースではフォームの記述を更新、質問項目を更新させたかったので、記述はそのままsetで置き換え、質問項目は一度、削除して再作成した。
form.setDescription(description); // 記述の設定
form.deleteAllResponses(); // 回答を全て削除
var items = form.getItems();
for (var i = 0; i < items.length; i++) {
// 質問項目を削除
form.deleteItem(items[i]);
}
// 記述式テキスト(短文回答)
var textItem = form.addTextItem();
textItem.setTitle('タイトル1'); // タイトル
textItem.setRequired(true); // 必須項目
// 記述式テキスト(長文回答)
var paragraphTextItem = form.addParagraphTextItem();
paragraphTextItem.setTitle('タイトル2');
paragraphTextItem.setHelpText('説明文'); // 説明文
私はこのsetHelpText
はてっきりHTMLの placeholder かと思いきや、説明文が追記できるんだね。知らなかった。なので、ケースによっては質問(タイトル)を変えずに説明文だけを変えるようにした。
Google Formでの回答の取得
フォームでのGoogle Apps Scriptなのだから、勿論、回答も取得することができる。以下、参考ページ。
- FormAppによるフォームの集計と作成(5/5):Google Apps Scriptプログラミング [中級編] - libro
- Class FormResponse | Apps Script | Google Developers
良くあるパターンとして 1. にあるとおり、フォームの送信ボタンを押したときに、自動返信も可能。私は、回答結果を集計して身内?に送りたいケースだった。送信毎にメール送信してもいいのだけど、大量のメールを送るのもウザい2ので、ある程度、回答が溜まった時間にトリガーを起動させる形で実装した。
var form = FormApp.getActiveForm();
var description = form.getDescription(); // フォームの記述取得
var formResponses = form.getResponses(); // 回答リストを取得
var list = [];
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses(); // 回答の項目リストを取得
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
var item = itemResponse.getItem();
var answer = '';
// 記述式テキストの場合
if (item.getType() == 'TEXT' || item.getType() == 'PARAGRAPH_TEXT') {
var helpText = item.getHelpText(); // 説明文を取得
answer = itemResponse.getResponse(); // 回答を取得
list.push(helpTxt + ':' + answer);
continue;
}
}
}
var response = '';
for (var k = 0; k < list.length; k++) {
response += list[k] + '\n';
}
console.log("回答: " + response);
GmailApp.sendEmail('test@mailaddress.com', 'メールタイトル', response); // メール送信
みたいな。当たり前にこの通りに実装したわけではないので、文法エラーになるかも知れないし、おかしな処理になってるのは御了解いただきたい。
最後に、結局、使わなかったが、日付フォーマットに便利そうなメソッドUtilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss')
、将来どこかで使うかも。