tag:crieit.net,2005:https://crieit.net/users/massasquash/feed
Massaの投稿 - Crieit
CrieitでユーザーMassaによる最近の投稿
2021-02-10T17:43:56+09:00
https://crieit.net/users/massasquash/feed
tag:crieit.net,2005:PublicArticle/16680
2021-02-10T11:54:19+09:00
2021-02-10T17:43:56+09:00
https://crieit.net/posts/GAS-SpreadSheet-UI
[GAS]SpreadSheetにカスタムメニューを追加して簡単なUIを作る
<h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1>
<p>GASでカスタムメニューを追加するスクリプトについてです。</p>
<p>画像のようにスプレッドシートのメニューバー(ツールバー)の一番右に「カスタムメニュー」という項目が新しく追加され、クリックしたときの処理をカスタマイズすることができます。</p>
<p><a href="https://crieit.now.sh/upload_images/ea884fcaf2dd48cd3c3dc75aca7e67d360234aa61d6f9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ea884fcaf2dd48cd3c3dc75aca7e67d360234aa61d6f9.png?mw=700" alt="スクリーンショット 2021-02-09 23.15.06.png" /></a></p>
<p>今回カスタムメニューを作って実装する内容は「最終行へアクティブセルを移動する」というシンプルなものです。<br />
例えば僕は特定のPC作業をしたときのログをスプレッドシートを作っているのですが、行数が増えてくると一番下までスクロールして入力するのが面倒になります。クリックするだけで一番下に飛べると便利だなーと思って作ってみました。</p>
<h2 id="全体のスクリプト"><a href="#%E5%85%A8%E4%BD%93%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">全体のスクリプト</a></h2>
<p>まずは今回作成するスクリプトの全体像です。</p>
<p>スプレッドシートのコンテナバインドスクリプトを作り、以下のスクリプトを記述します。<br />
その後、スプレッドシートのページをリロードすると、カスタムメニューが反映されるはずです。</p>
<pre><code class="javascript">// 現在のスプレッドシート
const SS = SpreadsheetApp.getActiveSpreadsheet();
// ファイルが開いたときに動作する処理
function onOpen() {
SS.addMenu('カスタムメニュー', [
{name: '最新行へ', functionName: 'moveToLastRow_'}
]);
}
// 最終行にアクティブセルを移動する関数
function moveToLastRow_(){
const sheet = SS.getActiveSheet();
const lastRow = sheet.getLastRow();
const lastRange = sheet.getRange(lastRow + 1, 1);
SpreadsheetApp.setActiveRange(lastRange);
}
</code></pre>
<p>それでは作り方とカスタマイズの仕方を見ていきます。</p>
<h2 id="1. onOpen()関数を作る"><a href="#1.+onOpen%28%29%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%9C%E3%82%8B">1. onOpen()関数を作る</a></h2>
<p>まずは<code>onOpen()</code>という名前で、ファイルが開かれたときに動作する関数を作っておきます。</p>
<pre><code class="javascript">// 現在のスプレッドシート
const SS = SpreadsheetApp.getActiveSpreadsheet();
// ファイルが開いたときに動作する処理
function onOpen() {
// [TODO] カスタムメニューを追加する処理
}
</code></pre>
<p>SpreadSheetオブジェクトの定義も最初にしておきましょう。のちの関数でもこのオブジェクトを使うため、グローバルスコープに記述しています。</p>
<p>このように<code>onOpen()</code>という名前で関数を作ると、ファイルが開いたときに動作する処理を作ることができます。トリガーを設定する必要などありません。<br />
こういった特定のイベントが起こったときに処理を実行する関数を「イベントハンドラ」と呼ぶそうです。</p>
<h2 id="2. メニューの選択肢を追加する"><a href="#2.+%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B">2. メニューの選択肢を追加する</a></h2>
<p>続いてカスタムメニューをクリックした時の選択肢を追加していきます。<br />
スプレッドシートオブジェクトの<code>addMenu()</code>メソッドを使い、先ほどのコードに以下のように記述します。</p>
<pre><code class="javascript">// ファイルが開いたときに動作する処理
function onOpen() {
// カスタムメニューを追加する処理
SS.addMenu('カスタムメニュー', [
{name: '最新行へ', functionName: 'moveToLastRow_'}
]);
}
</code></pre>
<p>この<code>addMenu()</code>の第一引数には、メニューバーに表示させる文字列を書きます。<br />
ここでは「カスタムメニュー」としておきましょう。</p>
<p>第二引数には、追加したいメニューをオブジェクトの配列で書いてあげます。<br />
このオブジェクトに<code>name</code>と<code>functionName</code>というプロパティを持たせることで、メニューに表記する文字列と、選択されたときに実行する関数名を指定することができます。</p>
<pre><code class="javascript">{name: '<メニューに表記する文字列>', functionName: '<関数名>'}
</code></pre>
<p>ここで指定する関数は後ほど作るので、現段階では実装予定の名前で大丈夫です。<br />
ここでは<code>moveToLastRow_</code>という関数名を指定しておきました。</p>
<p>注意点としては、関数名には丸かっこをつけずに書くことです。<br />
もし丸カッコをつけてしまうと、その関数が実行されて戻り値が<code>functionName</code>の値に設定されてしまうことになります。今回は関数名を文字列として指定したいので、丸かっこをつけないで文字列リテラルで書くのが正解になります。</p>
<p>この<code>addMenu()</code>の第二引数に指定するオブジェクトを増やしていくことで、どんどんメニューを追加することもできます(後ほど備考として記載します)。</p>
<h2 id="3. メニューを選択したときに動作する関数を作る"><a href="#3.+%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%92%E9%81%B8%E6%8A%9E%E3%81%97%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E5%8B%95%E4%BD%9C%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%9C%E3%82%8B">3. メニューを選択したときに動作する関数を作る</a></h2>
<p>最後に、先ほど関数名に指定した<code>moveToLastRow_()</code>関数を新たに定義すればOKです。<br />
ここでは現在のシートの「最終行の次の行の1列目」にアクティブセルを移動させています。</p>
<pre><code class="javascript">// 最終行にアクティブセルを移動する関数
function moveToLastRow_(){
const sheet = SS.getActiveSheet();
const lastRow = sheet.getLastRow();
const lastRange = sheet.getRange(lastRow + 1, 1);
SpreadsheetApp.setActiveRange(lastRange);
}
</code></pre>
<p><strong>アクティブセルの移動先を指定したい場合</strong><br />
もし移動させるセルを変更したい場合は、上のコードの5行目の<code>getRange()</code>に渡す引数を変えてあげましょう。</p>
<pre><code class="javascript">const lastRange = sheet.getRange(lastRow + 1, 1);
</code></pre>
<p>例えば、現在のシートの「最終行の3列目」などとしたい場合は、次のようにカスタマイズすればOKです。</p>
<pre><code class="javascript">const lastRange = sheet.getRange(lastRow, 3);
</code></pre>
<p>以上で実装は完了です。<br />
スクリプトを保存して、スプレッドシートを開いているページをリロードして少し待つと、カスタムメニューが追加されているかと思います。</p>
<h2 id="備考"><a href="#%E5%82%99%E8%80%83">備考</a></h2>
<p><strong>カスタムメニューの選択肢を増やしたい場合</strong><br />
<code>onOpen()</code>に書いた<code>SS.addMenu()</code>の中身のオブジェクトを増やしてみましょう。</p>
<pre><code class="javascript">function onOpen() {
// カスタムメニューを追加する処理
SS.addMenu('カスタムメニュー', [
{name: '最新行へ', functionName: 'moveToLastRow_'},
{name: 'マスタ転記', functionName: 'copyFromMaster_'},
{name: 'ほげほげ', functionName: 'hogehoge'}
]);
}
</code></pre>
<p>配列の要素としてオブジェクトを追加していく形になるので、注意してくださいね。<br />
構文エラー(SyntaxError)が出てしまう場合は、オブジェクトの書き方やカンマの有無など要チェックです。</p>
<p><strong>スプレッドシートを開いたときに最終行に移動させたい場合</strong><br />
今回はカスタムメニューを使って最終行へ移動するスクリプトを書きましたが、ファイルを開いたときにアクティブセルを移動させるやり方でも良いかもしれません。<br />
そうしたい場合は、<code>onOpen()</code>に直接今回の<code>moveToLastRow_()</code>の中身の処理を書いてあげればOKです。<br />
ただし、セル数にもよりますが起動が重くなってしまうかもしれないので、ご注意ください。</p>
<h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1>
<p>カスタムメニューは、スプレッドシートを使っていて「ボタンをクリックしたら一括で処理するようにしたい」というような場合にシンプルなUIを作ることができて、とても便利です。<br />
他にもスプレッドシートの関数で転記できるような作業でも、GASのカスタムメニューを使った実装をすることでシートを軽くしたりもできるので、よく活用しています。</p>
<p><strong>その他の実装アイデア</strong><br />
- カスタムメニューを実行すると、マスターデータから必要な情報を探して一括で転記する()<br />
- シートでメール送信先リストを作り、カスタムメニューをクリックするとメールが一括送信されるようにする</p>
Massa
tag:crieit.net,2005:PublicArticle/16373
2020-12-16T15:24:50+09:00
2020-12-24T10:21:10+09:00
https://crieit.net/posts/GAS-Python
農家だけど色々作ったので振り返る2020
<p>こちらの記事は <a href="https://crieit.net/">Crieit</a> のアドベントカレンダー <a href="https://crieit.net/advent-calendars/2020/crieit">なんでも</a> の15日目の記事として、書かせていただきます。<br />
(日付を間違えていて一日ずれての投稿になってしまったのは秘密です...)</p>
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>今年2020年は、本業の農業のかたわら色々なツール制作に取り組んできました。<br />
仕事に活きるもの、生活に活きるもの、プログラミング学習に活きるもの、また作ったけど活かせなかったものなどなど。言語は主にGASとPython。<br />
この機会に、ノンプログラマーがプログラミングを学んで作ったものを振り返って整理しておきたいと思います。</p>
<p>昨年からプログラミングをやっていて、今年の2月ごろにGASでちょっとしたプログラムで身近なことが便利になる、というのを知ってから、だいぶプログラミングが楽しくなったのを覚えています。</p>
<h2 id="制作したもの"><a href="#%E5%88%B6%E4%BD%9C%E3%81%97%E3%81%9F%E3%82%82%E3%81%AE">制作したもの</a></h2>
<h3 id="GASでのツール制作"><a href="#GAS%E3%81%A7%E3%81%AE%E3%83%84%E3%83%BC%E3%83%AB%E5%88%B6%E4%BD%9C">GASでのツール制作</a></h3>
<h4 id="(1)LINEbot:ゴミ出しリマインダ"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89LINEbot%EF%BC%9A%E3%82%B4%E3%83%9F%E5%87%BA%E3%81%97%E3%83%AA%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%80">(1)LINEbot:ゴミ出しリマインダ</a></h4>
<p>練習のために作ってみた、家族で共有するためのbotです。燃えるゴミ・燃えないゴミなどの収集日の前日の夜にリマインダを飛ばしてくれる、というだけのもの。<a target="_blank" rel="nofollow noopener" href="https://developers.line.biz/ja/services/messaging-api/">Messaging API</a>を使って実装し、放置していて今も動いています。</p>
<p><a href="https://crieit.net/posts/GAS-LINE">【GAS】家族で使えるゴミ出しリマインダ LINEチャットボットを作った - Crieit</a></p>
<h4 id="(2)LINEbot:農作業記録アシスタント"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89LINEbot%EF%BC%9A%E8%BE%B2%E4%BD%9C%E6%A5%AD%E8%A8%98%E9%8C%B2%E3%82%A2%E3%82%B7%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%88">(2)LINEbot:農作業記録アシスタント</a></h4>
<p>こちらは2月ごろにかなり力を入れて制作した、LINE上で対話形式で作業日報を入力していくツールです。入力した内容はGoogleCalendarに登録されます。<br />
<a target="_blank" rel="nofollow noopener" href="https://developers.line.biz/ja/services/messaging-api/">Messaging API</a>を使いラインのトーク画面に表示できるリッチメニューの実装を学習。<br />
LINEはITリテラシーが高くない一般の方々にも有用なツールだと実感します。</p>
<p>ただ、このbotを興味持ってくれた人数名にテストユーザーになってもらうためセッティングしたのですが、使用のハードルはやはり高そうだなーという印象でした。そもそも「日誌はアナログで取っており基本的に満足している」というところに刺さっていくのは、素人の思いつきでは厳しいものがあるんだろうな、と思います。ツール制作にこだわらず、別のアプローチが必要です。</p>
<p>個人的にもこちらは現在運用はしておらず、農作業の記録は<a target="_blank" rel="nofollow noopener" href="https://www.agri-note.jp/">アグリノート</a>という既存のサービスを活用しています。</p>
<p><a href="https://crieit.net/posts/GAS-LINE-bot">GASで「農作業記録アシスタント」LINE bot作った(紹介編) - Crieit</a><br />
<a href="https://crieit.net/posts/GAS-LINE-bot-5e59f5af2ba2b">GASで「農作業記録アシスタント」LINE bot作った(技術・設計編) - Crieit</a></p>
<p><a href="https://crieit.now.sh/upload_images/1903de9c56e588a3690fd8581790b2bc5fd9a8f14222f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1903de9c56e588a3690fd8581790b2bc5fd9a8f14222f.png?mw=700" alt="704d5676a24bb5091ddb305676ac996f5e55e34d74b65.png" /></a></p>
<h4 id="(3)Gmail添付ファイル保存ツール"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89Gmail%E6%B7%BB%E4%BB%98%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BF%9D%E5%AD%98%E3%83%84%E3%83%BC%E3%83%AB">(3)Gmail添付ファイル保存ツール</a></h4>
<p>業務資料を整理するため制作。農協からの情報の多くはFAXで送られてきますが、その内容がpdfファイルでメール添付でGmailに添付されて送られます。その添付ファイルをDriveに保存する作業を自動化しようと思って着手。<br />
4月に主にネット情報をもとにコピペで制作し、バグが出て放置していたものを、現在は言語を学び直しながら改善中です。Driveにファイル名によりフォルダ分けして格納し、個人で使ってるSlackへ通知とリンクが送られるようにもしてみました。</p>
<p><a href="https://crieit.now.sh/upload_images/7e391def0f93fbc5e90f01dfd1adde8a5fd9a91111134.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7e391def0f93fbc5e90f01dfd1adde8a5fd9a91111134.png?mw=700" alt="スクリーンショット 2020-12-16 14.49.08.png" /></a></p>
<h3 id="Pythonでのツール制作"><a href="#Python%E3%81%A7%E3%81%AE%E3%83%84%E3%83%BC%E3%83%AB%E5%88%B6%E4%BD%9C">Pythonでのツール制作</a></h3>
<h4 id="(1)LINEbot:共同作業グループ用の作業時間記録・集計ツール"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89LINEbot%EF%BC%9A%E5%85%B1%E5%90%8C%E4%BD%9C%E6%A5%AD%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E7%94%A8%E3%81%AE%E4%BD%9C%E6%A5%AD%E6%99%82%E9%96%93%E8%A8%98%E9%8C%B2%E3%83%BB%E9%9B%86%E8%A8%88%E3%83%84%E3%83%BC%E3%83%AB">(1)LINEbot:共同作業グループ用の作業時間記録・集計ツール</a></h4>
<p>7月に制作。初めて他者に使ってもらって良い評価をもらった、満足度の高いツールです。<br />
自分の所属している地域の共同作業グループで使うための入力支援ツールを<a target="_blank" rel="nofollow noopener" href="https://developers.line.biz/ja/services/messaging-api/">Messaging API</a>を使って作りました(LINE大好きすぎる)。</p>
<p>サーバーとして<a target="_blank" rel="nofollow noopener" href="https://jp.heroku.com/free">Heroku</a>を利用(正しい表現かちょっとわからない)。入力データはDB代わりにSpreadsheetと連携しています。シート操作のライブラリはgspreadを使いました。クラウドサーバにデプロイ時のライブラリ依存周りの設定、Spreadsheet設定でOAuth認証情報をクラウドサーバの環境変数に入れるというような部分がなかなか難しかった記憶です(そもそも環境変数とは、の段階からではありました)</p>
<p>現場では作業した人が、LINEで作業開始・終了の時間をタップだけで入力できて、後から時給計算する際もLINEで集計結果が返ってくるようになっています。これまで紙で管理して電卓で集計していたのが、相当楽になりました。<br />
グループでの作業は年に1回、収穫時期が来れば必ず必要になる作業なので、LINE botも毎年改善しながら運用を続けていきます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://note.com/agrifeel_labo/n/nf391d0b8e477">業務で活用するLINEbot制作にあたって意識した事|Massa|note</a></p>
<h4 id="(2)卸売市場価格を取得するためのスクレイピングツール"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E5%8D%B8%E5%A3%B2%E5%B8%82%E5%A0%B4%E4%BE%A1%E6%A0%BC%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0%E3%83%84%E3%83%BC%E3%83%AB">(2)卸売市場価格を取得するためのスクレイピングツール</a></h4>
<p><a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/community-nonpro-semi/">ノンプロ研</a>で受講したPython講座きっかけで、Seleniumを使ったWebサイトのスクレイピングに取り組みました。作っている農産物の価格動向を毎日見られるようにしたいなと思いました。</p>
<p>7月、まずは初めはpdfのダウンロードをしてDropboxへ入れるだけのツールを制作しましたが、ダウンロードしっぱなしで全然活用できず。<br />
次に11月には収集する対象を市況データのまとまった別のWebサイトに変更し、その日の欲しい市況データをSpreadsheetに入れる & 自分のSlackへ送信、というもの。</p>
<p>定期実行には、Windowsのバッチファイルとタスクスケジューラを使用してローカルで実行する方法、クラウドのHerokuに上げて<a target="_blank" rel="nofollow noopener" href="https://devcenter.heroku.com/articles/scheduler">Heroku Scheduler</a> を使用する方法を試しました。ローカルの方がセッティングは楽ですが、PC起動時にしかスクリプトが動かせないというデメリットがあります(やりようはあるのかな?)。クラウドはデプロイができれば任せっきりで楽ですが、HerokuでSeleniumを動かす設定に一手間かかりました。一長一短で、習得コストも考慮して運用方法は考えなければなあと学習。</p>
<p>こちらは今後、Slackに送られてくるデータをもっと見やすいように工夫したいです。</p>
<p><a href="https://crieit.now.sh/upload_images/60d1eba48a433808894d7fe3c013737c5fd99fba5487e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/60d1eba48a433808894d7fe3c013737c5fd99fba5487e.png?mw=700" alt="スクリーンショット 2020-12-16 14.48.28.png" /></a></p>
<h4 id="(3)楽天市場の価格調査"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E6%A5%BD%E5%A4%A9%E5%B8%82%E5%A0%B4%E3%81%AE%E4%BE%A1%E6%A0%BC%E8%AA%BF%E6%9F%BB">(3)楽天市場の価格調査</a></h4>
<p>卸売価格からのアプローチだけでなく、直販価格からのアプローチもしてみたくて取り組み。<br />
9月は統計に興味が向いていて、Pandasの扱い方を覚え、データの収集と加工、価格分析の基礎的なところをやってみました。</p>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://webservice.rakuten.co.jp/api/ichibaitemsearch/">楽天商品検索API</a>で特定のキーワードに合致する商品情報を取得する</li>
<li>取得したデータを使える形に加工して分析する</li>
</ul>
<p>統計学的な難しいことはわからないのですが、最終的に平均値・中央値などの簡単な統計量を算出し、価格が重量に線型近似していることを確かめ、平均的なキロ単価を算出することができました(正確では無いかと思いますが、参考程度には十分な数値だと思います)。</p>
<p>現場の人間がデータを扱うには、ExcelやSpreadsheetなどの表計算ソフトの扱いに慣れているかどうかが大事だなと思いました。「データ分析=Python, Pandas」みたいなイメージを持っていましたが、それよりも「何を目的としてやるか」があり、そのために「どのツール(手段)でやるか」という考え方で、習得コストのかからないやり方を選ぶのも大事なんだろうなと思います。</p>
<p>こちらは<a target="_blank" rel="nofollow noopener" href="https://m-ohsaki.stores.jp/">農産物のネットショップ</a>開設のための価格調査に直結したので、ひとまず成果につながった分析だったかなあと思っています。</p>
<p><a href="https://crieit.net/posts/Python-IT-API-csv">Python農家のITマーケティング試行錯誤日記1.楽天商品検索APIを使って商品情報をcsv出力する - Crieit</a><br />
<a href="https://crieit.net/posts/IT-2-Python">Python農家のITマーケティング試行錯誤日記2.商品情報の収集とデータ加工を行う - Crieit</a><br />
<a href="https://crieit.net/posts/IT-3-Python">Python農家のITマーケティング試行錯誤日記3.商品価格の集計と分析を行う - Crieit</a></p>
<p>⬇️商品「メークイン」の箱の重量(kg)と価格の中央値(円)の関係<br />
<a href="https://crieit.now.sh/upload_images/af1e6345e0af0b9d1c933d8d366b6e3a5fd9aa406ef6c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/af1e6345e0af0b9d1c933d8d366b6e3a5fd9aa406ef6c.png?mw=700" alt="スクリーンショット 2020-12-16 15.33.02.png" /></a></p>
<p>⬇️近似曲線から商品「メークイン」のキロ単価を計算<br />
<a href="https://crieit.now.sh/upload_images/30bc7229b89779b2024968f1159d5be35fd9aa38451a7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/30bc7229b89779b2024968f1159d5be35fd9aa38451a7.png?mw=700" alt="スクリーンショット 2020-12-16 15.32.23.png" /></a></p>
<h4 id="(4) GUI付きテキストファイル作成ツール"><a href="#%EF%BC%88%EF%BC%94%EF%BC%89+GUI%E4%BB%98%E3%81%8D%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BD%9C%E6%88%90%E3%83%84%E3%83%BC%E3%83%AB">(4) GUI付きテキストファイル作成ツール</a></h4>
<p>自分用に開発作業のログを残すのを便利にするために作りました。Python標準ライブラリの<code>tkinter</code>を使ってGUIを作って、プルダウンを選びボタンを押すと選んだ内容に応じたテンプレートが入ったテキストファイルが作成される、という仕組みです。またちょっとマニアックなこともトライしていて、同じく標準ライブラリの<code>subprocess</code>を使って、ボタンを押すとGithubへプッシュするコマンドを実行するようなコマンド操作も学習しました。<br />
作成したPythonスクリプトはpyファイルにしてデスクトップに置き、ダブルクリックで実行できるようにしています。</p>
<p>こちらは現在も運用中です。</p>
<p><a href="https://crieit.now.sh/upload_images/8f9d86893218bd481d69e41113c1a2755fd9a142a9cfe.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8f9d86893218bd481d69e41113c1a2755fd9a142a9cfe.png?mw=700" alt="スクリーンショット 2020-12-16 14.53.27.png" /></a></p>
<h4 id="(5)PDF文字抽出ツール/自炊用PDF結合・加工ツール"><a href="#%EF%BC%88%EF%BC%95%EF%BC%89PDF%E6%96%87%E5%AD%97%E6%8A%BD%E5%87%BA%E3%83%84%E3%83%BC%E3%83%AB%EF%BC%8F%E8%87%AA%E7%82%8A%E7%94%A8PDF%E7%B5%90%E5%90%88%E3%83%BB%E5%8A%A0%E5%B7%A5%E3%83%84%E3%83%BC%E3%83%AB">(5)PDF文字抽出ツール/自炊用PDF結合・加工ツール</a></h4>
<p>PythonでPDFを扱う<code>PyPDF2</code>、文字抽出には<code>pdfminer</code>というライブラリを使用。講座のPDFテキストを特定の文字があるページだけ画像化する…ということをしています。いくつかPDFファイルを触ってみたのですが、PDFファイルの文字を扱うのは一般的には難しくて、自分で作ったファイルなら良いけど、既存のPDFを扱うのはあまり考えない方が良いかな…と思いました。<br />
また最近書籍の自炊をしているとPDF結合や分割をしたくなったので、短いコードを作ってJupyter Notebook上で実行できるような、ちょっとした便利コード集みたいなものも、スキャン中の時間を生かして作りました。笑</p>
<p>慣れるとこういうちょっとしたツールがすぐにできる、というのがPythonの良いところだなあ…。<br />
PDF操作に便利なコード集も整理して記事にしておきたいです。</p>
<p><a href="https://crieit.net/posts/PDF-Python">PythonPDFから特定の文字列を抽出&ページを画像として保存するツール - Crieit</a></p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>アウトプットとしてはこんな感じでしょうか。かなり作ったんだなあ…。<br />
こうやって振り返ってみると、設計時点の反省点が明確に見えてきたり、「もう少しこう改善したいというのも整理されて良いなと思いました。</p>
<p>制作の動機が自分自身のために向いているものが多く、勉強ために作ったようなものもあります。<br />
別に悪いことでないのですが、どうしても「作る」のが好きでそこに拘ってしまうことがあり、おかげで機能がコテコテになってしまったり、メンテナンス性の悪いスクリプトになっている気がします。</p>
<p>一方で振り返って良い点としては、LINEやSlackで情報を受け取るような明確な「アウトプット」が見られるツールの作り方を習得できたことです。外作業が多いと「スマホで見られる」というのも重要に感じます。</p>
<h3 id="今後作っていきたいもの"><a href="#%E4%BB%8A%E5%BE%8C%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%84%E3%81%8D%E3%81%9F%E3%81%84%E3%82%82%E3%81%AE">今後作っていきたいもの</a></h3>
<p>必ずしもツール制作だけじゃなくて既存のデータの「整理」というのがキーワードなのですが、ざっくり</p>
<ul>
<li>農場データの整理(クラウドの利用)</li>
<li>既存のサービスの活用(農作業日誌、温度etc....csvファイルの活用や部分的な自動化・可視化)</li>
<li>現場で使えるツール(どこを改善したいか?誰のため?)</li>
</ul>
<p>技術的にも興味のあるものにもっと取り組んでいきたいのですが、来年はまたもうちょっと広い視点で農業経営に役に立つ・農村地域や人の役に立つツールを作れたらな、と思います。<br />
IoTを活用してデータを蓄積できるような環境も作ってみたいです。</p>
<p>勉強の幅がプログラミング以外にも広くて、どこに集中して勉強していけば良いか…。楽しく頑張ります。</p>
Massa
tag:crieit.net,2005:PublicArticle/16340
2020-12-11T23:10:59+09:00
2020-12-12T10:26:24+09:00
https://crieit.net/posts/GAS-Javascript-n-d
[GAS][Javascript]指定の年月の「第n d曜日」を配列で取得する
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>GASでのゴミ出しリマインダ用に「第n d曜日」を計算するアルゴリズムを作りました。<br />
面白かったので、まとめておきます。</p>
<p>考え方としては、</p>
<ul>
<li>(1)指定した年月の「d曜日」をすべて配列で取得する</li>
<li>(2)その配列から「第n d曜日」だけを抜き出して配列で取得する</li>
</ul>
<p>という順番で実装していきます。</p>
<p>特にDate型の扱いと、配列メソッドの使い方が勉強になりました。<br />
配列メソッドは、配列に要素として含まれているかどうかをブール値で返す<code>includes()</code>メソッドと、配列の要素のうち引数に指定した関数をみたすものだけを配列として取り出す<code>filter()</code>メソッドあたりの理解がミソになるかと思います。</p>
<p>このあたりの練習問題としても、やってみたい方は是非おすすめです!</p>
<h2 id="(1)指定した年月の「d曜日」をすべて配列で取得する"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E5%B9%B4%E6%9C%88%E3%81%AE%E3%80%8Cd%E6%9B%9C%E6%97%A5%E3%80%8D%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E9%85%8D%E5%88%97%E3%81%A7%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">(1)指定した年月の「d曜日」をすべて配列で取得する</a></h2>
<p>まずは、指定した年月の指定した曜日の一覧を配列で取得してみたいと思います。<br />
例えば「今月の水曜日」と指定してやると、2020年12月の水曜日に該当する</p>
<pre><code>第1水曜 2020/12/2
第2水曜 2020/12/9
第3水曜 2020/12/16
第4水曜 2020/12/23
第5水曜 2020/12/30
</code></pre>
<p>これらをDate型の配列という形で作ってみよう、という訳です</p>
<p>「変数<code>date</code> = 今日の日付(Date型)」と「変数<code>day</code> = 取得したい曜日(数値)」を指定して、目的の配列を作ってみたいと思います。</p>
<p>GAS(Javascript)のDate型では、0が日曜日、1が月曜日…、6が土曜日に対応しています。</p>
<p><code>const WEEKDAYS = ['日', '月', '火', '水', '木', '金', '土'];</code><br />
今回は必要ありませんが、こんな定数を用意しておくと、今後なんか活用できる機会があるかも?<br />
「あれ、0は日曜だっけ?月曜だっけ?」という備忘のためにスクリプト内に記述しておいても良いかもしれません。</p>
<p>今回は「水曜日」が欲しいので、<code>day = 3</code>としてみます。</p>
<p>スクリプトは以下の通りです。</p>
<pre><code class="javascript">function mainFunction() {
const date = new Date(); //今日の日付
const day = 3; //水曜日
const year = date.getFullYear();
const month = date.getMonth();
const days = [];
for (let i = 1; i <= 31; i++){
const tmpDate = new Date(year, month, i);
if (month !== tmpDate.getMonth()) break; //月代わりで処理終了
if (tmpDate.getDay() !== day) continue; //引数に指定した曜日以外の時は何もしない
days.push(tmpDate);
}
console.log(days);
}
</code></pre>
<p>上の<code>getDays()</code>関数を実行してログを確認してみると、「2020年12月の水曜日」を配列で得ることができます。</p>
<pre><code>[ Wed Dec 02 2020 00:00:00 GMT+0900 (日本標準時),
Wed Dec 09 2020 00:00:00 GMT+0900 (日本標準時),
Wed Dec 16 2020 00:00:00 GMT+0900 (日本標準時),
Wed Dec 23 2020 00:00:00 GMT+0900 (日本標準時),
Wed Dec 30 2020 00:00:00 GMT+0900 (日本標準時) ]
</code></pre>
<p>こちらの記事を全力でパクらせていただきましたw<br />
<a target="_blank" rel="nofollow noopener" href="https://etauthenonprogrammercoder.tumblr.com/post/186638513489/gas%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9C%88%E3%81%AE%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9B%9C%E6%97%A5%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">etau the non programmer coder — GAS特定の月の特定の曜日を取得する</a></p>
<h3 id="関数に切り分ける"><a href="#%E9%96%A2%E6%95%B0%E3%81%AB%E5%88%87%E3%82%8A%E5%88%86%E3%81%91%E3%82%8B">関数に切り分ける</a></h3>
<p>こんな感じで<code>getDays()</code>関数に切り分けて、再利用しやすいようにしてみました。<br />
<code>mainFunction()</code>内で日付と曜日を指定して実行すると、同様の結果が得られます。</p>
<pre><code class="javascript">function mainFunction(){
const date = new Date();
const day = 3; //水曜日
const days = getDays(date, day);
console.log(days);
}
/*
*その年月のd曜日を取得する関数
*
* @param {Date} date - 調べたい年月の日付(Date型)
* @param {number} day - 取得したい曜日を表す数値(0:日曜日〜6:土曜日)
* @return {days} ある年月のday曜日の日付の入った配列
*/
function getDays(date, day) {
const year = date.getFullYear();
const month = date.getMonth();
const days = [];
for (let i = 1; i <= 31; i++){
const tmpDate = new Date(year, month, i);
if (month !== tmpDate.getMonth()) break; //月代わりで処理終了
if (tmpDate.getDay() !== day) continue; //引数に指定した曜日以外の時は何もしない
days.push(tmpDate);
}
return days;
}
</code></pre>
<h2 id="(2)その配列から「第n d曜日」だけを抜き出して配列で取得する"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E3%81%9D%E3%81%AE%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E3%80%8C%E7%AC%ACn+d%E6%9B%9C%E6%97%A5%E3%80%8D%E3%81%A0%E3%81%91%E3%82%92%E6%8A%9C%E3%81%8D%E5%87%BA%E3%81%97%E3%81%A6%E9%85%8D%E5%88%97%E3%81%A7%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">(2)その配列から「第n d曜日」だけを抜き出して配列で取得する</a></h2>
<p>いくつかアプローチが考えられます。</p>
<p>・先ほど作った「d曜日」を返す<code>getDays()</code>関数を加工して「第n d曜日」の配列を返す関数を作る<br />
・メイン関数<code>mainFunction()</code>の中で配列<code>days</code>を加工して「第n d曜日」の配列を作る</p>
<p>後者のやり方でシンプルにできます。<br />
新たに変数<code>weeks</code>を定義して、配列で欲しい週を指定してあげます。<br />
(隔週に限らず、欲しい番号を指定してみましょう)</p>
<pre><code class="javascript">function mainFunction2(){
const date = new Date();
const day = 3; //水曜日
const weeks = [2, 4]; //第2週目、第4週目 を指定
const days = getDays(date, day);
const specifiedDays = days.filter((v, i) => weeks.includes(i+1));
console.log(specifiedDays);
}
</code></pre>
<p>スクリプトの中身については、後ほど文法の説明とともに(ちゃんと説明できてるかどうかわからない)解説を載せておきます。<br />
この関数を実行してログを表示すると、指定した第2・第4水曜日だけの配列ができています。やったね!</p>
<pre><code>[ Wed Dec 09 2020 00:00:00 GMT+0900 (日本標準時),
Wed Dec 23 2020 00:00:00 GMT+0900 (日本標準時) ]
</code></pre>
<h3 id="備考"><a href="#%E5%82%99%E8%80%83">備考</a></h3>
<p><code>weeks</code>に0, 10などのあり得ない数値を入れてやっても、特に不具合が出ることなく、該当する数値のみをとった配列が出来上がるようです。<br />
これを利用すると、例えば<code>weeks = [1, 3, 5]</code>などと指定しておけば、5週目のあるなしに関わらず欲しい配列が手に入りますね。</p>
<p>また、この「第n d曜日」を配列にする処理も他の関数に切り分けて作ってはみましたが、あまりその関数を頻繁に使う機会が思い浮かびませんでした。。。<br />
なので、今回作ってみたスクリプトとしてはここまで。</p>
<h3 id="解説:配列のincludes()とfilter()メソッドの活用"><a href="#%E8%A7%A3%E8%AA%AC%EF%BC%9A%E9%85%8D%E5%88%97%E3%81%AEincludes%28%29%E3%81%A8filter%28%29%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E6%B4%BB%E7%94%A8">解説:配列のincludes()とfilter()メソッドの活用</a></h3>
<p>GAS(Javascript)の文法について。知っていたら飛ばしてください。</p>
<p>ポイントはこの1行。</p>
<pre><code class="javascript">const specifiedDays = days.filter((v, i) => weeks.includes(i+1));
</code></pre>
<p>ちょっとよくわかりませんね。</p>
<p>先に、今回使う配列のメソッドの最低限の使い方だけ整理しておきます。</p>
<div class="table-responsive"><table>
<thead>
<tr>
<th>メソッド</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>arr.filter()</td>
<td>配列arrの要素のうち、引数に指定したをみたすものだけを抽出した配列として返す</td>
</tr>
<tr>
<td>arr.includes()</td>
<td>引数に指定したが配列arrに含まれているかどうかをブール値で返す</td>
</tr>
</tbody>
</table></div>
<p>さて...先ほどの1行は、全体の構文としては<br />
<code>const specifiedDays = days.filter(なんかの関数);</code></p>
<p>という構造になっています。<br />
この構造だけみると実はシンプルで、<code>specifiedDays</code>という配列に、先ほど作った指定した曜日が全部入った配列<code>days</code>から<code>filter()</code>メソッドで「なんらかの処理」をして要素を抽出した配列を返していることになります。</p>
<p>この「なんらかの処理」というのが、<code>filter()</code>メソッドの引数に指定した関数です。</p>
<p><code>(v, i) => weeks.includes(i+1)</code></p>
<p>こいつは「アロー関数」という関数リテラルの書き方ですね。<br />
矢印の左側が仮引数、右側が具体的な処理です。<br />
(仮引数v, iはvalue, indexに相当します)</p>
<p>ざっくり「<code>weeks</code>で指定した数値がインデックス担っているものだけ、<code>days</code>から要素を抽出している」というような処理になっています。</p>
<p>今回の場合はこれにより第2、第4曜日だけを<code>days</code>から抽出して、<code>specifiedDays</code>を作っています。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>飲み会から派生したネタでした。<br />
実用的かつ気になっていた配列のメソッドを扱う、とても良い練習になりました。<br />
メソッドの解説のあたりはだいぶ飛ばしてしまっているので、もう少しうまく説明できないかな…。思いついたら修正してみます。</p>
<h3 id="MEMO"><a href="#MEMO">MEMO</a></h3>
<p>参考記事<br />
<a target="_blank" rel="nofollow noopener" href="https://etauthenonprogrammercoder.tumblr.com/post/186638513489/gas%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9C%88%E3%81%AE%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9B%9C%E6%97%A5%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">etau the non programmer coder — GAS特定の月の特定の曜日を取得する</a></p>
<p>参考書籍(pp202-213 配列を取り扱う - Arrayオブジェクト)<br />
<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/gp/product/4798063339/ref=dbs_a_def_rwt_hsch_vapi_taft_p1_i4">Amazon.co.jp: 詳解! Google Apps Script完全入門第2版 ~GoogleアプリケーションとGoogle Workspaceの最新プログラミングガイド: 高橋宣成: 本</a></p>
Massa
tag:crieit.net,2005:PublicArticle/16073
2020-09-26T18:32:22+09:00
2020-12-11T23:17:39+09:00
https://crieit.net/posts/PDF-Python
[Python]PDFから特定の文字列を抽出&ページを画像として保存するツール
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>「<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/community-nonpro-semi/">ノンプロ研</a>」というコミュニティで開催されている講座で使用する目的で作りました。<br />
毎回講座の資料が講師の方から共有してもらえるので、それを演習問題のページだけを抜き出して画像保存するツールです。</p>
<p>PDF資料から特徴のあるページだけを抽出するような作業を行いたい場合は、抽出キーワードを変えると使えるかもしれません。</p>
<p>ただどうも文字抽出が上手くできるかどうかは元のPDFファイルによるものが多く、必ずしもできる訳ではなさそうなので注意が必要です。</p>
<h2 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h2>
<ul>
<li>必要なライブラリのインストールをしておいてください(次の項目参照)。</li>
<li>準備したい講座回数(<code>NO</code>)、資料PDFファイルの名(<code>FILES</code>)とフォルダパス(<code>PDF_DIRPATH</code>)、画像出力先フォルダのパス(<code>OUTPUT_DIRPATH</code>)をスクリプトに記入してください</li>
<li>スクリプトを実行して少し待つと、該当する講座の資料から演習ページだけ画像の保存が行われます(pngファイル)</li>
<li>同時に演習をテキストで抜き出したものがテキストファイルで保存されます</li>
</ul>
<p><a href="https://crieit.now.sh/upload_images/495dde582b6e0fa537533d7f10134da85f6efce5cdd8f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/495dde582b6e0fa537533d7f10134da85f6efce5cdd8f.png?mw=700" alt="image" /></a></p>
<h2 id="使用しているPythonライブラリについて"><a href="#%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%81%84%E3%82%8BPython%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">使用しているPythonライブラリについて</a></h2>
<ul>
<li>PDFから文字列を抽出する<code>pdfminer.six</code></li>
<li>PDFを画像保存する<code>pdf2image</code></li>
</ul>
<p>これらを使っています。<br />
また<code>pdf2image</code> を使うには、依存関係のある<code>pillow</code>と<code>Poppler</code>というライブラリも必要のようです。</p>
<h2 id="スクリプトの解説"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E8%A7%A3%E8%AA%AC">スクリプトの解説</a></h2>
<h3 id="(1)ライブラリのインポート"><a href="#%EF%BC%881%EF%BC%89%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88">(1)ライブラリのインポート</a></h3>
<pre><code class="python">import pathlib
import re
from io import StringIO
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
import pdf2image
</code></pre>
<h3 id="(2)ユーザー入力項目"><a href="#%EF%BC%882%EF%BC%89%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E5%85%A5%E5%8A%9B%E9%A0%85%E7%9B%AE">(2)ユーザー入力項目</a></h3>
<p>このツールを使用する前に、変換したいPDFファイルやディレクトリをユーザー自身で指定します。<br />
ユーザーが入力する部分は、グローバルな定数としてまとめてあります。<br />
(パス取得のスクリプトがまだやや冗長な気がしています)</p>
<pre><code class="python"># ユーザー入力項目
# 準備したい講座回数・PDFファイル名をリストに記入
NO = 3
FILES = [
'20200923初心者講座Pythonコース-01.pdf',
'20200930初心者講座Pythonコース-02.pdf',
'20201007初心者講座Pythonコース-03.pdf',
]
# 資料PDFファイルが保存されているフォルダのパスを記入
PDF_DIRPATH = pathlib.Path(
'<pdfファイルのあるフォルダのパス>'
)
PDF_FILEPATH = PDF_DIRPATH / FILES[NO-1]
# 出力先フォルダのパス
OUTPUT_DIRPATH = pathlib.Path(
'<変換した画像を保存したいフォルダのパス>'
)
# 抽出する文字列(正規表現:ここでは「演習○-○」)
SEARCH_TEXT = r'演習\s*\d-\d+'
</code></pre>
<p>Python標準ライブラリの<code>pathlib</code>を使って、パスを記述しています。<br />
<code>pathlib.Path(<パス文字列>)</code>とすることで、Pathオブジェクトを作成しています。</p>
<p>このPathオブジェクトを活用すると、パス同士を<code>/</code>記号を使って結合することができます。<br />
ちなみにこのスクリプトでは<code>PDF_DIRPATH</code>がPathオブジェクト・<code>PDF_DIRPATH</code>はstr型のオブジェクトですが、このように一方がPathオブジェクトなら、文字列が混ざっていても<code>/</code>でパスとして結合することもできるようですね。</p>
<h4 id="(参考)pathlibモジュールとosモジュール"><a href="#%28%E5%8F%82%E8%80%83%29pathlib%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A8os%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB">(参考)pathlibモジュールとosモジュール</a></h4>
<p>どちらもPython標準ライブラリです。<br />
Python3.4以前ではファイルパス操作に<code>os.path</code>や<code>glob</code>モジュールを使っていたようですが、<br />
Python3.4で<code>pathlib</code>モジュールが追加されてからは、こちらを使う方がシンプルに書きやすくなったようです。</p>
<h3 id="(3)正規表現にマッチするページとテキストを抽出する関数"><a href="#%EF%BC%883%EF%BC%89%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE%E3%81%AB%E3%83%9E%E3%83%83%E3%83%81%E3%81%99%E3%82%8B%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%A8%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E6%8A%BD%E5%87%BA%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0">(3)正規表現にマッチするページとテキストを抽出する関数</a></h3>
<p>PDFファイルから、(2)で指定した「抽出する文字列」(ここでは演習問題)に該当するページを、ページ数とテキストとして抽出する関数を作ります。<br />
ここで画像からテキスト抽出できる<code>pdfminer.six</code>ライブラリを活用しています。</p>
<p>長い関数ですが、戻り値としては<code>output_texts</code>変数で、これは「キーにページ番号、値に演習問題ナンバーとテキストが格納されたdict」となっています。</p>
<pre><code class="python">def get_text_from_pdf():
"""PDFファイルから演習問題に該当するテキストをページ数と共に抽出する関数
Returns:
output_texts(dict): 演習問題一覧。ページ数をキーとして、演習ナンバーと本文が入った辞書
"""
# pdfminerの設定
rsrcmgr = PDFResourceManager()
codec = 'utf-8'
laparams = LAParams()
laparams.detext_vertical=True
# PDFファイルを1ページずつ見て該当するかチェック
output_texts = {} # 該当するページ数とテキストを格納するdict
with open(PDF_FILEPATH, 'rb') as fp:
for i, page in enumerate(PDFPage.get_pages(fp)):
outfp = StringIO()
device = TextConverter(
rsrcmgr,
outfp,
codec=codec,
laparams=laparams
)
interpreter = PDFPageInterpreter(rsrcmgr, device)
interpreter.process_page(page)
extracted_text = outfp.getvalue() \
.replace('\u2028', '') \
.split('Copyright')[0] \
.rstrip()
# ページ抽出:抽出条件(演習問題のページ)に該当すればTrue
extracted_page = re.search(SEARCH_TEXT, extracted_text)
# 演習問題のページ番号・演習番号・テキストを格納したdict作成
if extracted_page:
output_texts[i+1] = (extracted_page.group(), extracted_text)
return output_texts
</code></pre>
<p>処理としては<br />
「PDFファイルを1ページずつ見ていって、<code>SEARCH_TEXT</code>に一致するページが出てきたら、ページ番号とテキストを変数<code>output_texts</code>に格納する」<br />
というような処理を行っています。</p>
<h4 id="PDFMinerについて"><a href="#PDFMiner%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">PDFMinerについて</a></h4>
<p>仕組みやスクリプトをを完璧に理解しきれていませんが、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/wagahaiCAT/items/63489b6ec6c0c3559886">こちらの記事</a>を参考にさせていただきました。<br />
出来上がったスクリプトでは、for文の外と中にそれぞれ設定項目を分けて記述しています(かえってわかりづらくしてしまっているかもしれません)。</p>
<p>PDFファイルから文章を抽出する処理の流れは結構複雑で、そのまま抽出することはできないようです。<br />
PDFファイルからPDFParserを使ってPDFDocumentという形式に一旦変換して、そこからPDFInterpreter, PDFDeviceを使って翻訳し、テキストとして出力する、という流れかと思います。この後半の過程で、PDFResouceManagerにフォントやイメージなどのリソース情報を保管しています。<br />
(間違ってたらすみません)</p>
<p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mczkzk/items/894110558fb890c930b5">こちらの記事</a>に公式ドキュメントから引用された図解があるので、公式ドキュメントと合わせて参考にさせていただきました。</p>
<h3 id="(4)抽出したページのテキストをファイルに保存する関数"><a href="#%EF%BC%884%EF%BC%89%E6%8A%BD%E5%87%BA%E3%81%97%E3%81%9F%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0">(4)抽出したページのテキストをファイルに保存する関数</a></h3>
<p>(3)で作った<code>output_texts</code>をそのままテキストファイルに出力する関数です。<br />
ただdict型の変数をそのまま出力しているだけなので、かなり見づらいテキストファイルになっています。<br />
整形して出力するようにしたいところ。</p>
<pre><code class="python">def save_text(output_texts):
"""演習問題一覧をテキストファイル出力
Arg:
output_texts(dict): 演習問題一覧。ページ数をキーとして、演習ナンバーと本文が入った辞書
"""
output_text_path = OUTPUT_DIRPATH / f'0{NO}_ensyu.txt'
with output_text_path.open('w') as f:
print(output_texts, file=f)
</code></pre>
<p><code>print()</code>で<code>file</code>引数にここではテキストファイルのパスを指定することで、出力先をファイルに指定しています。</p>
<h3 id="(5)抽出したページの画像を保存する関数"><a href="#%EF%BC%88%EF%BC%95%EF%BC%89%E6%8A%BD%E5%87%BA%E3%81%97%E3%81%9F%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E7%94%BB%E5%83%8F%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0">(5)抽出したページの画像を保存する関数</a></h3>
<p>PDFファイルを1ページずつ見ていって、抽出したいページだけを画像ファイルに保存する関数です。<br />
ここで<code>pdf2image</code>ライブラリを使用しています<br />
(3)で作った<code>output_texts</code>(dict型でした)に抽出したいページ数がキーとして入っているので、これを用いて抽出したいページかどうかを判定しています。</p>
<pre><code class="python">def save_images(output_texts):
"""演習問題を画像ファイル出力
Arg:
output_texts(dict): 演習問題一覧。ページ数をキーとして、演習ナンバーと本文が入った辞書
"""
# - convert_from_path関数には1ページごとの画像のリストが入る
# - mkidr(exist_ok=True)にすると上書きされるっぽい
img_dirpath = OUTPUT_DIRPATH / f'0{NO}'
pathlib.Path(img_dirpath).mkdir(exist_ok=True)
images = pdf2image.convert_from_path(PDF_FILEPATH, size=1280)
for i, image in enumerate(images):
if i+1 in contents:
filename = contents[i+1][0].replace(' ', '')
image_filepath = img_dirpath / f'{filename}.png'
image.save(image_filepath)
</code></pre>
<h4 id="pdf2imageについて"><a href="#pdf2image%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">pdf2imageについて</a></h4>
<p>扱い方は簡単で、<code>pdf2image.convert_from_path()</code>の引数にPDFファイルパスを指定してやれば良さそうです。<br />
ここでは<code>size</code>引数でサイズを指定してみました。他の引数を使えば、pngファイルだけでなくjpgファイルなどファイル形式を指定することもできるようです。</p>
<h3 id="(6)実行"><a href="#%EF%BC%88%EF%BC%96%EF%BC%89%E5%AE%9F%E8%A1%8C">(6)実行</a></h3>
<p>以下で実行します。<br />
PDFファイルのページ数によっては、少し時間がかかってしまいます。</p>
<pre><code class="python"># 実行
contents = get_text_from_pdf()
save_text(contents)
save_images(contents)
</code></pre>
<h2 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h2>
<p>pdf2imageについて<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kikuyan8540/items/35751c573de014df205b">PythonでPDFをまとめて画像に変換する - Qiita</a></p>
<p>PDFMinerについて<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/wagahaiCAT/items/63489b6ec6c0c3559886">【Python】PDFの文章をページ毎にCSVに変換(2/24追記) - Qiita</a><br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/mczkzk/items/894110558fb890c930b5">【PDFMiner】PDFからテキストの抽出 - Qiita</a></p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>PDFを操作するのをやってみたくて、用途はないけど色々調べてたのが役に立ちました。<br />
今回はシンプルなPDFファイルだったのでまだ良かったですが、複雑なレイアウトのPDFファイルを扱いたい場合はもっと泥臭い作業になるのかな…。</p>
<p>ライブラリの使い方はあまり理解してないので、ちゃんと整理しておこうと思います。</p>
<p>そして今回このツール制作を通じての課題。<br />
<strong>使い勝手が良くメンテナンス性の高いスクリプトにするにはどうすれば良いんだろう?</strong> というところに興味があります。<br />
サーバーにアップして誰でも使えるツールを作る…というのはちょっと難しく労力が大きいので、ローカルで実行すればすぐに使えるような形を意識してみました。</p>
<p>ユーザーが入力するべきところ、変更の可能性があるところ(抽出する文字列)を定数としてグローバルスコープに書いているのですが、もっとスマートなやり方があるのかな、と思っています。<br />
関数の使い方についても学んでいきたいです。</p>
<h2 id="MEMO:スクリプト全文"><a href="#MEMO%3A%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E5%85%A8%E6%96%87">MEMO:スクリプト全文</a></h2>
<p>スクリプト全文はこちらに載せました!<br />
<a target="_blank" rel="nofollow noopener" href="https://github.com/Massasquash/mytools/blob/master/20200925_pdf_to_pic/pdf_to_pic.py">mytools/pdf_to_pic.py at master · Massasquash/mytools · GitHub</a></p>
Massa
tag:crieit.net,2005:PublicArticle/16067
2020-09-23T18:54:15+09:00
2020-12-11T23:17:08+09:00
https://crieit.net/posts/IT-3-Python
[Python]農家のITマーケティング試行錯誤日記3.商品価格の集計と分析を行う
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p><a href="https://crieit.net/posts/IT-2-Python">前回</a>の続きです。今回がラストになります。<br />
楽天市場から取得した商品データの価格分析をするための前処理をしたので、今回はいよいよ簡単な分析にトライします。</p>
<ul>
<li>楽天市場で販売されている野菜(今回はメークイン)の価格相場はどのくらいなのか?</li>
<li>1kgあたりの金額はいくらが妥当なのか?</li>
</ul>
<p>といったことを、初歩的な分析スキルを使って出してみたいと思います。</p>
<h2 id="方針"><a href="#%E6%96%B9%E9%87%9D">方針</a></h2>
<p>目的を改めて整理すると「農産物を販売するにあたっての価格調査のため」です。<br />
農産物価格についてkgごとに集計したりグラフ化してみたりすることで、「どのくらいの価格で販売するのが妥当なのか」をある程度の根拠を持って得たいな、というのが今回の目的でした。<br />
そのためにまず調べやすそうな楽天市場での相場を調べることで、この根拠の一つにしてみよう、という考えです。</p>
<p>以下のような手順で見ていきます。</p>
<h3 id="(1)集計してみる"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E9%9B%86%E8%A8%88%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">(1)集計してみる</a></h3>
<p>まずは入数ごとの商品価格の要約統計量(平均値、中央値など)を集計し、表にして見てみます。<br />
つまり、重さ(1kg, 2kg…)ごとの商品価格を具体的な数値で把握してみることで、今回の分析の第一歩とします。</p>
<h3 id="(2)分布を確認してみる(可視化)"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E5%88%86%E5%B8%83%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B%EF%BC%88%E5%8F%AF%E8%A6%96%E5%8C%96%EF%BC%89">(2)分布を確認してみる(可視化)</a></h3>
<p>入数ごとのデータを使ってヒストグラムや散布図を描いてみて、入数と商品件数や、入数と商品価格の関係がどのような分布になるかを見てみることにします。<br />
また、商品価格の中央値に絞って可視化を行ってみることで、次の(3)の分析に繋げます。</p>
<h3 id="(3)線形近似してパラメータを求める"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E7%B7%9A%E5%BD%A2%E8%BF%91%E4%BC%BC%E3%81%97%E3%81%A6%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B">(3)線形近似してパラメータを求める</a></h3>
<p>入数と価格の中央値の関係を1次関数(直線)と見立ててその式のパラメータを求めてみます。<br />
先ほど入数ごとの価格の中央値に着目して可視化したことで「入数と商品価格は1次関数の関係(グラフ上で直線)になりそう」という推測が生まれます。これは実際に当てはめると、商品価格は「送料や箱代・作業代などのベースとなる金額」に「1kgあたりの金額」が加算されて商品代が決められる、と考えることができます。<br />
関係式のパラメータを求めることで、その「ベースとなる金額」と「1kgあたりの金額」を算出してみます。</p>
<h2 id="集計と分析"><a href="#%E9%9B%86%E8%A8%88%E3%81%A8%E5%88%86%E6%9E%90">集計と分析</a></h2>
<p>集計に入る前に、データの準備をしておきます。<br />
<a href="https://crieit.net/posts/IT-2-Python">前回</a>の最後に保存した加工済みのデータをcsvファイルから読み込んでDataFrameに入れておきます。</p>
<pre><code class="python">df = pd.read_csv('20200914_rakuten_mayqueen_2.csv')
</code></pre>
<h3 id="(1)集計してみる"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E9%9B%86%E8%A8%88%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">(1)集計してみる</a></h3>
<p>準備ができたらまずは、入数ごとの商品価格の要約統計量を調べてみます。<br />
入数ごとの表にするために<code>groupby()</code>関数を使います。<br />
また、統計量の表示には<code>describe()</code>メソッドを使います。</p>
<pre><code class="python"># 入数ごとの統計量をgroupbyで調べる
df_by_kg = df.groupby('入数').describe()['商品価格']
df_by_kg
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/22790d6c7a5ae2a746f675e427ab3ef75f6a170cb3cce.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/22790d6c7a5ae2a746f675e427ab3ef75f6a170cb3cce.png?mw=700" alt="image" /></a></p>
<p>この表の<code>count</code>列を見てみると、1件〜2件しかデータがない行と、データ件数が多い行がそれぞれあるのがわかります。<br />
例えば「じゃがいも1kgや2kgでの販売」はそもそも件数が少なく、「じゃがいも5kgや10kgでの販売」は件数が多い、ということがわかります。<br />
箱の規格は大体決まっているので実際の現場からすると納得の数値なのですが、このようにデータからも確認することができました。</p>
<p>この表から、必要な行だけ抽出しておきます。今回は件数が多い行のみを活用することにします。<br />
残す行の入数の数値を直接指定しても良いのですが、後々スクリプトを再利用する可能性も考えて、ここでは5件以上ある行(3kg, 5kg, 10kg, 20kgが該当)を抜き出してみることにします</p>
<pre><code class="python"># 必要な行だけ抜き出す(件数が5つ以上あるものだけ残す)
df_by_kg = df_by_kg.loc[df_by_weight[('商品価格', 'count')]>5, :]
df_by_kg
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/89504b4eabe580fe947ef58ace0e420e5f6a195573fe8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/89504b4eabe580fe947ef58ace0e420e5f6a195573fe8.png?mw=700" alt="image" /></a></p>
<p>さらにここから、列も必要なものだけを残します。<br />
今回は比較したい列として、「件数<code>count</code>」「平均値<code>mean</code>」「最小値<code>min</code>」「中央値<code>50%</code>」「最大値<code>max</code>」を残すことにしました。</p>
<pre><code class="python"># 必要な列だけ抜き出す
df_by_kg = df_by_kg.loc[:, [ 'count', 'mean', 'min', '50%', 'max']]
df_by_kg
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/c50f6d296153e9aa4f58e462891aa78c5f6a1a0d1a1eb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c50f6d296153e9aa4f58e462891aa78c5f6a1a0d1a1eb.png?mw=700" alt="image" /></a></p>
<p>これで、スッキリした集計結果の表が得られました。<br />
この表を元に、例として「じゃがいも10kg」の商品価格を見てみます。その統計量は、</p>
<ul>
<li>最小値(<code>min</code>) 1,399円</li>
<li>最大値(<code>max</code>) 7,560円</li>
<li>平均値(<code>mean</code>) 3,820円</li>
<li>中央値(<code>50%</code>) 3,600円</li>
</ul>
<p>というような金額(送料・税込)になっていました。<br />
相場感を掴むためには平均値か中央値を参考にすれば良さそうですが、最小値と最大値の幅が大きいことから全体のバラつきが大きそうだと考えられます。そのため、参考にする数値としては平均値ではなく<strong>中央値</strong>を見ていこう、と考えます(このバラつきは次の項目で可視化して見てみます)。</p>
<p>ちなみに最安値のものが異様に安いので気になってみてみると、いわゆる「訳あり品」として超小玉のものが販売されているようでした。逆に最高値を見てみると特別高級なブランド品という訳でもなく一地域の商品だったのですが、2020年の6月〜8月上旬頃まではじゃがいもの相場が全国的に異様に高かったため、このような金額で出品されているのかなと思いました(あくまで推測です)。</p>
<h3 id="(2)分布を確認してみる(可視化)"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E5%88%86%E5%B8%83%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B%EF%BC%88%E5%8F%AF%E8%A6%96%E5%8C%96%EF%BC%89">(2)分布を確認してみる(可視化)</a></h3>
<p>次は、データをもとにグラフを描いて可視化することで、よりデータを深くみていこうと思います。</p>
<p>まずは「ヒストグラム」を表示してみます。<br />
ここでは入数(kg)と度数(いくつのデータがあるか)を図示します。</p>
<p>ヒストグラムを表示するには、<code>matplotlib</code>というライブラリの<code>hist()</code>関数を用います。<br />
データとして集計前のDataFram(<code>df</code>)を指定します。<br />
オプションに<code>bins</code>を指定することで、ヒストグラムの帯の幅を変えています。ここでは1から30までの1刻みで件数を図示しています。</p>
<pre><code class="python">import matplotlib.pyplot as plt
# ヒストグラム(入数と件数の関係)
plt.hist(df['入数'], bins=np.arange(1, 30))
plt.xlabel('Quantity(kg)')
plt.ylabel('Count')
plt.grid(True)
plt.show()
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/b61556ef5e043764adcc0ddf429638ec5f6a1b9caa4e1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b61556ef5e043764adcc0ddf429638ec5f6a1b9caa4e1.png?mw=700" alt="image" /></a></p>
<p>先ほど数値で見たことの確認になりますが、入数kgによって件数に差がありますね。5kg、10kgあたりの商品が特に多く、3kgや20kgも比較的出ているというのがわかります。</p>
<p>次に、「散布図」を表示してみます。<br />
散布図を表示するには、<code>matplotlib</code>の<code>scatter()</code>関数を用います。<br />
これも集計前のDataFram(<code>df</code>)を指定してみます。</p>
<pre><code class="python"># 散布図(入数と商品価格の関係)
plt.scatter(df['入数'], df['商品価格'])
plt.xlabel('Quantity(kg)')
plt.ylabel('Pirce(yen)')
plt.grid(True)
plt.show()
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/fe59440662f4b1c0c74f24e689ccc4a35f6a1bd479884.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fe59440662f4b1c0c74f24e689ccc4a35f6a1bd479884.png?mw=700" alt="image" /></a></p>
<p>何kgの商品がいくらで販売されているのかが点で表示されますが、ここで先ほど集計した時に触れた「価格のバラつき」が大きいことが目に見えてわかります。<br />
この状態だと、バラつきが大きくて値決めの判断材料にはできなさそうです。</p>
<p>そこで、今度は商品価格の「中央値」に絞ってプロット図を描いてみます。<br />
先ほどの散布図と違って、入数ごとに対応する商品価格の中央値となる1点がグラフ上にプロットされます。<br />
ここでは集計後のDataFrame(<code>df_by_kg</code>)を使って図示します。</p>
<pre><code class="python"># 点をプロット(入数と商品価格(中央値)の関係)
plt.plot(df_by_kg.index, df_by_kg['50%'], 'o')
plt.xlabel('Quantity(kg)')
plt.ylabel('Pirce(yen)')
plt.grid(True)
plt.show()
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/ad77690da113f9a9075115694cd200c75f6a1c2b93588.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ad77690da113f9a9075115694cd200c75f6a1c2b93588.png?mw=700" alt="image" /></a></p>
<p>プロットした点が、綺麗な直線上に並んでるように見えます。<br />
なんだか入数と商品価格の関係を掴むことができそうです。</p>
<p>それぞれの点における数値(つまり中央値)は以下の通り。</p>
<p><a href="https://crieit.now.sh/upload_images/955c9e30d9ba9618d1bfb05c33c273885f6a1c4718834.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/955c9e30d9ba9618d1bfb05c33c273885f6a1c4718834.png?mw=700" alt="image" /></a></p>
<p>野菜の販売金額を設定する際には、このぐらいの金額に設定することで、ネット販売における相場とのズレが少ない妥当な値決めができそうです。<br />
<br />
<br />
このように、数値を見るだけでなく可視化すると直感的にわかりやすくなります。<br />
また可視化するにしても、漠然と全体を見るだけだとバラつきが多くてどう見て良いのかよくわからなかったものが、「中央値」という1つの統計量に絞って見てみると規則性を見ることができました。</p>
<h3 id="(3)線形近似してパラメータを求める"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E7%B7%9A%E5%BD%A2%E8%BF%91%E4%BC%BC%E3%81%97%E3%81%A6%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B">(3)線形近似してパラメータを求める</a></h3>
<p>もう少し、先ほどの結果を深掘りしてみることにします。<br />
先ほどプロットした入数kgに対する商品価格の中央値の値は、1次関数の関係に近い(グラフ上で点がほぼ直線上に並んでいる)と考えられます。<br />
そこで「線形近似」というのを行ってみます。<br />
近似した直線の式のパラメータ(傾きと切片)を求めることで、商品価格を詳細に見ることができそうです。<br />
つまり、「1kgあたりの金額」はこの直線の傾きの値に該当し、「送料や箱代・作業代などのベースとなる金額」はこの直線の切片に該当します。</p>
<p>線形近似にはnumpyの機能を使います。</p>
<pre><code class="python"># 線形近似
linear = np.polyfit(df_by_kg.index, df_by_kg['50%'], 1) # 線形近似して切片と傾きを求める
func = np.poly1d(linear) # 切片と傾きから1次式を作る
x = df_by_kg.index
y = func(x)
# 線形近似したグラフ表示
plt.plot(x, y)
# 散布図も合わせて表示
plt.plot(df_by_kg.index, df_by_kg['50%'], 'o')
plt.xlabel('kg')
plt.ylabel('yen')
</code></pre>
<p><a href="https://crieit.now.sh/upload_images/cfec2cf3ec2fef757b97492df98485bd5f6a1f7b19d61.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cfec2cf3ec2fef757b97492df98485bd5f6a1f7b19d61.png?mw=700" alt="image" /></a></p>
<p>このように、プロットした点がだいたい直線に乗っていることが確認できるかと思います。<br />
そしてこの<code>linear</code>変数に、この直線の式のパラメータが入っています。</p>
<pre><code class="python">print('直線のパラメータ[傾き, 切片] = ', linear)
# -> 直線のパラメータ[傾き, 切片] = [ 142.94797688 2071.99421965]
</code></pre>
<p>つまり、</p>
<ul>
<li>ベースとなる金額(切片に該当)約 2072円</li>
<li>1kgあたり金額(傾きに該当) 約143円</li>
</ul>
<p>この数値を使うと、例えば4kgの商品を作るとしたら、2072円 + 4kg@143円 = 2,644円 くらいで設定すると良いんじゃないか、と計算することができます。</p>
<p>商品価格の中央値が、ここまで綺麗に一次関数の式に近似できたのが面白かったです。<br />
今回使用したデータ数は分析するには少なかったと思うのですが、それでも感覚的に納得できる数値を得ることができました。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>今回のような基礎的な分析でも、活用できそうな根拠のある数値を得ることができました。<br />
このような分析結果をもとに意思決定に繫げるのが、仕事でデータ分析を活用するにあたっての肝になるのかな、と思います。<br />
そしてそれは1回で終えるのではなく、トライ&エラーを重ねて、より正しく説得力のある分析と意思決定を行うサイクルを回していくことが大事になってくるようにも思います。</p>
<p>ひとつ大事な観点として、分析結果の妥当性はもうちょっと検証する必要はありそうです。<br />
例えばデータ数をもっと増やしたり、データの中身を精査する必要があります。今回の例では、品種によって細分化したらもっと性格なデータを出せるなあと思いました。</p>
<p>データ分析したこの後は実際の価格決めという意思決定のフェーズに入っていくのですが、それにはまた別の視点が必要です。<br />
少なくとも以下のことは、自身の中で固めておく必要があるなあと思います。</p>
<ul>
<li>商品の価値をどう設定するのか。相場より安く設定するのか高く設定するのか。</li>
<li>ターゲットは誰なのか。今回楽天市場のデータで分析したが、よりターゲットに近い市場での分析が必要かもしれない。</li>
</ul>
<p>もうちょっとスピード感を持って意思決定できれば良いのでしょうけど、難しい。</p>
<p>ひとまず今回の数値を使って、近々ひとつアウトプットを出すようにしたいと思います!</p>
<p>さて、三回に渡ってデータ分析に関する記事を続けてきましたが、今回のテーマは一旦ここまでにします。<br />
学んだ知識をもとに探り探りの分析だったため、もしかしたら考え方が間違っていたりよりスマートな記述の仕方があるかもしれません。<br />
ここまで読んでいただいて、もし気になる点があればコメントいただけると大変嬉しいです!</p>
<h2 id="(記事リンク)農家のITマーケティング試行錯誤日記"><a href="#%EF%BC%88%E8%A8%98%E4%BA%8B%E3%83%AA%E3%83%B3%E3%82%AF%EF%BC%89%E8%BE%B2%E5%AE%B6%E3%81%AEIT%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E6%97%A5%E8%A8%98">(記事リンク)農家のITマーケティング試行錯誤日記</a></h2>
<p><a href="https://crieit.net/posts/Python-IT-API-csv">農家のITマーケティング試行錯誤日記1.楽天商品検索APIを使って商品情報をcsv出力する[Python]</a><br />
<a href="https://crieit.net/posts/IT-2-Python">農家のITマーケティング試行錯誤日記2.商品情報の収集とデータ加工を行う[Python]</a><br />
<a href="https://crieit.net/ぽsts/IT-3-Python">農家のITマーケティング試行錯誤日記3.商品価格の集計と分析を行う[Python]</a></p>
Massa
tag:crieit.net,2005:PublicArticle/16058
2020-09-15T20:15:55+09:00
2020-12-11T23:18:09+09:00
https://crieit.net/posts/IT-2-Python
[Python]農家のITマーケティング試行錯誤日記2.商品情報の収集とデータ加工を行う
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p><a href="https://crieit.net/posts/Python-IT-API-csv">前回</a>は「楽天商品検索API」を使って30件(1ページ分)のデータを抽出し、csvファイルに保存するまでを行いました。</p>
<p>今回はより実用的にするために、まず前回のスクリプトを発展させて取得するデータ件数を増やしたのちに、集計や分析の前段階として、商品データを商品重量ごと(5kg, 10kg...)に分類できるようなデータ加工まで行います。</p>
<h2 id="方針"><a href="#%E6%96%B9%E9%87%9D">方針</a></h2>
<h3 id="(1)商品情報を取得する"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E5%95%86%E5%93%81%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">(1)商品情報を取得する</a></h3>
<ul>
<li>検索キーワード「メークイン」商品情報を300件(30件×10ページ)取得する(NGワードも指定する)</li>
<li>必要な商品情報のみを入れたdict型を作成する</li>
<li>PandasのDataFrameに格納し、列を整理する</li>
</ul>
<p>前回と流れは同じですが、今回は取得件数とNGワードをここで指定しています。<br />
これらはデータを集める目的や、必要な情報量と中身に応じて調整します。</p>
<p>NGワードは僕の場合は、自分の想定と合わず商品価格に影響してきそうなワードを指定しました。ここでは「有機栽培/オーガニック/減農薬/農薬不使用」「セット/詰め合わせ」「ふるさと納税」を除きます。<br />
取得件数を300件としたのは特に深い意味はありませんが、今回のキーワード・NGワードで商品検索をかけたところ320件程度のヒット数だったため、キリの良い数字を選びました。</p>
<h3 id="(2)データ加工の手順"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E3%83%87%E3%83%BC%E3%82%BF%E5%8A%A0%E5%B7%A5%E3%81%AE%E6%89%8B%E9%A0%86">(2)データ加工の手順</a></h3>
<ul>
<li>収集したデータから必要なデータのみを抽出する</li>
<li>「商品名」から重さ(5kg, 10kg...)を抽出し、新たな「入数」列を作る</li>
</ul>
<p>本来は野菜の品種ごとにまで分けた方が正確な金額が出せるのですが、今回はそこまではしないことにしました。あまり細分化しすぎるとデータ数が減ってしまうためです。</p>
<h2 id="(1)商品情報を取得するスクリプト"><a href="#%EF%BC%881%EF%BC%89%E5%95%86%E5%93%81%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">(1)商品情報を取得するスクリプト</a></h2>
<h3 id="1.必要なライブラリのインポートと入力パラメータの準備"><a href="#1.%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E3%81%A8%E5%85%A5%E5%8A%9B%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%AE%E6%BA%96%E5%82%99">1.必要なライブラリのインポートと入力パラメータの準備</a></h3>
<p>ここは前回とほぼ変わりありませんが、APIでリクエストを送る入力パラメータの<code>page</code>と<code>NGKeyword</code>、そして<code>postageFlag</code>の部分のみ変えています。</p>
<p>楽天商品検索APIの1回のリクエストで取得できるデータの上限は30件(入力パラメータの<code>hits</code>の値)となっています。<code>page</code>にページ数<code>2</code>を指定してやれば、31件目以降のデータが取得できます。<br />
この<code>page</code>の値を<code>for</code>文で回すことを想定しています。</p>
<p>また、入力パラメータの<code>postageFlag</code>は商品価格に送料を含むかどうかを指定しています(1で送料含むor送料無料)</p>
<pre><code class="python">import requests
import numpy as np
import pandas as pd
REQUEST_URL = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706"
APP_ID="<楽天APIのIDを入れる>"
# 入力パラメータ
serch_keyword = 'メークイン'
ng_keyword = 'ふるさと納税 有機 オーガニック 減農薬 農薬不使用 セット 詰め合わせ'
page = 1
serch_params={
"format" : "json",
"keyword" : serch_keyword,
"NGKeyword":ng_keyword,
"applicationId" : [APP_ID],
"availability" : 0,
"hits" : 30,
"page" : page,
"sort" : "standard",
"postageFlag" : 1
}
</code></pre>
<h3 id="2.必要な商品情報のみを入れたdict型を作成する"><a href="#2.%E5%BF%85%E8%A6%81%E3%81%AA%E5%95%86%E5%93%81%E6%83%85%E5%A0%B1%E3%81%AE%E3%81%BF%E3%82%92%E5%85%A5%E3%82%8C%E3%81%9Fdict%E5%9E%8B%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">2.必要な商品情報のみを入れたdict型を作成する</a></h3>
<p><code>for</code>文を使って複数にまたがるページの商品情報を取得していきます。</p>
<p>商品情報から必要な項目のみ抜き出して<code>tmp_item</code>という名前のdictに格納し、それを<code>item_list</code>という名前のリストに格納しているのは、前回と同様の流れになります。</p>
<pre><code class="python"># 商品情報をリストで取得
item_list = [] # 30件ずつ取得した辞書型の商品情報tmp_itemが10ページ分入る
max_page = 10
for page in range(1, max_page+1):
serch_params['page'] = page
# APIにリクエストを送り、結果として商品データresultを得る
response = requests.get(REQUEST_URL, serch_params)
result = response.json()
# resultから必要な情報を抜き出したdictを作る
item_key = ['itemName', 'itemPrice', 'itemCaption', 'shopName', 'shopUrl', 'itemUrl']
for i in range(0, len(result['Items'])):
tmp_item = {}
item = result['Items'][i]['Item']
for key, value in item.items():
if key in item_key:
tmp_item[key] = value
item_list.append(tmp_item.copy())
</code></pre>
<p>ここで、仮に商品件数が10ページ分に満たないようなキーワードを入れた場合でも、特にエラーは出ずに取得することができました。ページ数は実際の商品数よりも大きく設定しても良さそうです(APIのリファレンスによると<code>page</code>の上限は100)。<br />
ですがその分処理に時間がかかり、自分の環境だと稀にエラーが出てしまう現象が起きたので、必要最小限に留めた方が良いのかなと思います。</p>
<h3 id="3.DataFrameを作成する"><a href="#3.DataFrame%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">3.DataFrameを作成する</a></h3>
<p>これも前回と同様に、先ほどの商品情報のdictが格納されたlistからPandasのDataFrameを作成します。<br />
少し修正して、インデックスを0からではなく1から順番に振ってくれるようにしました。</p>
<pre><code class="python"># データフレーム作成
df = pd.DataFrame(item_list)
df = df.reindex(columns=['itemName', 'itemPrice', 'itemCaption', 'itemUrl', 'shopName', 'shopUrl'])
df.columns = ['商品名', '商品価格', '商品説明文', '商品URL', '店舗名', '店舗URL']
df.index = df.index + 1 # インデックスを1からに振り直す
</code></pre>
<p><code>df.count()</code> で取得件数、<code>df.head()</code>で先頭5件のデータを確認。<br />
想定どおりの300件のデータが入っていれば大丈夫そうです。</p>
<h3 id="4.csv出力"><a href="#4.csv%E5%87%BA%E5%8A%9B">4.csv出力</a></h3>
<p>毎回このスクリプトを走らせてデータを取得するのも面倒なので、使いたい時に使えるようにcsv出力しておきます。</p>
<pre><code class="python">df.to_csv('20200914_rakuten_mayqueen.csv')
</code></pre>
<h2 id="(2)データ加工・集計のスクリプト"><a href="#%282%29%E3%83%87%E3%83%BC%E3%82%BF%E5%8A%A0%E5%B7%A5%E3%83%BB%E9%9B%86%E8%A8%88%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">(2)データ加工・集計のスクリプト</a></h2>
<h3 id="1.csv読み込み"><a href="#1.csv%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF">1.csv読み込み</a></h3>
<p>データの件数を増やすことができたので、次はそのデータを加工して分析に適した形にしてみます。<br />
このまま進めても良いですが、いったん先ほどのcsvフィルを読み込んでみてから進めます。</p>
<pre><code class="python">df = pd.read_csv('20200914_rakuten_mayqueen.csv')
</code></pre>
<h3 id="2.必要なデータのみを抽出する"><a href="#2.%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E3%81%BF%E3%82%92%E6%8A%BD%E5%87%BA%E3%81%99%E3%82%8B">2.必要なデータのみを抽出する</a></h3>
<p>このDataFrameを加工していくのですが、まずはざっとデータの中身をspread sheetで眺めてみると、今回の価格調査の目的には当てはまらない、野菜以外の余計なデータが混じっていました。<br />
今回必要なのは重量のデータなので「商品名に重量が入っている商品データのみを残す」という方針でやってみます。</p>
<p>Pandasで、指定した文字列を含む(部分一致)行を抽出してbool値で返す<code>str.contains()</code>を使います。</p>
<pre><code class="python"># 商品名に「kg」が入っている商品データのみ残す
kg_flg = df['商品名'].str.contains('kg')
df = df[kg_flg]
</code></pre>
<p><code>kg_flg</code>はbool値のSeriesとなっていて、「kg」が含まれている行にはTrue,そうでない行にはFalseが入ります。Trueになっている行が必要な残したいデータです。<br />
これを用いて<code>df[kg_flg]</code>とすることで、Trueの行だけデータの入ったDataFrameを抽出することができます。</p>
<p><code>df.count()</code>で件数を確認すると、116件とだいぶ減ってしまいました。<br />
よりデータ数を確保するなら、この辺りはもうちょっと検証する必要がありそうです。</p>
<h3 id="3.商品名から重量を抽出して新たな列を作る"><a href="#3.%E5%95%86%E5%93%81%E5%90%8D%E3%81%8B%E3%82%89%E9%87%8D%E9%87%8F%E3%82%92%E6%8A%BD%E5%87%BA%E3%81%97%E3%81%A6%E6%96%B0%E3%81%9F%E3%81%AA%E5%88%97%E3%82%92%E4%BD%9C%E3%82%8B">3.商品名から重量を抽出して新たな列を作る</a></h3>
<p>これで商品名にkgが含まれている行のみが残りましたが、さらにこの重量を別の列に切り出したいです。<br />
商品名には「数字 + kg」という形で重量が入っているはずなので、これの数値を取り出して「入数」という列を新しく作ります。</p>
<p>ここでは正規表現を使ってみます(詳細な説明は省きますが、<code>([0-9]+)kg</code>とすることで「数字 + kg」を表現できます)<br />
この正規表現をPandasの<code>str.extract()</code>の引数に指定します。このメソッドは引数に正規表現を指定すると、それに最初にマッチした文字列を抽出して新たな列を作る、というものです。まさに今回やりたいことにぴったりな便利メソッドです。</p>
<pre><code class="python"># 商品名から重量を別カラムに切り出す
df['入数'] = df['商品名'].str.extract('([0-9]+)kg')
df =df.reindex(columns=['商品名', '入数', '商品価格', '商品説明文', '商品URL', '店舗名', '店舗URL'])
df.to_csv('20200914_rakuten_mayqueen_2.csv')
</code></pre>
<p>見やすいように列の入れ替えも行い、最後にcsv出力をしておきます。<br />
こんな感じでDataFrameを作ることができました。</p>
<p><a href="https://crieit.now.sh/upload_images/3a9a287d043e84762270b380660926425f609fb1250c2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3a9a287d043e84762270b380660926425f609fb1250c2.png?mw=700" alt="image" /></a></p>
<p>画像では、この後の分析のために入数を数値型に変換したのちに、商品価格を入数で割返したkg単価も付け加えてみました。ですが結構バラバラの数値が出ているので、ここは次回もうちょっと掘り下げてみます。</p>
<p>ともあれ、これで商品価格の分析のための下準備が終わったのではないでしょうか。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>分析のためには出来るだけ多いデータを集めた方が偏りが少ないと思うのですが、このやり方だと結構ざっくりと商品件数を削っており、もうちょっと上手いやり方がありそうだなと思いました。<br />
また、残ったデータも完璧なものと言えるのか定かではないので、細かいデータ分析をするにはまだ検証しなければいけないことが多いと思います。</p>
<p>まあ今回は個人的にやっていることですし、まずはアウトプットできることを目指して、ある程度の納得感があれば良いなーくらいに思って、細かい部分にはこだわらず進めています。</p>
<p><a href="https://crieit.net/drafts/IT-3-Python">次回</a>、データを集計して商品価格の平均値などの統計量を算出してみたり、可視化や線形近似を行ってみてkg単価を算出してみたりと、簡単な分析を行ってみようと思います。</p>
<h2 id="(記事リンク)農家のITマーケティング試行錯誤日記"><a href="#%EF%BC%88%E8%A8%98%E4%BA%8B%E3%83%AA%E3%83%B3%E3%82%AF%EF%BC%89%E8%BE%B2%E5%AE%B6%E3%81%AEIT%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E6%97%A5%E8%A8%98">(記事リンク)農家のITマーケティング試行錯誤日記</a></h2>
<p><a href="https://crieit.net/posts/Python-IT-API-csv">農家のITマーケティング試行錯誤日記1.楽天商品検索APIを使って商品情報をcsv出力する[Python]</a><br />
<a href="https://crieit.net/posts/IT-2-Python">農家のITマーケティング試行錯誤日記2.商品情報の収集とデータ加工を行う[Python]</a><br />
<a href="https://crieit.net/drafts/IT-3-Python">農家のITマーケティング試行錯誤日記3.商品価格の集計と分析を行う[Python]</a></p>
Massa
tag:crieit.net,2005:PublicArticle/16044
2020-09-01T22:20:21+09:00
2020-12-11T23:18:34+09:00
https://crieit.net/posts/Python-IT-API-csv
[Python]農家のITマーケティング試行錯誤日記1.楽天商品検索APIを使って商品情報をcsv出力する
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>楽天市場のAPIを活用し、キーワードに当てはまる商品情報をcsvに出力してみました。</p>
<p>活用したのはこちらの「楽天商品検索API」というものです。<br />
<a target="_blank" rel="nofollow noopener" href="https://webservice.rakuten.co.jp/api/ichibaitemsearch/">楽天ウェブサービス: 楽天商品検索API(version:2017-07-06) | API一覧</a></p>
<h3 id="開発環境と使用するライブラリ"><a href="#%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%A8%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">開発環境と使用するライブラリ</a></h3>
<p>開発環境としてJupyter Notebookを利用しました。<br />
大掛かりなツールや定期的に実行したいツール作成時にはまた別のテキストエディタで作っていく必要があるかもしれませんが、ちょっとした単発のツールを作る際には、Jupyter Notebookなら少しずつ試しながらスクリプトが書けるし、即座に実行できるのでとても便利です。</p>
<p>使用するライブラリは<code>request</code>と<code>pandas</code>。<br />
APIを叩くために<code>request</code>を使い、取得したデータ操作とcsv出力のために<code>pandas</code>を利用しました。</p>
<h2 id="目的"><a href="#%E7%9B%AE%E7%9A%84">目的</a></h2>
<p>農産物を販売するための価格調査のために行いました。<br />
取得した情報をもとに、さらに分析して意思決定するところまでを想定しています(今回は情報取得するところまで)。</p>
<p>色々な直販サイトはありますが、楽天市場は馴染みがあり商品数も多く、APIも提供されているので取得しやすいなと考えました。</p>
<h2 id="楽天APIを扱う準備"><a href="#%E6%A5%BD%E5%A4%A9API%E3%82%92%E6%89%B1%E3%81%86%E6%BA%96%E5%82%99">楽天APIを扱う準備</a></h2>
<p>APIを活用するためには、スクリプトを書き始める前にまずは楽天の開発者向けページからアプリを作成してIDを取得しなければいけません。</p>
<p>こちらのRakuten Developersのサイト<br />
<a target="_blank" rel="nofollow noopener" href="https://webservice.rakuten.co.jp/document/#ichibaApi">楽天ウェブサービス: API一覧</a></p>
<p>右上の「+アプリID発行」からアプリを作成しておきます。ここで取得したアプリIDを自分のスクリプトで実行する際に使うことで、楽天市場の情報にアクセスでき流ようになります。</p>
<p>楽天市場だけでなく、楽天の他のサービス(楽天トラベルや楽天レシピなど)のAPIもあるのが良いですね。機会があれば使ってみたいです。</p>
<h2 id="商品情報を取得するスクリプト"><a href="#%E5%95%86%E5%93%81%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">商品情報を取得するスクリプト</a></h2>
<h3 id="(1)キーワードを入れて商品情報を取得してみる"><a href="#%281%29%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%82%92%E5%85%A5%E3%82%8C%E3%81%A6%E5%95%86%E5%93%81%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">(1)キーワードを入れて商品情報を取得してみる</a></h3>
<p>今回は「メークイン」というじゃがいもの品種名をキーワードに含む商品情報を取得することにします。</p>
<p>まずは必要なライブラリのインポート。</p>
<pre><code class="python">import requests
import pandas as pd
</code></pre>
<p>次にAPIを叩いて情報を取得するスクリプト。</p>
<pre><code class="python">REQUEST_URL = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706"
APP_ID="<ここに楽天のサイトで取得したアプリIDを入れる>"
serch_keyword = 'メークイン'
serch_params={
"format" : "json",
"keyword" : serch_keyword,
"applicationId" : [APP_ID],
"availability" : 0,
"hits" : 30,
"page" : 1,
"sort" : "-updateTimestamp"
}
response = requests.get(REQUEST_URL, serch_params)
result = response.json()
</code></pre>
<p>このスクリプトを実行したのち、<code>result['Items']</code>とすれば、情報がdict型のlistの形で取得できます。<br />
今回は30商品が取得できています(<code>serch_params</code>の<code>"hits":30</code>で指定している数値がこの取得数に当たります)</p>
<p>先ほどのスクリプトをざっと見ておきます。</p>
<p><code>REQUEST_URL</code>には<a target="_blank" rel="nofollow noopener" href="https://webservice.rakuten.co.jp/api/ichibaitemsearch/">楽天ウェブサービス: 楽天商品検索API(version:2017-07-06) | API一覧</a>に載っているリクエストURLを指定。<br />
<code>APP_ID</code>には先ほど楽天の開発者向けページから取得したアプリIDを記述しておきます。</p>
<p><code>serch_keyword</code>で検索したい文字列を指定することで、そのキーワードにマッチする商品が検索されます。例えばここをPythonの<code>input()</code>関数でユーザー入力を受け付けても使い勝手が良さそうですね。</p>
<p>次に、<code>serch_params</code>にはリクエストを送る際のパラメータをdict型で書いておきます。<br />
<a target="_blank" rel="nofollow noopener" href="https://webservice.rakuten.co.jp/api/ichibaitemsearch/">楽天ウェブサービス: 楽天商品検索API(version:2017-07-06) | API一覧</a>の「入力パラメーター」の項に詳細が載っていますので、詳細はそちらを参照してみてください。<br />
注意点として、この入力パラメーターには<code>applicationId</code>(アプリID)は必須でこれには先ほど記述した<code>APP_ID</code>を指定、また<code>keyword</code>,<code>shopCode</code>, <code>itemCode</code>, <code>genreId</code>のいずれかの指定が必須のようです。<br />
今回は検索キーワードで商品情報を取得したいので、<code>keyword</code>に先ほど指定した<code>serch_keyword</code>を指定しました。</p>
<p>この入力パラメータの<code>"hits":30</code>というのが取得する商品の件数に当たり、最大値が30のようです(省略してもデフォルトで30が設定されているようですが、今回のスクリプトでは忘れないように明示しておきました)。<br />
また<code>"page":1</code>というのが取得ページ番号になるので、この数値をfor文などでループを回せば複数ページに渡る大量の商品情報を簡単に取得できそうですね。</p>
<h3 id="(2)必要な商品情報を入れたdict型を作成"><a href="#%282%29%E5%BF%85%E8%A6%81%E3%81%AA%E5%95%86%E5%93%81%E6%83%85%E5%A0%B1%E3%82%92%E5%85%A5%E3%82%8C%E3%81%9Fdict%E5%9E%8B%E3%82%92%E4%BD%9C%E6%88%90">(2)必要な商品情報を入れたdict型を作成</a></h3>
<p>さて、先ほどAPIを叩いて取得できたdictには<a target="_blank" rel="nofollow noopener" href="https://webservice.rakuten.co.jp/api/ichibaitemsearch/">楽天ウェブサービス: 楽天商品検索API(version:2017-07-06) | API一覧</a>の「出力パラメーター」の項に載っている項目がdictのkeyとvalueとして入っています。</p>
<p>例えば<code>result['Items'][2]['Item']['itemName']</code>という風にkeyを指定すると、商品名が取得できます。</p>
<p>現段階で取得した情報は、このままだと余計な情報も入っていて扱いが不便なので、必要な情報のみが入っているdictを作ることにします。以下の項目に絞りました。</p>
<p>「itemName」「itemPrice」「itemCaption」「shopName」「shopUrl」「itemUrl」<br />
(後から、送料フラグ「postageFlag」も必要だなと思ったのですが、以下のスクリプトには反映されておりません)。</p>
<pre><code class="python"># for文を回してdictのlistを作る
item_key = ['itemName', 'itemPrice', 'itemCaption', 'shopName', 'shopUrl', 'itemUrl']
item_list = []
for i in range(0, len(result['Items'])):
tmp_item = {}
item = result['Items'][i]['Item']
for key, value in item.items():
if key in item_key:
tmp_item[key] = value
item_list.append(tmp_item)
</code></pre>
<p>これで、dict型の商品情報の入ったlistが取得できます。</p>
<p>ここで詰まったのが、<code>item_list.append(tmp_item.copy())</code>のところで<code>copy()</code>メソッドを使う必要があることでした。<br />
このメソッドを使わず<code>item_list.append(tmp_item)</code>としてしまったら、1つの商品情報が複数入ったdictができてしまい、首をひねって日を跨ぐことに。</p>
<p>以下の記事に助けられました。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/dogrunjp/9748789">Pythonのリストにdict型の変数をappendすると変数がポインタ的に振る舞うので… · GitHub</a></p>
<p>この理屈は理解しておいた方が良さそうなので、また別途まとめたいですね。</p>
<h3 id="(3)pandasでデータを整形"><a href="#%283%29pandas%E3%81%A7%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E6%95%B4%E5%BD%A2">(3)pandasでデータを整形</a></h3>
<p>dict型のlistを作ることができたら、あとは難しいことはなく<code>pandas</code>の基本操作で事足ります。<br />
データフレームを作り、また活用しやすくするために少し整形しておきます。</p>
<pre><code class="python"># データフレームを作成
item_df = pd.DataFrame(item_list)
# 列の順番を入れ替える
items_df = items_df.reindex(columns=['itemName', 'itemPrice', 'itemCaption', 'itemUrl', 'shopName', 'shopUrl'])
# 列名を日本語語に変更する
items_df.columns = ['商品名', '商品価格', '商品説明文', '商品URL', '店舗名', '店舗URL']
</code></pre>
<h3 id="(4)csv出力"><a href="#%284%29csv%E5%87%BA%E5%8A%9B">(4)csv出力</a></h3>
<p>作成したデータフレームをcsvファイルに出力します。</p>
<pre><code class="python">items_df.to_csv('./rakuten_mayqueen.csv')
</code></pre>
<p><code>to_csv()</code>メソッドの引数には、保存先のパス(ディレクトリとファイル名)を指定します。今回は相対パスで、このスクリプトがあるディレクトリ直下にcsvファイルを作成しました。</p>
<p>さて、出力されたデータをExcelなりSpreadSheetで開いてみましょう。</p>
<p><a href="https://crieit.now.sh/upload_images/c3de87150b8d6a7b5ddc97919a9f2c545f4e48ce423c9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c3de87150b8d6a7b5ddc97919a9f2c545f4e48ce423c9.png?mw=700" alt="スクリーンショット 2020-09-01 20.28.15.png" /></a></p>
<p>いい感じに取得することができました!</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>ひとまず楽天市場から商品情報を取得して、csv出力まですることができました。<br />
今後の方針としては、</p>
<p><strong>(1)データの収集と整形</strong><br />
より目的にあったデータを必要な数だけ集めて、活用できる形に整形します。</p>
<ul>
<li>取得する商品数を増やす</li>
<li>余計な商品を除外する</li>
<li>重量(例えば商品名に「10kg」「5kg」などが入っている)を別カラムに切り出す</li>
</ul>
<p>単にデータ量を増やすだけでは意味がなくて、余計なデータや極端なデータを省いたデータを集める必要があります。商品情報取得時のソート順なども関係してきそうです。<br />
例えば現状のデータだと、じゃがいもだけではなく包装資材やじゃがいもを使った別商品、謎に洋書まで取得してきている様子で、これらは除外するようにデータを加工しなければいけません。</p>
<p><strong>(2)収集したデータの分析と意思決定</strong></p>
<ul>
<li>平均値、中央値、最小値、最大値などを算出してみる</li>
<li>妥当な値決めを行う(意思決定)</li>
</ul>
<p>本当ならば別視点で「どの商品が売れ行きが良いのか」もデータを集めて分析できれば面白いのでしょうけど、今回はそこまでは考慮せず、現在の商品価格のみを見てみることにします。</p>
<p>より正確な意思決定にはまた別の学習(プログラミングスキル以外の)が必要になってきますね。<br />
ただ、今回のデータを元に平均値などの統計量を見ることができれば、値決めの判断材料としては役に立つのかなーって思っています。<br />
<br />
そんな訳で、<a href="https://crieit.net/posts/IT-2-Python">次回</a>は少し込み入ったデータ収集と加工をしてみたいと思います。</p>
<h2 id="(記事リンク)農家のITマーケティング試行錯誤日記"><a href="#%EF%BC%88%E8%A8%98%E4%BA%8B%E3%83%AA%E3%83%B3%E3%82%AF%EF%BC%89%E8%BE%B2%E5%AE%B6%E3%81%AEIT%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E6%97%A5%E8%A8%98">(記事リンク)農家のITマーケティング試行錯誤日記</a></h2>
<p><a href="https://crieit.net/posts/Python-IT-API-csv">農家のITマーケティング試行錯誤日記1.楽天商品検索APIを使って商品情報をcsv出力する[Python]</a><br />
<a href="https://crieit.net/posts/IT-2-Python">農家のITマーケティング試行錯誤日記2.商品情報の収集とデータ加工を行う[Python]</a><br />
<a href="https://crieit.net/posts/IT-3-Python">農家のITマーケティング試行錯誤日記3.商品価格の集計と分析を行う[Python]</a></p>
Massa
tag:crieit.net,2005:PublicArticle/16000
2020-07-11T13:49:10+09:00
2020-07-22T12:24:27+09:00
https://crieit.net/posts/Python-100
「Python実践データ分析100本ノック」学習記録(第1部:データ加工 )
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>初心者ながら「Python実践データ分析100本ノック」を進めています。</p>
<p>「第1部:データ加工」編ということで、第1章〜第2章(ノック1〜20)について、学んだことを言語化する目的でまとめを作ってみました。<br />
データ分析を実践で活用する流れや、重要そうなポイントを整理しておきます。</p>
<p>学習しながら整理しているので、間違えや補足があれば指摘いただけると嬉しいです!</p>
<h2 id="第1部の概要と感想"><a href="#%E7%AC%AC%EF%BC%91%E9%83%A8%E3%81%AE%E6%A6%82%E8%A6%81%E3%81%A8%E6%84%9F%E6%83%B3">第1部の概要と感想</a></h2>
<p>この第1部では「揃っているデータを加工して機械学習に使えるデータにするところまで」の練習でした。<br />
この段階ではまだ機械学習を活用する前段階の、データの整理とデータの全体像を把握、そして機械学習に適したデータ形式に加工する「<strong>クレンジング</strong>」という作業のやり方を学びます。</p>
<p>第1章ではデータの結合など基本的な前処理をしたのち、統計量や欠損値などを調べてデータ全体像を把握し、さらにデータを集計・可視化(折れ線グラフで図示)する、という一連の流れ。まずは導入として綺麗で揃っている扱いやすいデータを扱います。</p>
<p>第2章では上記の「基本的な前処理」の部分を深掘りします。現場にありがちな汚いデータを扱って、「データの揺れ」や欠損のあるデータの加工のプロセスを学びます。<br />
実際の現場ではデータが最初からまとまって整理されている訳ではなく、手入力や複数人での入力により表記違いや入力ミスがあるのが一般的です。それらを含んだ綺麗でないデータを「どう扱って機械学習に適したデータに加工していくのか」という、泥臭い作業ながらも重要な作業です。</p>
<h3 id="個人的な感想"><a href="#%E5%80%8B%E4%BA%BA%E7%9A%84%E3%81%AA%E6%84%9F%E6%83%B3">個人的な感想</a></h3>
<ul>
<li>1周目は細かいロジックよりも「データ加工の流れを把握すること」を最優先にすると、進めやすく整理しやすかった(細かいロジックは2周目で理解する)</li>
<li>現場で入力ミスや表記揺れが発生してしまうのはよくありがち。データを取る際はそれが発生しないような仕組みも重要だと思った</li>
<li>データ分析をする目的も重要。「データをどのような形に加工すればゴールなのか」は書籍だけだと難しいので実践の必要あり</li>
<li>「自分なりにデータと対話しながら分析を進めていく」という表現があって、その「データと対話する」という感覚が良いなあと思った</li>
</ul>
<h2 id="データ加工する際の流れ"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E5%8A%A0%E5%B7%A5%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AE%E6%B5%81%E3%82%8C">データ加工する際の流れ</a></h2>
<p>基本的にはデータ解析用のライブラリ<strong>Pandas</strong>を活用し、グラフ描画に<strong>Matplotlib</strong>というライブラリを使用。<br />
目的に応じて、3の集計や4の可視化を行って必要なデータを作っていきます(カッコの中はそれぞれの工程で本書に出てきたPandasの関数です)</p>
<ol>
<li>データの読み込み(<code>read_csv</code>, <code>read_excel</code>)</li>
<li>データの加工
<ul>
<li>複数のデータを結合、列の追加をして必要なデータフレームを作成する。ユニオンとジョイン(<code>concat</code>, <code>merge</code>)</li>
<li>欠損値(<code>isnull</code>)・データの揺れのチェックと修正</li>
</ul></li>
<li>集計(<code>groupby</code>, <code>pivot_table</code>)</li>
<li>グラフによる可視化</li>
<li>データの保存。ダンプ(<code>to_csv</code>)</li>
</ol>
<h3 id="「データ加工」のいくつかのポイント"><a href="#%E3%80%8C%E3%83%87%E3%83%BC%E3%82%BF%E5%8A%A0%E5%B7%A5%E3%80%8D%E3%81%AE%E3%81%84%E3%81%8F%E3%81%A4%E3%81%8B%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88">「データ加工」のいくつかのポイント</a></h3>
<ul>
<li>データの検算:データを結合した際には件数を確認したり検算して、操作が正しいかどうかを把握する</li>
<li>欠損値のチェック:欠損値があると正しく修正できないので、チェックして必要に応じて補完or除去する</li>
<li>データの揺れの修正:表記揺れをなくしたり、日付に関しては扱いやすいようにデータ型を統一する</li>
<li>各種統計量の把握をする:平均値や最大・最小など、また全体の数字のスケール感を把握する (<code>describe</code>, <code>sum</code>, <code>mean</code>など)</li>
</ul>
<h2 id="重要そうなポイント"><a href="#%E9%87%8D%E8%A6%81%E3%81%9D%E3%81%86%E3%81%AA%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88">重要そうなポイント</a></h2>
<h3 id="(1)Pandasの基礎的操作"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89Pandas%E3%81%AE%E5%9F%BA%E7%A4%8E%E7%9A%84%E6%93%8D%E4%BD%9C">(1)Pandasの基礎的操作</a></h3>
<p>本来は基礎的な操作を抑えてからこの書籍をやるべきなのですが、自分はいきなり書籍に取り組んでしまったので、ある程度進めるうちに付いていけなくなり整理することにしました。<br />
多くは書籍を進めていく中で覚えていけるのですが、データ列の参照方法、データを行インデックスで参照する方法(<code>loc</code>関数)などは常時使いされるので、使い方をちゃんと抑えておきたいです。</p>
<pre><code class="python">#列を取得(抽出)
df[列名]
df[[列名1, 列名2, 列名3, …]] #列名のリストを指定すると、複数の列を取得することもできる
</code></pre>
<pre><code class="python">#行、列を指定して値を取得
#loc:行ラベルと列ラベルで指定、iloc:行番号と列番号で指定。ラベルや番号にはスライスが使われることも多い
df.loc[行名, 列名]
df.iloc[行番号, 列番号]
</code></pre>
<h3 id="(2)データの結合について"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%B5%90%E5%90%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">(2)データの結合について</a></h3>
<p>データ加工をする過程で、データを結合する作業が何度も登場します。<br />
巻末にある「Appendix①」に目を通しておくと理解が深まります。</p>
<p>結合のやり方には「縦方向の結合(ユニオン)」と「横方向の結合(ジョイン)」があります。</p>
<ul>
<li><p>縦方向の結合(ユニオン) <code>concat</code>:<br />
同じ情報(列名)を持ったデータを下にくっつけて行を増やしていくイメージ</p></li>
<li><p>横方向の結合(ジョイン) <code>merge</code>:<br />
例えば「売上データ」と「顧客データ」など別の情報(列名)を持ったデータを、ある情報(例えば顧客名)などをキーとしてくっつけることで列を増やすというイメージです。</p></li>
</ul>
<p>特にデータを横方向に結合する<code>merge</code>は引数に指定できるオプションが初見では複雑に感じます。<br />
結合したい表を2つ左右に並べたイメージで、「左側の表」と「右側の表」をonに指定した列名でくっつける。そしてその時にどちらの表をベースにするかという結合方法をhowで指定する…というイメージで捉えると、僕はひとまず理解が進みました(慣れるためのざっくりしたイメージ付けなので、正確でないのはご了承ください)。</p>
<pre><code class="python">#concat:縦に結合
pd.concat([df1, df2])
</code></pre>
<pre><code class="python">#merge:横に結合。左に置く表(df1)と右に置く表(df2)を指定してあげるイメージ。以下のどちらのやり方でも可能
pd.merge(df1, df2)
df1.merge(df2)
#on:結合するキーを指定するオプション。共通の列名を指定する。
#列名がデータによって異なる場合、right_onやleft_onを使うこともできる。
pd.merge(df1, df2, on=キーにしたい列名)
pd.merge(df1, df2, left_on=df1のキーの列名, right_on=df2のキーの列名)
#how:結合方法を指定するオプション。inner, outer, left, rightを指定できる。
pd.merge(df1, df2, on=キーにしたい列名, how=left)
</code></pre>
<h3 id="(3)欠損値の補完のやり方"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E6%AC%A0%E6%90%8D%E5%80%A4%E3%81%AE%E8%A3%9C%E5%AE%8C%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9">(3)欠損値の補完のやり方</a></h3>
<p>ノック15の「欠損値をチェックして補完する」ところのアルゴリズムが急に複雑になりました。<br />
流れとしては「特定の列を見ていって欠損値のある行を特定し、その欠損値を別のデータから参照してきて置き換える」という流れになるのでしょうか。<br />
欠損値のある行をTrueとしたフラグ(<code>flg_is_null</code>)をインデックスとしてTrueの行のみに操作を施す…というやり方が登場します。<br />
ここで(1)で挙げた<code>loc</code>の理解が重要になってきます。</p>
<p><em>→(詳細を追記予定)</em></p>
<h3 id="(4)日付の表記揺れの補正とデータ型"><a href="#%EF%BC%88%EF%BC%94%EF%BC%89%E6%97%A5%E4%BB%98%E3%81%AE%E8%A1%A8%E8%A8%98%E6%8F%BA%E3%82%8C%E3%81%AE%E8%A3%9C%E6%AD%A3%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B">(4)日付の表記揺れの補正とデータ型</a></h3>
<p>ノック8では、登録日などの日付データから、目的に応じて年月の列を作成して集計する、という流れが出てきました。このように月別にカテゴリ分けができるのは、活用できる機会が多そうです。<br />
またノック17では、日付の揺れを補正するために、数値型や文字列型で入ってしまっている日付データの書式を統一する、という処理が登場します。<br />
データ型を確認する<code>dtypes</code>や変換する<code>astype()</code>の使い方、また日付型に関係する関数扱い方(<code>to_datetime</code>, <code>to_timedelta</code>)を整理しておきたいところです。</p>
<p><em>→(詳細を追記予定)</em></p>
<h3 id="(5)集計について"><a href="#%EF%BC%88%EF%BC%95%EF%BC%89%E9%9B%86%E8%A8%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">(5)集計について</a></h3>
<p>グループ化(<code>group_by</code>)とピボットテーブル(<code>pivot_table</code>)による集計が出てきますが、これらの活用の仕方が1周ではまだピンと来ませんでした。これらの関数をどういう目的の時にどう使ったら良いかうまく言語化できなかったので、課題とします。</p>
<p>ピボットテーブルを使うと、行と列を指定してそれが表になって出力されます。見やすい表に整形して出力してくれる、という機能でしょうか。<br />
(Excelの「ピボットテーブル機能」と同様のようで馴染みのある方は多いのでしょうか?)</p>
<pre><code class="python">#groupby:まとめたい(グループかしたい)列を引数に指定して、集計方法を示す関数を呼び出すと、グループごとの統計値が算出できる
#引数に1つの列を指定する基本の使い方
df.groupby('month').sum() #monthごとの合計値
df.groupby('month').mean() #monthごとの平均値
#引数に複数列をリストで指定する使い方。表形式で統計値が出力される
df.groupby(['month', 'name']).sum() #monthとnameの組み合わせごとに合計値を表示
df.groupby(['month', 'name']).count() #monthとnameの組み合わせごとにデータ数を表示
</code></pre>
<p><em>→(詳細を追記予定)</em></p>
<h3 id="(備考)データフレームとは?"><a href="#%EF%BC%88%E5%82%99%E8%80%83%EF%BC%89%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%A8%E3%81%AF%EF%BC%9F">(備考)データフレームとは?</a></h3>
<p>学習していると「データフレーム(df)」という単語が頻繁に出てきます。<br />
データフレームはPandasの型の一つで、ざっくり「行名と列名も一緒に入ったエクセルの表のようなデータ」というようなイメージでしょうか。<br />
(<code>df.values</code>で値を参照、<code>df.index</code>や<code>df.columns</code>で行名と列名を取り出したりすることができます)</p>
<p>書籍の中では特に解説はありませんが、Pandasには主に<code>DataFrame</code>と<code>Series</code>というデータ型があるようで、それぞれ</p>
<ul>
<li><code>Series</code>は1列だけのデータ。行名(index)のみ割り振られている</li>
<li><code>DataFrame</code>はテーブル形式の2次元のデータ。行名(index)と列名(column)が割り振られている</li>
</ul>
<p>どちらもPython標準のリストとはまた異なるデータ型です。<br />
他にも3次元データもあるようですね。また今後、必要に応じて理解していこうと思います。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>他にもこのまとめに入れられなかった要素(<strong>Matplotlib</strong>による可視化など)もあるので、必要に応じて整理したいです。</p>
<p>この書籍、著者の方の意図としては、まえがきに「Pythonの入門書ではありません」と明記されているように、本来は初歩を学んだ人がデータ分析を学ぶための実践書のような位置付けとなっています。<br />
Pandasの操作もデータ分析の基礎も理解していない僕がやるのは本来の活用の仕方から外れているのかもしれませんが、それでも「実際にデータ分析をするにはどのようなことを学ぶべきか」というイメージ作りができるのがとても良いな、という印象を持っています。<br />
なんとか最後まで完走して、画像処理・言語処理まで体験してみたいというモチベーションで続けています。</p>
<p>実践形式で手を動かすのにとても適した書籍なので、興味はあるけどまだ取り組むことが見えていない人でも、基礎練習として取り組むのにも有効に思うので、オススメの書籍です!</p>
Massa
tag:crieit.net,2005:PublicArticle/15997
2020-07-06T00:05:47+09:00
2020-12-11T23:19:19+09:00
https://crieit.net/posts/Flask-5f01ec4b4a177
[Python]Flaskアプリの基本構造を整理
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>これまでなんとなく<code>flask</code>のコードを良くわからないまま「おまじない」として書いていたので、一度ちゃんと調べて整理してみます。<br />
目標としては、</p>
<ul>
<li>その記述があることでどんなことが起こっているのか、を把握できるようにする</li>
<li>書かれているコードの文法構造を理解し、言語化しておいてあとで調べられるようにする</li>
</ul>
<p>この辺りを目指して整理してみます。脱コピペ。</p>
<h2 id="flaskアプリの基本構造"><a href="#flask%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E5%9F%BA%E6%9C%AC%E6%A7%8B%E9%80%A0">flaskアプリの基本構造</a></h2>
<p>シンプルなWebアプリの基本構造を見てみます。</p>
<p>解説などこちらを参照しました(スクリプトは一部違いがあります)<br />
<a target="_blank" rel="nofollow noopener" href="https://methane.github.io/flask-handson/start.html#hello-world">Flask を始めよう — Flask Handson 1 documentation</a></p>
<pre><code class="python">#①モジュールのインポート
from flask import Flask
#②Webアプリ作成
app = Flask(__name__)
#③エンドポイント設定(ルーティング)
@app.route('/')
def hello():
return 'Hello World!'
#④Webアプリ起動~~~~
if __name__ == '__main__':
app.run(debug=True)
</code></pre>
<p>このスクリプトを実行し、ブラウザで「http://127.0.0.1:8000」にアクセスすると、画面には「Hello World!」と表示されているはずです。<br />
そこまで上手くいっていれば、ローカルでWebアプリが起動できている、ということになります。</p>
<h2 id="大まかな流れとイメージを掴む"><a href="#%E5%A4%A7%E3%81%BE%E3%81%8B%E3%81%AA%E6%B5%81%E3%82%8C%E3%81%A8%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E6%8E%B4%E3%82%80">大まかな流れとイメージを掴む</a></h2>
<p>上のスクリプトは大きく4つのパートに分かれています。</p>
<ul>
<li><strong>①モジュールのインポート</strong>:アプリ作成に必要な機能の詰まったコードファイルを使えるようにします。</li>
<li><strong>②Webアプリを作成</strong>:必要な機能が詰まった「アプリの核」を用意します。</li>
<li><strong>③エンドポイントを指定(ルーティング)</strong>:ブラウザからアクセスするURLと、それに対応した処理をここに書きます。</li>
<li><strong>④Webアプリを起動</strong>:ファイルが実行された時にアプリが立ち上がる(ブラウザで表示できる)ようにします。</li>
</ul>
<p>僕らがFlaskでWebアプリを作る時は、③の部分を色々と作っていくことになります。<br />
①のインポートはpythonの基本文法の一つ。そして②④はflaskの操作となっており基本的にコードを変更する必要はなさそうで、これが「おまじない」としてよく書かれている部分になります。</p>
<h2 id="各パートを詳細に見てみる"><a href="#%E5%90%84%E3%83%91%E3%83%BC%E3%83%88%E3%82%92%E8%A9%B3%E7%B4%B0%E3%81%AB%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B">各パートを詳細に見てみる</a></h2>
<h3 id="①モジュールのインポート"><a href="#%E2%91%A0%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88">①モジュールのインポート</a></h3>
<pre><code class="python">from flask import Flask
</code></pre>
<p><code>flask</code>モジュールの<code>Flask</code>クラスをインポートしています。<br />
これにより、このスクリプト内で<code>Flask</code>クラスの機能を活用できるようになります。</p>
<h4 id="モジュールとは"><a href="#%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A8%E3%81%AF">モジュールとは</a></h4>
<p>関数やクラスなどが詰まったpythonファイルです。<code>hogehoge.py</code>という名前で保存されているファイルが1つのモジュール、と考えて良さそうです。</p>
<h3 id="②Webアプリ作成"><a href="#%E2%91%A1Web%E3%82%A2%E3%83%97%E3%83%AA%E4%BD%9C%E6%88%90">②Webアプリ作成</a></h3>
<pre><code class="python">app = Flask(__name__)
</code></pre>
<p><code>Flask(__name__)</code>と第一引数に<code>__name__</code>を渡してFlaskクラスのインスタンスを作成し、それを<code>app</code>に代入しています。<br />
この<code>app</code>にはflaskアプリの核が入っているイメージで、今後<code>app.route()</code>でルーティングを定義したり(③)、<code>app.run()</code>でローカルサーバー起動したりできるものになります。</p>
<h4>グローバル変数<code>__name__</code>について</h4>
<p><code>__name__</code>というのはモジュールの属性の一つで、グローバル変数のようです(変な名前ですね)。<br />
そしてその正体は「そのプログラムがどこから呼ばれて実行されているか」を格納している変数となります。</p>
<p>この<code>.py</code>ファイルが直接起動された時は、<code>__name__</code>には<code>__main__</code>という文字列が入ります。<br />
一方で、このファイルが他のスクリプトから<code>import</code>されて呼ばれた時には、<code>__name__</code>にはモジュール名(拡張子なしのファイル名)が入ります。</p>
<p>(これを<code>Flask()</code>の第一引数に渡すと何が起こっているのか、まではまだ理解ができずです)</p>
<h3 id="③エンドポイント設定(ルーティング)"><a href="#%E2%91%A2%E3%82%A8%E3%83%B3%E3%83%89%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E8%A8%AD%E5%AE%9A%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%EF%BC%89">③エンドポイント設定(ルーティング)</a></h3>
<pre><code class="python">@app.route('/')
def hello():
return 'Hello World!'
</code></pre>
<p>この記述によりルーティングが定義されます<br />
シンプルな記述ですが、だいぶ多くのことが凝縮されていそうです。</p>
<p>こちら(<a target="_blank" rel="nofollow noopener" href="https://methane.github.io/flask-handson/start.html#hello-world">Flask を始めよう — Flask Handson 1 documentation</a>)から引用しつつ整理してみます。</p>
<blockquote>
<p>@app.route('/') という行は、 app に対して / というURLに対応するアクションを登録しています。</p>
</blockquote>
<p>つまり、僕らが「http://hogehoge.com/」(パスが<code>/</code>)というURLにアクセスしたら、「こういう処理を返しますよ」というアクションを関数で作り、それらを1対1で紐づけています。<br />
今回の例では、<code>hello()</code>関数の返り値(<code>return</code>の後の文字列)がWebページに表示されることになります。<br />
この関数を「ビュー関数」と呼んだりするようです。</p>
<p>さて、ここで<code>@</code>という変な記号が何を表しているのか、ですが…。</p>
<blockquote>
<p>@ で始まる行はデコレータといって、その次の行で定義する関数やクラスに対して何らかの処理を行います。 @app.route('/') は、次の行で定義される関数を指定した URL にマッピングするという処理を実行しています。</p>
</blockquote>
<p>ここでいう「デコレータ」はちょっと高度なPython言語の文法かと思います。<br />
この辺りの文法的な理解と、ビュー関数がいかに動作して結果がWebに表示されるかはかなり難しそうなので、もうちょっと学習を進めながら理解を深めたいと思います。</p>
<h4 id="デコレータと高層関数"><a href="#%E3%83%87%E3%82%B3%E3%83%AC%E3%83%BC%E3%82%BF%E3%81%A8%E9%AB%98%E5%B1%A4%E9%96%A2%E6%95%B0">デコレータと高層関数</a></h4>
<p>「デコレータ」は「高層関数」を使った処理をスマートに記述できるpythonの記述の仕方で、「高層関数」とは引数として関数を受け取って処理をする関数、また戻り値として関数を返す関数のことです。<br />
ここではデコレータを使って、<code>hello()</code>という関数を高層関数<code>route()</code>に渡して処理している、と考えられます。<br />
(この辺りの理解が曖昧なので間違っていたら指摘をお願いします)</p>
<h4 id="用語の整理"><a href="#%E7%94%A8%E8%AA%9E%E3%81%AE%E6%95%B4%E7%90%86">用語の整理</a></h4>
<ul>
<li>ルーティング:URLと動作の紐付けをすること。ここではパスと関数を紐付け。</li>
<li>エンドポイント:アクセスするためのURLを指す。</li>
<li>URLとパス:ファイルの位置を指定するためのもの。URLは絶対パス、インターネット上の住所のようなもの。</li>
<li>ビュー関数:リクエストに対してどのような動作を返すかを記した関数。</li>
</ul>
<h3 id="④Webアプリ起動"><a href="#%E2%91%A3Web%E3%82%A2%E3%83%97%E3%83%AA%E8%B5%B7%E5%8B%95">④Webアプリ起動</a></h3>
<pre><code class="python">if __name__ == '__main__':
app.run(debug=True)
</code></pre>
<p>最後に、<code>app.run()</code>を実行してローカルサーバーを起動し、アプリを立ち上げます。<br />
<code>if</code>文は「このプログラムが直接実行されたかどうか」を判定しています。<br />
②で見たように、ファイルをスクリプトとして直接実行した場合、<code>__name__</code>には<code>__main__</code>が代入されます。<br />
つまりここでは、プログラムが直接実行された場合に<code>app.run()</code>が実行されて、アプリが立ち上がるのです。</p>
<h4 id="run()メソッド"><a href="#run%28%29%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89">run()メソッド</a></h4>
<ul>
<li>このFlaskの<code>run()</code>関数にキーワード引数で「ホスト」「ポート番号」「デバッグモード」の指定ができます。なくても起動可。</li>
<li>例えば<code>app.run(host='http://127.0.0.1', port=8080, debug=True)</code>と記述して起動することができます。
<ul>
<li><code>app.run(debug=True)</code> -> デバッグモードで実行する</li>
<li><code>app.run(host='http://127.0.0.1')</code> -> ホストを指定</li>
<li><code>app.run(port=8080)</code> -> ポート番号を指定(デフォルトでは5000)</li>
</ul></li>
</ul>
<h4 id="用語の整理"><a href="#%E7%94%A8%E8%AA%9E%E3%81%AE%E6%95%B4%E7%90%86">用語の整理</a></h4>
<ul>
<li>ホスト:(まとめ中)</li>
<li>ポート番号:(まとめ中)</li>
</ul>
<h3 id="(備考)WSGI規格"><a href="#%EF%BC%88%E5%82%99%E8%80%83%EF%BC%89WSGI%E8%A6%8F%E6%A0%BC">(備考)WSGI規格</a></h3>
<ul>
<li>Web Server Gateway Interfaceの意味。</li>
<li>WebサーバーとWebアプリを接続するための標準化されたインターフェース規格をWSGI規格と呼ぶ。</li>
<li>Flask, DjangoなどPythonの多くフレームワークはこのWSGI規格を採用している。</li>
<li>普通にWebアプリ開発するだけならあまり意識することはない。</li>
</ul>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>ほんとの初心者のうちは意識しなくても良いことですが、そこから次のステップに進むために理解を深めていくと出来ることが増えていくのかなあと感じます。<br />
後半はかなり駆け足でまとめてしまったので、また理解度に応じて修正しようと思います。</p>
<p>ドキュメント読みます。<br />
<a target="_blank" rel="nofollow noopener" href="https://a2c.bitbucket.io/flask/quickstart.html">クイックスタート - Flask v0.5.1 documentation</a><br />
<a target="_blank" rel="nofollow noopener" href="https://flask.palletsprojects.com/en/1.0.x/api/#flask.Flask">API — Flask Documentation (1.0.x)</a></p>
Massa
tag:crieit.net,2005:PublicArticle/15951
2020-06-12T23:34:11+09:00
2020-11-21T20:28:59+09:00
https://crieit.net/posts/Python-FizzBuzz-5ee39263c9299
[Python]FizzBuzz問題のカッコイイ解答を読み解いてみる
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>現在僕が受講しているPython基礎を習得する講習にて、</p>
<p><strong>「FizzBuzz問題を2通り以上の書き方で書いてみよう!」</strong></p>
<p>という課題がありました。</p>
<p>その模範解答の1つとして講師の方が挙げてくださった解答を目にして、基礎を学習中の身の僕はとても感動を覚えました。</p>
<p>こちらがその解答例。</p>
<p><em>FizzBuzzのカッコイイ解答</em></p>
<pre><code class="python">end = 100
for i in range(1, end + 1):
print('FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i)
</code></pre>
<p>う、うつくしい…<br />
たった3行で書けるとは。(もっと言うと2行でいけてる)</p>
<p>けど <strong>ちょっと何を言っているのかわからない。</strong></p>
<p>悔しいので、この記述を読み解いてみることにしました。<br />
これまでどこかでは見たことのある色んなエッセンスが詰まっていて、読み解けた時には程よい満足感に包まれました。</p>
<p>この記事では、「このスクリプトが何を言っているのかわかる」というところを目指して、解説してみます。<br />
ぜひ一緒に読み解いてみましょう!</p>
<p>※文法の正確な解説は参考リンクとして貼らせていただいて、まずは「意味がわかる」ところを目指します。<br />
※自分も理解が浅い部分があるため、何か間違いあればご指摘お願いします!</p>
<h2 id="FizzBuzz問題"><a href="#FizzBuzz%E5%95%8F%E9%A1%8C">FizzBuzz問題</a></h2>
<pre><code class="text">数字を1から順に100までログ出力します。ただし、
・数字が3の倍数の時には数字の代わりに「Fizz」
・数字が5の倍数の時には数字の代わりに「Buzz」
・数字が3の倍数かつ5の倍数の時には代わりに「FizzBuzz」
と出力するようにしてください。
</code></pre>
<p>もしこの問題をやったことない方は、まずはこれをスクリプトで書いてみることに挑戦しましょう!<br />
いろんな入門書、アルゴリズムの本で登場します。</p>
<p>今回の記事は、ひとまずこの問題に解答できる、という前提で書いているので、その上で読み進めてみてください。<br />
(<code>for</code>文や<code>if</code>文の基本的な使い方ができていれば良いかと思います)</p>
<h2 id="オーソドックスな解答例"><a href="#%E3%82%AA%E3%83%BC%E3%82%BD%E3%83%89%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AA%E8%A7%A3%E7%AD%94%E4%BE%8B">オーソドックスな解答例</a></h2>
<p>一応、FizzBuzz問題のオーソドックスな解答例です。<br />
色々な書き方があるので、この限りではありません。</p>
<pre><code class="python">end = 100
for i in range(1, end + 1):
if i % 3 == 0 and i % 5 == 0:
print('FizzBuzz', end='\n')
elif i % 3 == 0:
print('Fizz', end=' ')
elif i % 5 == 0:
print('Buzz', end=' ')
else:
print(i, end=' ')
</code></pre>
<p>プチテクニック:</p>
<ul>
<li><code>print()</code> 関数に <code>end</code>オプションをつけて文字列を指定することで、その文字列(<code>\n</code>改行、<code>\t</code>タブなども可)を表示させて見やすくできます。</li>
<li>「FizzBuzz」が出力される15の倍数ごとに改行することで見やすくなる、という学びがあったため使ってみました。</li>
</ul>
<h2 id="今回のカッコイイ解答について読み解く"><a href="#%E4%BB%8A%E5%9B%9E%E3%81%AE%E3%82%AB%E3%83%83%E3%82%B3%E3%82%A4%E3%82%A4%E8%A7%A3%E7%AD%94%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E8%AA%AD%E3%81%BF%E8%A7%A3%E3%81%8F">今回のカッコイイ解答について読み解く</a></h2>
<p>冒頭に書いたカッコイイ解答ですが、3行目がちょっと何言ってるかわからない。</p>
<p><em>よくわからん3行目のスクリプト</em></p>
<pre><code class="python">print('FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i)
</code></pre>
<p>調べながら読み解いていくと、この一文には、</p>
<ul>
<li>(1)文字列のスライス <code>'hogehoge'[a:b]</code></li>
<li>(2)三項演算子<code>x if 条件式 else y</code></li>
<li>(3)文字列・数値の論理演算<code>x or y</code></li>
</ul>
<p>このあたりがポイントとして含まれていそうです。</p>
<p>...<br />
...と、その前に前提知識を。</p>
<h3 id="(0)前提知識:数値・文字列の真偽値"><a href="#%EF%BC%88%EF%BC%90%EF%BC%89%E5%89%8D%E6%8F%90%E7%9F%A5%E8%AD%98%EF%BC%9A%E6%95%B0%E5%80%A4%E3%83%BB%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E7%9C%9F%E5%81%BD%E5%80%A4">(0)前提知識:数値・文字列の真偽値</a></h3>
<p>必要な知識として <strong>「数値・文字列の真偽値」</strong> について整理しておきます。</p>
<p>数値を条件式として使った場合には、TrueかFalseの真偽値で返ります。そしてその真偽については、</p>
<ul>
<li>数値0, 空の文字列の時 => False</li>
<li>それ以外の時 => True</li>
</ul>
<p>となります。<code>if</code>文の条件式などで使うことでコードがシンプルになるので、慣れておきたいですね。</p>
<h3 id="(1)文字列のスライス"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E3%82%B9%E3%83%A9%E3%82%A4%E3%82%B9">(1)文字列のスライス</a></h3>
<p>先ほどの一文をさらに分解して、わかりそうなところから見ていきましょう!</p>
<pre><code class="python">'FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)]
</code></pre>
<p>この部分は、細かい部分は無視して構造だけ書くと、</p>
<p><code>'FizzBuzz'[a:b]</code></p>
<p>と言う形になっており、これは <strong>「文字列のスライス」</strong> を適用しています。</p>
<p>今回の<code>FizzBuzz</code>という文字列にスライスを適用することで、</p>
<pre><code class="python">print('FizzBuzz'[0:8]) #① => FizzBuzz
print('FizzBuzz'[0:4]) #② => Fizz
print('FizzBuzz'[4:8]) #③ => Buzz
print('FizzBuzz'[0:0]) #④ => (何も表示なし)
</code></pre>
<p>と上手いこと表現できます。</p>
<p>まず、これが第一のポイント。</p>
<h3>(2)三項演算子<code>if</code>-<code>else</code></h3>
<p>次に読解したいのは、<code>'FizzBuzz'[a:b]</code>と書いた時の<code>a</code>,<code>b</code>に当たる</p>
<pre><code class="python">(4 if i % 3 else 0)
(4 if i % 5 else 8)
</code></pre>
<p>この記法です。</p>
<p>なんとなく「後置<code>if</code>」という表現を聞いたことがあったのですが、これは <strong>「三項演算子」</strong> と呼ぶらしいです。</p>
<p>使う分には <strong>if〜elseを1行で書ける書き方</strong> と抑えておけば良さそうでしょうか。</p>
<p>参考:<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/howmuch515/items/bf6d21f603d9736fb4a5">三項演算子(Python) - Qiita</a></p>
<p>これを当てはめると、上の2つの書き方は</p>
<p><code>(4 if i % 3 else 0)</code><br />
=> 「i%3の値がTrue(iを3で割った余りの値が0以外、つまり3で割り切れない時)なら4、それ以外(3で割り切れる時)は0」</p>
<p><code>(4 if i % 5 else 8)</code><br />
=>「iを5の値がTrue(iを5で割った余りの値が0以外、つまり35で割り切れない時)なら4、それ以外(3で割り切れる時)は8」</p>
<p>と、条件によって変わる数値を表す表現になります。<br />
(0は条件式の中で扱うと、真偽値は<code>False</code>になるんでしたね(前提条件))</p>
<p><strong>なるほどわからん。</strong></p>
<p>...<br />
...具体的に今回の問題の<code>FizzBuzz</code>で考えてみると、これまでの話がなんとなく繋がって見えてきます。</p>
<p><code>i</code>が3の倍数か、5の倍数か、3の倍数かつ5の倍数(15の倍数)かで場合分けができて、</p>
<pre><code class="python">print('FizzBuzz'[0:8]) #①=> FizzBuzz(iが3の倍数かつ5の倍数の時)
print('FizzBuzz'[0:4]) #② => Fizz(iが3の倍数で、5の倍数ではない時)
print('FizzBuzz'[4:8]) #③ => buzz(iが5の倍数で、3の倍数ではない時)
print('FizzBuzz'[0:0]) #④ => (何も表示なし)(iが3の倍数でも5の倍数でもない時)
</code></pre>
<p>この4パターンの結果を</p>
<p><code>'FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)]</code></p>
<p>この1行で表現することができるのです。</p>
<p>ただこれだけだとまだ、<code>Fizz</code>, <code>Buzz</code>, <code>FizzBuzz</code>の文字列は出力できても、数字を出力することができません。</p>
<p>そこで、次のポイントです。</p>
<h3 id="(3)数値・文字列の論理演算"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E6%95%B0%E5%80%A4%E3%83%BB%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E8%AB%96%E7%90%86%E6%BC%94%E7%AE%97">(3)数値・文字列の論理演算</a></h3>
<p>ここまできたらもう一息です。もう一つ見ていない部分がありました。</p>
<p><em>よくわからん3行目のスクリプト</em></p>
<pre><code class="python">print('FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i)
</code></pre>
<p>この最後の<code>or i</code>という表現に謎が残っています。<br />
そこでまた細かい部分を省いて構造だけ書いてみると、</p>
<p><code>print('FizzBuzz'[a:b] or i)</code></p>
<p>という構造になっています。</p>
<p>この<code>or</code>は <strong>「論理演算子」</strong> というやつですが、どうもなんかこの表現の中では直感的にわかる<code>or</code>の使い方と様子が違いそうです。</p>
<p>一般的な論理演算子<code>or</code>の使い方は</p>
<p><code>条件式 or 条件式</code> => <code>True</code>か<code>False</code>を返す<br />
例) <code>1 < 10 or 2 > 20</code> => <code>True</code></p>
<p>このように、<code>or</code>で並ぶのは条件式で、1つでも合っていれば<code>True</code>、どれも間違っていたら<code>False</code>が返る、と言う真偽値を返す演算となっています。<br />
これは直感的にわかりやすいですね。</p>
<p>ところが今回の表現は、</p>
<p><code>文字列・数値 or 文字列・数値</code> => どちらかの値(文字列か数値)を返す<br />
例) <code>'FizzBuzz' or 3</code> => <code>FizzBuzz</code><br />
例) <code>'' or 3</code> => 3</p>
<p>という風に<code>or</code>で並んでいる値のどれかを返す演算になっています。上の例では<br />
「<code>or</code>の前の値が真・後ろの値が真のとき、全体としては前の値を返す」<br />
「<code>or</code>の前の値が偽・後ろの値が真のとき、全体としては後ろの値を返す」<br />
というルールとなっています。</p>
<p>参考:<br />
<a target="_blank" rel="nofollow noopener" href="https://snowtree-injune.com/2018/08/14/bool/">Python♪論理演算子(and, or, not)の使い方と数値や文字列の論理演算 | Snow Tree in June</a></p>
<p><strong>なるほどわからん。</strong></p>
<p>...<br />
...細かい文法は今後学んでいくとして、今回は読み解くことを優先して「このようなルールがあるんだなー」と流すことにします。<br />
(「短絡評価(ショートサーキット)」という評価法?が使われています)</p>
<p>具体的に今回の問題に当てはめてみると、(2)で考えた4パターンをさらに以下のように考えることができます。</p>
<pre><code class="python">print('FizzBuzz'[0:8] or i) #① => FizzBuzz(orの前の値が真・後ろの値が真の時、前の値を返す)
print('FizzBuzz'[0:4] or i) #② => Fizz(上と同様)
print('FizzBuzz'[4:8] or i) #③ => buzz(上と同様)
print('FizzBuzz'[0:0] or i) #④ => i(orの前の値が偽・後ろの値が真のとき、後ろの値を返す)
</code></pre>
<p>①〜③の出力結果は(2)の時と同じですが、④だけ出力結果が変わっています。</p>
<p><code>'FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i</code></p>
<p>と<code>or</code>を使うだけで、1行で数値を出力することが可能になったのです。</p>
<h3 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h3>
<p>楽しかった。以上です。<br />
ここまで読んでみて、改めて冒頭のカッコイイ<code>FizzBuzz</code>解答をのスクリプトを実行してみると、少し見え方が違うのではないでしょうか。</p>
<p>なかなか直感的に説明できないのがもどかしく感じたので、精進せねばと思いました。<br />
今回見てきた文法は一つ一つは結構目にする書き方かと思うので、こういうのを使いこなして、自分もpythonでシンプルなスクリプトを書けるようになりたいものです。</p>
<p><code>FizzBuzz</code>面白いですね。<br />
また新しい概念を学んだら、あえてそれを生かした<code>FizzBuzz</code>を作ってみる、と言うのも練習として面白そうだなあと思いました。</p>
Massa
tag:crieit.net,2005:PublicArticle/15904
2020-05-20T21:15:03+09:00
2020-05-22T12:33:21+09:00
https://crieit.net/posts/Django-5ec51f47951bb
Djangoで開発を始める時にやっておくと良いこと
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>Djangoで開発を始める時にやっておいたほうが良いこと。<br />
後々になると変更が難しくなるので、必ずではないのですが初期段階のうちに設定しておいた方が良いことがあります。<br />
<strong>「settings.pyが入っている設定ディレクトリ名の設定」</strong> と <strong>「拡張ユーザーモデル」</strong> を作っておくことです。</p>
<p>他にも色々あるかもしれませんが、現段階での備忘録として。</p>
<h3 id="この記事の対象者"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%AF%BE%E8%B1%A1%E8%80%85">この記事の対象者</a></h3>
<ul>
<li>Djangoのチュートリアル的な教材を一通り終えたぐらいで、これからアプリ制作を始めようという人</li>
<li>ディレクトリ構造やプロジェクト、アプリケーションについて基礎知識を整理しておきたい人</li>
<li>ユーザーモデルをちょろっとカスタマイズしたいという人</li>
</ul>
<h2 id="(1)プロジェクト制作時に設定ディレクトリの名前を変更しておく"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%88%B6%E4%BD%9C%E6%99%82%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%AE%E5%90%8D%E5%89%8D%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8F">(1)プロジェクト制作時に設定ディレクトリの名前を変更しておく</a></h2>
<p>これは、プロジェクトのディレクトリ構造をわかりやすくするための措置です。<br />
普通に<code>$ django-admin startproject myproject</code>なとしてプロジェクト制作を始めると、ベースディレクトリ名と設定ディレクトリ名が同じ名前(ここでは<code>myproject</code>)で作られるため区別がつきづらい、という弊害が起こってしまいます。</p>
<p>どういうことかというと、このコマンドでプロジェクト制作を始めると、こんなディレクトリ構造になります。</p>
<pre><code>myproject <- ベースディレクトリ
|-- manage.py
`-- myproject <- 設定ディレクトリ
|-- __init__.py
|-- settings.py
|-- urls.py
|-- manage.py
`-- wsgi.py
</code></pre>
<p>プロジェクト名(ここでは<code>myproject</code>)と同じ名前のベースディレクトリがあり、その中に同名の設定ディレクトリができています。</p>
<p>(ちなみに[現場で使える Django の教科書《基礎編》]の書籍の中では、区別がつきやすいよう<br />
- ベースディレクトリ=<code>django-admin startproject</code> で作られるプロジェクトのディレクトリ<br />
- 設定ディレクトリ=<code>settings.py</code>のあるディレクトリ<br />
と呼んでいるので、その呼び方に準じることにします)</p>
<p>これだとわかりづらいので、設定ディレクトリの方の名前を変えてやる方法があります。</p>
<p>プロジェクト作成時に、ベースディレクトリを任意の名前(プロジェクト名)で作成した後にベースディレクトリの下へ移動し、第一引数に設定ディレクトリ名(ここでは<code>config</code>)、第二引数に<code>.</code>を指定して<code>startproject</code>を実行します。</p>
<pre><code>$ mkdir myproject(ここにプロジェクト名を入れます)
$ cd myproject/
$ django-admin startproject config .
</code></pre>
<p>これを実行することで、ディレクトリ構造は</p>
<pre><code>myproject <- ベースディレクトリ
|-- manage.py
`-- config <- 設定ディレクトリ *この名前が変わる
|-- __init__.py
|-- settings.py
|-- urls.py
|-- manage.py
`-- wsgi.py
</code></pre>
<p>このようになり、わかりやすい構成になるわけです。</p>
<h2 id="(2)ユーザーモデルを拡張しておく"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92%E6%8B%A1%E5%BC%B5%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8F">(2)ユーザーモデルを拡張しておく</a></h2>
<p>DjangoにはデフォルトでUserモデルが用意されており、簡単にログイン機能などが実装できるようになっています。<br />
それだけでとても便利なのですが、よりUserモデルを使いやすくするために、カスタマイズしたユーザーモデルを作っておくことが<a target="_blank" rel="nofollow noopener" href="https://docs.djangoproject.com/ja/2.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project">公式でも推奨</a>されているようです。</p>
<p>Userモデルを拡張する主な方法は3種類あって、<br />
- 1.<code>AbstractBaseUser</code>を継承する<br />
- 2.<code>AbstractUser</code>を継承する<br />
- 3. 別モデルを作って<code>OneToOneField</code>で関連させる</p>
<p>それぞれのやり方で長所短所があるのですが、<br />
開発が進んでデータベースに既にデータがある場合は3のやり方が良さそうです。<br />
アプリ制作を始める最初の段階なら1か2推奨ですが、1は入門〜初心者には難しそうなので、初めは2のやり方で慣れるのが無難かなというところです。</p>
<p>2の方法では、ユーザーモデルに独自のカラムを追加することができます。</p>
<p>カスタムユーザーモデルの作り方は簡単これだけ。</p>
<ol>
<li><code>models.py</code>に記述</li>
<li><code>settings.py</code>に<code>AUTH_USER_MODEL</code>を設定</li>
<li>マイグレート</li>
</ol>
<h3>①<code>app/models.py</code>に記述(<code>app</code>はアプリケーションディレクトリ名が入る)</h3>
<p>ここにカスタムユーザーモデルを定義します。(<code>AbstractUser</code>というモデルにユーザーモデルのベースとなる情報が定義されているので、それを継承して使う形になります)</p>
<p>ここでテーブル名や、追加したいカラムを指定することができます。<br />
例えば、<code>custome_user</code>というテーブルに外部キーを用いた<code>Calendar</code>というカラムを設定できます。</p>
<pre><code>from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
class Meta(AbstractUser.Meta):
db_table = 'custom_user'
swappable = 'AUTH_USER_MODEL'
calendar = models.ForeignKey(Calendar, verbose_name='アクティブカレンダー', on_delete=models.PROTECT, blank=True, null=True)
</code></pre>
<p>これは僕の実際のコードの例なので、ユーザーモデルに<code>Calendar</code>というモデルを紐づけていますが、例えばユーザーモデルに性別や年齢、メールアドレス、パスワードなどを持たせたいときには、ここでカラム指定してあげると良いかと思います。<br />
デフォルトのUserモデルにカラムを追加する形になるので、もともとあるカラムも残ります(Userモデルの詳細はMEMO参照)</p>
<h3>②<code>config/settings.py</code>に<code>AUTH_USER_MODEL</code>を設定(<code>config</code>はプロジェクト作成時に作られる設定ディレクトリの名前)</h3>
<p>ここに使用するカスタムユーザーモデルを定義します。「アプリ名.モデル名」のように記述します</p>
<pre><code>AUTH_USER_MODEL = app.CustomUser
</code></pre>
<h3 id="③マイグレート"><a href="#%E2%91%A2%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%88">③マイグレート</a></h3>
<p>モデルを変更したら定番のデータベースのマイグレート。</p>
<pre><code>$ Python manage.py makemigrations
$ Python manage.py migrate
</code></pre>
<h2 id="Django入門向けのいろいろ"><a href="#Django%E5%85%A5%E9%96%80%E5%90%91%E3%81%91%E3%81%AE%E3%81%84%E3%82%8D%E3%81%84%E3%82%8D">Django入門向けのいろいろ</a></h2>
<h3 id="バージョンについて"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">バージョンについて</a></h3>
<p>どのバージョンを指定するかですが、<strong>LTS (Long-Term Support)</strong>(=長期間のセキュリティサポートがあるバージョンのこと)に該当するバージョンを採用すると間違いがなさそう。</p>
<ul>
<li>2.2 => 2022年4月までサポート</li>
<li>1.11 => 2020年4月までサポート</li>
</ul>
<p>ちなみに2019年12月にDjango3系が出ているのですが、LTSに当たる3.2は2021年の4月にリリース予定とのこと。</p>
<p>特に理由がなければ現時点で最新のLTSで始めておけば間違いがないのかな?という印象です。<br />
(もしデプロイ先が決まっているなら、そのサーバーが対応しているかなどは調査の必要があるのかも)</p>
<h3 id="プロジェクトとアプリケーションについて"><a href="#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A8%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">プロジェクトとアプリケーションについて</a></h3>
<p>Djangoには「プロジェクト」と「アプリケーション」という概念があるので、少し整理しておきます。</p>
<p><strong>プロジェクト</strong><br />
WEBサービスの全体のプロジェクト<br />
<code>$ django-admin startproject myproject</code><br />
(<code>myproject</code>がプロジェクト名)のコマンドで作られる<br />
<br />
<br />
<strong>アプリケーション</strong><br />
小さな機能のひとまとまり<br />
<code>python3 manage.py startapp myapp</code><br />
(<code>myapp</code>がアプリケーション名)のコマンドで作られる</p>
<p>プロジェクトが大きな箱で、アプリケーションはその箱の中に機能ごとに分けて作られるイメージです。</p>
<p>1つのWEBサービスを作る際に1つのプロジェクトを作り、小さなサービスならその中に1つのアプリケーションを作成して実装していきます。<br />
プロジェクトの規模が大きくなってくると、1つのプロジェクトに複数のアプリケーションを作って運用することもできます。</p>
<ul>
<li>アプリケーションは他のアプリケーションとなるべく依存しないように作るとよい</li>
<li>プロジェクトにアプリケーションを次々と追加する形で機能を追加していくのが基本スタイル</li>
</ul>
<p>複数のアプリケーションを作る場合は、このように考慮するとメンテナンスがしやすそうですね。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>一通りチュートリアル的な教材を終えた後は</p>
<p><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Django-%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8%E3%80%8A%E5%9F%BA%E7%A4%8E%E7%B7%A8%E3%80%8B-%E6%A8%AA%E7%80%AC-%E6%98%8E%E4%BB%81/dp/4802094744">現場で使える Django の教科書《基礎編》</a></p>
<p>こちらの本がとてもわかりやすく勉強が捗ります!</p>
<p>基礎的なことが広く網羅されていて、知識の定着や理解のために良いです。つまみ食いしながら読んでもよし。<br />
今回まとめたようなベストプラクティスな手法が多く掲載されていて、かなり重宝しております。</p>
<h2 id="MEMO:デフォルトのUserモデルについて"><a href="#MEMO%EF%BC%9A%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%AEUser%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">MEMO:デフォルトのUserモデルについて</a></h2>
<p>参考までに、Django 2.1.7で確認したUserモデルのフィールドを記載。<br />
デフォルトのUserモデルはAbstractUserモデルを継承している形になるので、AbstractUserモデルのカラム名が、カスタムユーザーモデルを作った際にも引き継がれます。</p>
<p><em><code>lib/django/contrib/auth/models.py</code></em></p>
<pre><code class="python">class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
</code></pre>
Massa
tag:crieit.net,2005:PublicArticle/15820
2020-04-12T12:09:40+09:00
2020-04-12T15:48:26+09:00
https://crieit.net/posts/Python-5e92867415fa6
超速! Python基礎文法集(自分用チートシート)
<h1 id="超速! Python基礎文法集"><a href="#%E8%B6%85%E9%80%9F%21+Python%E5%9F%BA%E7%A4%8E%E6%96%87%E6%B3%95%E9%9B%86">超速! Python基礎文法集</a></h1>
<p>超簡潔に自分用チートシートにしておく。ところどころ感覚的に掴むために変な書き方してます。「関数・クラス」などは随時追記か別でまとめるかも。<br />
<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/Python%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E5%85%A5%E9%96%80-%E4%BC%9D%E7%B5%B1%E7%9A%84%E3%81%AA%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%A7%E5%AD%A6%E3%81%B6%E5%AE%9A%E7%9F%B3%E3%81%A8%E8%A8%88%E7%AE%97%E9%87%8F-%E5%A2%97%E4%BA%95-%E6%95%8F%E5%85%8B-ebook/dp/B0822N5RMS">「Pythonではじめるアルゴリズム入門」</a>を教材にさせていただいてます。数学好きなら興味深い題材がたくさん。</p>
<h2 id="(0)実行"><a href="#%EF%BC%88%EF%BC%90%EF%BC%89%E5%AE%9F%E8%A1%8C">(0)実行</a></h2>
<h3 id="コマンド"><a href="#%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89">コマンド</a></h3>
<ul>
<li><code>python --version</code> でバージョン確認</li>
<li><code>python</code> で対話モード開始</li>
<li><code>python filename.py</code> でスクリプトファイルを実行</li>
</ul>
<h3 id="文字コードの指定に注意"><a href="#%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E6%8C%87%E5%AE%9A%E3%81%AB%E6%B3%A8%E6%84%8F">文字コードの指定に注意</a></h3>
<ul>
<li>Python3系であれば、文字コードは <strong>UTF-8</strong> を使う。</li>
<li>Python2系であれば、ソースコードの冒頭に以下が必要<br />
<code># -*- coding:utf-8 -*-</code> または <code># coding:utf-8</code></li>
</ul>
<h2 id="(1)基本"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E5%9F%BA%E6%9C%AC">(1)基本</a></h2>
<ul>
<li>コメントアウトは<code>#</code></li>
<li>途中の改行は<code>\</code>(バックスラッシュ)</li>
<li>データ型を調べる <code>type(hoge)</code></li>
</ul>
<h3>変数 <code>valiable_name</code></h3>
<ul>
<li>アルファベットと数字、アンダーバー(予約語不可,1文字目はアルファベット or アンダーバー )</li>
<li><strong>コーディング規則 PEP-8</strong> => 変数名は小文字・アンダーバーで連結</li>
</ul>
<h3 id="リストとタプル"><a href="#%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A8%E3%82%BF%E3%83%97%E3%83%AB">リストとタプル</a></h3>
<ul>
<li>データの型に制約はなく、異なる型の要素でも同じリスト・タプルに格納できる</li>
<li>要素指定の範囲を<code>:</code>で区切る方法</li>
</ul>
<pre><code class="python">list = [0, 1, 2 ,3, 4]
list[1:3] # 1番目〜3-1番目(1, 2)が取り出せる
list[2:] # 2番目〜最後(2, 3, 4)が取り出せる
list[:3] # 最初〜3-1番目(0, 1, 2)が取り出せる
list[:-3] # 最初〜5-3番目(0, 1)が取り出せる。後ろから3つの要素を省いて取り出す感覚?
</code></pre>
<ul>
<li>取り出せる要素数の計算方法 list[a:b] => b - a 個( list[a:] => len(list) - a)</li>
</ul>
<h4>リスト <code>[list]</code></h4>
<ul>
<li>後から変更できる <code>list = [1, 2, 3]</code> で定義 / <code>list[0]</code>で取り出す</li>
</ul>
<h4>タプル <code>(taple)</code></h4>
<ul>
<li>変更不可 <code>taple = (1, 2, 3)</code>で定義 / <code>taple[0]</code>で取り出す</li>
<li>処理がリストよりも若干高速・間違ってデータを置き換える心配がなくなる</li>
<li><p>タプルの中身を変更しようとするとError<br />
<code>TypeError: 'Tuple' object does not support item assignment</code></p>
<h3>文字列 <code>'シングルクォート'</code> <code>"ダブルクォート"</code></h3>
<ul>
<li>リストのように一部を取り出すこともできる <code>'hoge'[1] # => 'o'</code></li>
<li>文字列の連結もできるがデータ型に注意。テクニック<br />
<code>'abc' + str(123)</code> で数字 => 文字列へ変換して文字列表<br />
<code>'abc%i' % 123</code> で文字列の中に数字を埋め込んで文字列表示</li>
</ul></li>
<li>データ型の違う文字列と数字の足し算をするとError<br />
<code>TypeError: unsupported operand type(s) for +: 'int' and 'str'</code></li>
</ul>
<h2 id="(2)条件分岐と繰り返し"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E6%9D%A1%E4%BB%B6%E5%88%86%E5%B2%90%E3%81%A8%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97">(2)条件分岐と繰り返し</a></h2>
<ul>
<li>条件の最後に<code>:</code>と<strong>ブロック</strong>内インデント必須</li>
<li><strong>比較演算子</strong>と<strong>論理演算子</strong>、演算子の優先順位に注意</li>
</ul>
<h3>条件分岐 <code>if</code></h3>
<ul>
<li>条件を増やすときは<code>elif</code></li>
</ul>
<pre><code class="python">if (hoge >= 10) and (hoge < 20):
'hogeは10以上20以下'
else:
'hogeは10以下または20以上'
</code></pre>
<h3>繰り返し <code>for</code></h3>
<ul>
<li>指定した回数だけ繰り返すとき使用</li>
<li>基本:<code>range(10)</code> で10回繰り返し、<code>range(4,7)</code>で4,5,6と繰り返し</li>
</ul>
<pre><code class="python">for i in range(10):
print(i)
</code></pre>
<ul>
<li>リストを指定するとその要素が順に取りされる</li>
<li>リストに格納した要素を位置と合わせて順に処理するときは<code>enumerate()</code>関数で</li>
</ul>
<pre><code class="python">for i in [5, 3, 7]:
print(i) # => 5, 3, 7と順番に出力される
for i, e in enumerate([5, 3, 7])):
print(i, ':', e) # => '0 : 5'…と位置と要素のペアが順番に出力される
</code></pre>
<h3>繰り返し<code>while</code></h3>
<ul>
<li>事前に繰り返す回数がわからないとき条件式と合わせて使用</li>
</ul>
<pre><code class="python">i = 0
while i < 4:
print(i)
i += 1
</code></pre>
<h2 id="(3)リスト内包表記でリストを作成する"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E5%8C%85%E8%A1%A8%E8%A8%98%E3%81%A7%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">(3)リスト内包表記でリストを作成する</a></h2>
<ul>
<li>リストに要素を連続して追加したいとき( <code>for</code>とブロックを使って<code>list.append(i)</code> で要素追加できるがそれより簡素)</li>
<li>リスト内包表記の方が処理が高速になることがある</li>
<li><code>if</code>を分の後ろに書けるのはPythonではリスト内のみ</li>
</ul>
<p>(1)順番に <code>for</code> で取り出す:集合 {x | xは10未満のℕ)</p>
<pre><code class="python">data = [i for i in range(10)] # 0〜9までの10個の要素が追加
</code></pre>
<p>(2)条件付き <code>if</code>を組合せて取り出す:集合 {x | xは10未満のℕ, xが偶数のとき}</p>
<pre><code class="python">data = [i for i in range(10) if i% 2 == 0] # 0〜9までの偶数のみ追加
</code></pre>
<p>(3)条件付き <code>if 〜 else</code>を組合せて取り出す:集合 {x | xが偶数ならその数, それ以外のときは0, xは10未満のℕ}</p>
<pre><code class="python">data = [i if i % 2 == 0 else 0 for i in range(10)] # 0〜9まで 偶数のときはその値を、奇数のときは0を追加
</code></pre>
Massa
tag:crieit.net,2005:PublicArticle/15808
2020-04-03T19:27:48+09:00
2020-04-04T09:09:10+09:00
https://crieit.net/posts/1-AI
01.人間がAIに支配される未来が来る?
<p>※こちらは「ひとりぼっちPython」連載記事です。<a href="https://crieit.net/magazines/massasquash/%E3%81%B2%E3%81%A8%E3%82%8A%E3%81%BC%E3%81%A3%E3%81%A1Python">連載一覧はコチラ</a></p>
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>今回は「人工知能」「AI」について触れてみます。</p>
<p>(本記事は概念やイメージを掴むための入門向けのお話のため、Pythonのコードや技術的な内容はありませんのでご了承ください…!)<br />
<br />
<br />
人工知能とかAI、この数年くらいで急によく聞くようになったワードかなと思います。どんどん新製品や新サービスが出てきたりニュースにも登場したりしていて、進歩が激しい分野なんだなっていうのがわかります。</p>
<p>僕も普段家でAmazonでのネットショッピングやGoogleスマートスピーカーを使うことが多いのですが、これらもAIが活用されているのですよね。<br />
知らず知らずのうちにAIの便利な機能を使っていたり、AIによってオススメ商品を買わされていたりします(クレカの請求が怖い)。</p>
<p>そんな感じで身近なところでも使われていて、とにかく頻繁に人工知能やAIという言葉は聞くけれど、じゃあ</p>
<p><strong>「AIってズバリ、何?」</strong></p>
<p>という単純で素朴な質問に対して、明快に答えられないな…と思ったのです。</p>
<p><strong>「うーん、なんか良くわからないけど、色々できてしまうスゴイやつ…?」</strong></p>
<p>僕の語彙力の引き出しを全力で引っ張ってみても、そんな程度の回答で悲しくなりました…。</p>
<p>皆さんはどう答えますかね?</p>
<p>いろんな観点がありそうですが、自分なりの解答を得たいと思い、人工知能・AIの入り口を紐解いてみます。</p>
<h2 id="AIと聞いて抱くイメージ"><a href="#AI%E3%81%A8%E8%81%9E%E3%81%84%E3%81%A6%E6%8A%B1%E3%81%8F%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8">AIと聞いて抱くイメージ</a></h2>
<p>「AIって何?」っていう直接的な疑問は一旦置いておいて、まずはAIと聞いてどんなイメージを思い浮かべるでしょうか?</p>
<ul>
<li>便利ロボット?学習して成長していくやつ?</li>
<li>人間より上位の存在? 下位の存在?</li>
<li>ポジティブな印象? ネガティブな印象?</li>
</ul>
<p>なんとなく僕は、人類が支配されてしまいそうな怖いイメージを持っています。AIが人類を支配するというよりは、AIを活用して色々やっている上位の人達が大規模にデータを集めていて、僕たち下々の人間は知らないうちに彼らに支配されてゆく…そんなイメージです(ネガティブ思考全開)。</p>
<p>身近には便利になっている気がするけど、実はそれは甘い汁を吸わされているんじゃないか…と、考えすぎると疑心暗鬼の闇に沈んでしまうので、あまり考えないようにして生活しています。<br />
<br />
<br />
<strong>「シンギュラリティ」</strong> という言葉があります。</p>
<p>想像上の未来のお話で、 <strong>「AIなどの技術が、自ら人間より賢い知能を生み出すことが可能になる時点」</strong> を指す言葉のようです。<br />
人工知能が進歩しすぎて人間の能力を超えてしまう時、と考えるとわかりやすいかもしれないです。<br />
そんな時代がいつしか来る、と言われています。</p>
<p>「自分で考えて仕事ができるAIロボットに僕らの仕事が奪われるんじゃないか」</p>
<p>とか、そんな言葉も聞きますよね。</p>
<p>その真偽は僕ら常人に想像がつかない範囲だと思います。<br />
まだ見ぬ未来に不安を覚えるより、今存在している現実的なAIについて考えてみることで、僕らの生活を便利で豊かなものにしてくれるAIさんに寄り添ってみることにします。<br />
そうすることで、仮にそんなファンタジーな世界が訪れた時でも対策ぐらいは立てられそうですよね。</p>
<h2 id="想像上のAIと現実的なAI"><a href="#%E6%83%B3%E5%83%8F%E4%B8%8A%E3%81%AEAI%E3%81%A8%E7%8F%BE%E5%AE%9F%E7%9A%84%E3%81%AAAI">想像上のAIと現実的なAI</a></h2>
<p>どこかの偉い人が定義した「強いAI」と「弱いAI」という言葉があります。<br />
「AI・人工知能」という大きなくくりの中には2種類があって、それらを分けている分類です。</p>
<p>まず <strong>「強いAI」</strong> とは「汎用人工知能」とも呼ばれて、 <strong>「自意識を持った人間同様の知能を持つ存在」</strong> を指す言葉のようです。<br />
つまり、人間のように自ら考えて、行動することができるAIのこと。よくターミネーターといった例えが登場します。ドラえもんもこの分類に入るのでしょうか。<br />
こちらは今は想像上の存在で、本当にこのようなAIができるかどうかもよくわかりません。<br />
上のシンギュラリティは、このような人間以上の知性を持った「強いAI」が登場した世界、と置き換えるとわかりやすいのかなと思います。</p>
<p>一方で <strong>「弱いAI」</strong> とはもっと現実的かつ実用的なもので、 <strong>「特定の機能に特化したAI」</strong> のことを指します。<br />
人間の機能の一部分のみにフォーカスして、その処理をすることに特化したもの、と考えて良いかなと思います。</p>
<p>そんな訳で、今はまだ全知全能な「強いAI」が存在しない(できるかもわからない)現状、僕らが普段関係しているのはこの「弱いAI」に分類されているもの、と考えて良さそうですね。</p>
<p>(強い・弱いという区別も素人目からしたら何か違和感があるなと思ってしまうのですが、この辺りは色々議論されている部分でもあるようですね)</p>
<h2 id="どんなAIが今は実用化されているだろうか"><a href="#%E3%81%A9%E3%82%93%E3%81%AAAI%E3%81%8C%E4%BB%8A%E3%81%AF%E5%AE%9F%E7%94%A8%E5%8C%96%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%A0%E3%82%8D%E3%81%86%E3%81%8B">どんなAIが今は実用化されているだろうか</a></h2>
<p>そんなこんなで漠然としたイメージだけ抱いてネガティブ思考に堕ちていても仕方がないので、もうちょっと地に足をつけて考えてみます。<br />
まずは具体的に、実用化されているAIとして思いつくものを挙げてみました。</p>
<ul>
<li>将棋や囲碁のプロ棋士に勝てる人工知能</li>
<li>ペッパー君に代表されるAIロボット</li>
<li>AIによって無人でチャット対応ができる、企業のカスタマーサポート</li>
<li>スマートスピーカーに使われている音声アシストなど。声を認識してインターネットを介して調べ物やお店の予約、買い物など秘書的な活躍をしてくれる</li>
<li>Web広告やネットショッピングでオススメ商品を提供してくれる</li>
</ul>
<p>他にもたくさんあると思うんですが、何か思い着くのはありますかね?<br />
<br />
<br />
自動運転技術なんかも今は研究が進んでいますよね。<br />
聞くところによると、海外のUber社は今はタクシーのシェアリングサービスですが、それによって人間様の行動のデータを集めて自動運転のための情報を集めている…だとか(正確な情報かは知りません)。</p>
<p>余談ですが、Uberは僕もアメリカに行った時に乗ってみました。すごく便利ですねあれ。<br />
クレジットカードを登録しておくことで現金での支払いは不要。タクシーに乗りたい時にスマホアプリで予約すると、ほんの数分でお迎えが来てアプリに通知が入ります。<br />
僕が経験したのは、本当は自分が乗るはずじゃないタクシーに載っちゃって途中で間違いに気づいた運転手さんにその場で放り出されたり、<br />
町を案内してくれた友人がUber運転手をやっていて突然「ちょっと1時間稼いでくる」と出て行って数時間戻ってこなかったり、<br />
おそらくUber社は、そんな人間らしさをデータとして集めて心温まるAIを作ろうとしているのでしょう…。</p>
<p>余談でした。</p>
<h2 id="AIは何でも出来る訳じゃない?"><a href="#AI%E3%81%AF%E4%BD%95%E3%81%A7%E3%82%82%E5%87%BA%E6%9D%A5%E3%82%8B%E8%A8%B3%E3%81%98%E3%82%83%E3%81%AA%E3%81%84%EF%BC%9F">AIは何でも出来る訳じゃない?</a></h2>
<p>先ほど挙げたものはどれも、その特定の分野では優れているのですが、実は万能なものではありません。<br />
例えば将棋や囲碁が強い人工知能は人生ゲームやモノポリーでは勝てないし、例えば洋服屋のカスタマーサポートのチャットボットが急に自動車業界に部署異動させられてしまったら、お客さんがマフラーの交換の見積もりを尋ねた時になんか上司に怒られる事態が発生してしまいそうです。</p>
<p>このように、 <strong>現実的なAIとは 「何か特定の機能・分野に特化したもの」</strong> であり、 <strong>AIにも「出来ること」と「出来ないこと」がある</strong> 訳です。</p>
<p>人間で考えても、誰しも得意不得意ってありますよね。<br />
それがAIだと極端になっていて、得意なものはとことん得意だけど、自分の習熟していない分野になるとからっきしダメになってしまうのです。</p>
<p>AIではその得意不得意が特にデータに依存してきます。<br />
基本的に積み重ねられた文字や画像などのデータを学習することで識別したり分析したりしていて、それも学習のためには大量のデータが必要になってくるのです。<br />
特定の分野に強いAIは、その分野について大量のデータをもとに学習しているのです(詳細は省きますが「強化学習」とか必ずしもそうじゃないようですが)。</p>
<p>Amazonや楽天市場は大量のお客さんがいて大量の購入履歴があるため、「◯◯代の✖︎✖︎県在住の男性。こういう商品をよく買っているので、こんなのをお勧めしたら買ってくれるかも」とピンポイントで商品を勧める(レコメンドする)ことができるのですが、</p>
<p>それを一個人が販売システムから作ったとしても、お客さんが少ないので学習するデータが少なく、その人が買わなさそうなトンチンカンな商品を勧めてしまったりするのです。</p>
<p>…それはそれで作ってみたい。<br />
(絶対その人が興味なさそうな本とか音楽、動画を勧めてくるサービスが1つくらいあってもいい)</p>
<p>一例としてデータの必要性を挙げましたが、まだまだ今のAIの制約はたくさんありそうです。<br />
この辺り、もうちょっと具体的に紐解いてみたいのですが今回はこの辺までとして、次回は「AIに出来ること・出来ないこと」あたりにフォーカスしてまとめてみたいなって思います。</p>
<p>今回はおしまい。</p>
<h2 id="今回のまとめ"><a href="#%E4%BB%8A%E5%9B%9E%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81">今回のまとめ</a></h2>
<p>「AIって何?」という疑問に対しての明確な答えにまでは辿り着くことができませんでしたが、<br />
ひとまずのところ「万能でなんでもできるもの」ではなくて「人間の機能の一部分を代替してくれるもの」と考えることができそうですね。</p>
<h3 id="用語"><a href="#%E7%94%A8%E8%AA%9E">用語</a></h3>
<ul>
<li>人工知能(AI)</li>
<li>シンギュラリティ</li>
<li>「強いAI」と「弱いAI」</li>
</ul>
<h3 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h3>
<p><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4797370262">絵でわかる人工知能 明日使いたくなるキーワード68 (サイエンス・アイ新書) | 三宅 陽一郎, 森川 幸人, 森川 幸人 |本 | 通販 | Amazon</a></p>
<p><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4797391693">人工知能解体新書 ゼロからわかる人工知能のしくみと活用 (サイエンス・アイ新書) | 神崎 洋治 |本 | 通販 | Amazon</a></p>
Massa
tag:crieit.net,2005:PublicArticle/15780
2020-03-27T21:45:06+09:00
2020-04-02T03:05:14+09:00
https://crieit.net/posts/Python-5e7df55255160
序章「ひとりぼっちPython」
<h2 id="雑記"><a href="#%E9%9B%91%E8%A8%98">雑記</a></h2>
<p>巷で話題のPython。<br />
言語別エンジニアの年収グラフでは1位を獲得しており(どっかで見た気がする)、近年話題の飛び交う「機械学習」「AI」「ブロックチェーン」にもよく活用されている言語ということで、とても人気なのが伺えます。<br />
寝る前のお供に時々「日経ソフトウェア」という雑誌を購入しているのですが、ここ最近の表紙を見るとほとんどPythonという文字で埋め尽くされています。</p>
<p>ただ、その人気の裏側というか、実際にどうなのかというのがわからないのが正直なところです。安易に個人で初心者が手を出して痛い目を見るんじゃないのかなあとか。正直ちょっとPythonって敷居が高い印象があります(言語的には比較的取り組みやすそう?)。</p>
<p>で、まあ好奇心に負けて(?)Pythonに少し取り組んでみた訳です。いろんな言語に取り組むのはベストプラクティスでないと怒られそうですが、都合の悪いことは聞かないこととします!!!!!</p>
<p>実際に「機械学習」の基礎の基礎みたいな部分をUdemyで学習してみました。回帰分析を学んでみたのですが、やっぱり面白いのですね。<br />
もともと数学的な分野は好きな部類なので、もうちょっと勉強してみたいなって思いました。</p>
<p>そんなこんなで <strong>「ノンプログラマ」で本業の傍ら趣味と実益を兼ねて学習をしながら 「ひとり開発」 に取り組んでいる人間</strong> が、<strong>Pythonについて取り組んでみたことや興味のあるトピックについて</strong> 知識を広げてみたいと思っているのがこの連載です。</p>
<h2 id="この連載で取り上げたいこと"><a href="#%E3%81%93%E3%81%AE%E9%80%A3%E8%BC%89%E3%81%A7%E5%8F%96%E3%82%8A%E4%B8%8A%E3%81%92%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8">この連載で取り上げたいこと</a></h2>
<p>(1)Pythonでどんなツールが作れるの?どんな身近な課題が解決できるの?<br />
(2)Pythonといえばよく聞く、機械学習・AI・IoT・ブロックチェーンなどの現代の気になるワードについて<br />
(3)個人レベルでどうこれらに取り組める?</p>
<p>これらについて調べたこと、考えたこと、実際に取り組んでみたことを取り上げてトピックにしてみたいなーって。</p>
<h2 id="この連載の目標"><a href="#%E3%81%93%E3%81%AE%E9%80%A3%E8%BC%89%E3%81%AE%E7%9B%AE%E6%A8%99">この連載の目標</a></h2>
<p>pythonに限らずプログラミング自体そうなんですが</p>
<p>「なんとなくできるとカッコいい!」<br />
「とにかくPCに触れているのが好き!」</p>
<p>というだけで学習を続けるのも限界があります。</p>
<p>20代で若ければそれで技術をつけて就職!転職!というストーリーは全然ありで良いのかもしれないのですが、30代に入ってある程度人生が固まってきている自分自身の現状、漫然とやっても限界があるしなあ。と、そんな思いがどうしても出てきてしまいます。</p>
<p>好きでやる、だけじゃ続けられない。<br />
やるからには何か意義のあることをしたいな、って思う訳です。</p>
<p>そうなると大事なのは「出口」かなって思っています。<br />
「Pythonに取り組むことで何ができるんだろう?」<br />
「どんな身近な課題が解決できるようになるのだろう?」</p>
<p>そういうのを模索する過程で、世の中のニュースやよく知らない技術についてもっと理解しつつ、実際に意義のあるものを作れるようになろう、と取り組んでみるのがこの連載の目標です。</p>
<p>読んでくれた方と一緒に勉強していくような、そんな感じを目指しています。</p>
<p>…と、何かちゃんとしたことを言ってしまったのですが<br />
企画倒れになる気がするのでまあ気ままにやりましょう…。</p>
Massa
tag:crieit.net,2005:PublicArticle/15755
2020-03-10T13:41:20+09:00
2020-03-10T14:58:22+09:00
https://crieit.net/posts/GAS-Dropbox-Web-API
GAS&Dropboxを題材にWeb APIについて理解してみる
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>「APIってなんぞや」</p>
<p>そんな疑問をWebアプリ学習に取り組み始めてからずっと抱えていました。<br />
ちょっと調べてみれば教科書的にはいっぱい出てくるのですが、仕組みや具体的にどんなものなのかが全く良くわからない。なんとなく使えるようになると便利そうだなーというくらい。</p>
<p>GASを使っていくつか実際に使ってみてイメージを掴むことができたので、今回はAPIについてどんなものなのか整理してみます。<br />
具体的に「Dropbox API」を例に、言語はGASを使用して「Gmailに送られてきた添付ファイルをDropboxに保存する」という機能を作ってみるという想定の中で「APIとはどんなものか」「APIではどんなことができるのか」「APIを使う時の流れ」についてまとめてみました。</p>
<p>ちなみにこの記事には実際のコードや実装方法はなく、解説のみなのでご了承ください。あくまでAPIのイメージを目的としています。<br />
また、わかりやすく書くことに努めましたが、不正確な点などあるかと思うのでその点もご了承ください。</p>
<p>Dropboxを使ったことない方はすみません、便利なアプリなので是非試してみてください!</p>
<h3 id="対象者"><a href="#%E5%AF%BE%E8%B1%A1%E8%80%85">対象者</a></h3>
<ul>
<li>APIの具体的なイメージを掴みたい人</li>
<li>APIを活用するとどんなことができるようになるのか知りたい人</li>
</ul>
<h2 id="そもそもAPIとは"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82API%E3%81%A8%E3%81%AF">そもそもAPIとは</a></h2>
<p>そもそもAPIとは、一言でいうと「あるソフトウェアが外部からの機能を使えるように共有しているもの」。Application Programming Interfaceの略、なのですが、まあわからない状態だと正直何が何だかです。</p>
<p>今回の例では、Dropboxさんが開発者向けに「この機能を自由に使っていいよー」と公開している機能があり、その機能を外部である僕たち開発者が使わせてもらう形になってます。<br />
その外部に公開している窓口となるのがAPIです。<br />
このAPIにアクセスすることにより、例えば自分のDropboxにファイルを新しくアップロードしたり、入っているファイルを更新したりということがプログラミングで実装することができます。</p>
<p>Dropbox以外にもたくさんのアプリがAPIを公開しています。よく見かけるのが、ログインする際のSNS認証(facebook、twitter、lineアカウントなどでログインできるやつ)やBOTなど(twitterでの自動ツイートやLINEで自動返信されるやつ)。<br />
これらはそれぞれのアプリが提供しているAPIを活用することにより実装されています。</p>
<h2 id="APIを活用するとどんなことができるの?"><a href="#API%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%A8%E3%81%A9%E3%82%93%E3%81%AA%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%AE%EF%BC%9F">APIを活用するとどんなことができるの?</a></h2>
<p>前項で例を書いたように、自分のアプリと外部の色々なアプリを連携させて、より実装の幅を広げることができます。</p>
<p>さらにレベルアップしていくと、自分のアプリの機能の一部をAPIとして外部に共有することもできて、別の開発にも流用できたり他の開発者に使ってもらえたりとできるようです。作ったものを共有することで技術の発展にも繋がるので、このレベルまで行けるととても夢が広がりますね。</p>
<h2 id="APIを使用する際の流れ"><a href="#API%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AE%E6%B5%81%E3%82%8C">APIを使用する際の流れ</a></h2>
<p>APIを使用する際の全体の流れを整理してみました。</p>
<p>(1)ベースとなるアプリのスクリプトを書く:<br />
今回の例の場合、まずはGASで「Gmailの添付ファイルを取得する」という機能を実装するコードを書いておきます。この段階では外部と連携するためのAPIは使わず、GASの機能だけで実装できます(GASではGmailを簡単に扱えちゃいます)</p>
<p>(2)Dropbox側で、APIを使うためのアプリを制作しておく:<br />
次に「ファイルをDropboxに保存する」という機能を実装するための準備として、Dropboxの開発者向けのページからAPIを使うためのアプリを制作しておく必要があります。<br />
そもそもAPIとは外部から一部分の機能を使えるというものでしたね。ここでAPI用のアプリを制作しておくことで、自分専用のアクセストークン(一般にランダムな英数字がズラーっと並んだもの)というものが与えられます。</p>
<p>(3)スクリプトでDropbox APIに機能を使わせてもらうための命令を書く:<br />
「ファイルをDropboxに保存する」というプログラムを書きます。この際に先ほどのアクセストークンを使用してDropbox APIのアプリに「機能を使わせてくださいねー」という命令を送るコードを書くことで、その機能を使うことができるようになります。<br />
今回の例では取得してきた添付のファイルを「Dropboxの指定のフォルダにアップロードする」という機能を、APIを使って実装することになります。<br />
このように外部アプリにAPIを使ってアクセスすることを、専門用語で「APIを叩く」と呼びます。</p>
<p>この全体の流れはDropboxだけでなくどのAPIを活用する場合でも基本的には同じです(多分)。もちろん具体的なコードの書き方やアプリの制作方法に関して変わりますが、やってるとととしてはこういう流れです。</p>
<h2 id="色々な用語集(Coming soon..)"><a href="#%E8%89%B2%E3%80%85%E3%81%AA%E7%94%A8%E8%AA%9E%E9%9B%86%EF%BC%88Coming+soon..%EF%BC%89">色々な用語集(Coming soon..)</a></h2>
<ul>
<li>APIを「叩く」</li>
<li>エンドポイント</li>
<li>ヘッダー情報</li>
<li>アクセストークン</li>
<li>webhook</li>
</ul>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>今回はWeb APIのイメージを掴むために書いてみました。<br />
自分で読み返しても、まだまだふわっとしていて理解が浅いなーと感じますね…。<br />
徐々にこの記事に書き足してもっと理解を深めてみたいと思います。</p>
<p>新しい概念を理解するためには、実際にコードを書いて使ってみて、できることを体感してみるのがまず第一歩ですね。<br />
余裕があれば次回、GASのコードで実際のDropbox APIを使った実装の仕方をまとめます</p>
Massa
tag:crieit.net,2005:PublicArticle/15739
2020-02-29T14:25:03+09:00
2020-03-06T23:34:59+09:00
https://crieit.net/posts/GAS-LINE-bot-5e59f5af2ba2b
GASで「農作業記録アシスタント」LINE bot作った(技術・設計編)
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>制作したLINE botの紹介を前回の記事で書きました。<br />
<a href="https://crieit.net/posts/GAS-LINE-bot">GASで「農作業記録アシスタント」LINE bot作った(紹介編) - Crieit</a></p>
<p>今回はそのために活用した技術や設計に関することをまとめてみます。<br />
具体的なコードの中身やLINE botの制作手順などについては割愛させてもらって、考えてきたことやどんな風に実装したか、また残っている技術的な課題や個人的反省について整理してみました。紹介編と合わせてご覧いただけたらと思います。</p>
<h3 id="使用技術等"><a href="#%E4%BD%BF%E7%94%A8%E6%8A%80%E8%A1%93%E7%AD%89">使用技術等</a></h3>
<ul>
<li>言語はGoogle Apps Script(Rhinoランタイム:Javascript1.6がベース)</li>
<li>LINE messaging API</li>
<li>サーバーレス</li>
</ul>
<h3 id="制作期間"><a href="#%E5%88%B6%E4%BD%9C%E6%9C%9F%E9%96%93">制作期間</a></h3>
<p>約1ヶ月くらい(要件定義から制作まで)<br />
GASの基礎学習を少しずつUdemyで学習(2週間くらい?)</p>
<h2 id="(1)設計に関して"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89%E8%A8%AD%E8%A8%88%E3%81%AB%E9%96%A2%E3%81%97%E3%81%A6">(1)設計に関して</a></h2>
<p>技術の解説に入る前に、アプリ制作のために構想したことを。<br />
カッコつけて設計とは言ってみたものの、不十分だったり設計とは呼ばない内容かもしれませんがご容赦ください。またこんなにはっきり文章化して作り始めてはいません。</p>
<h3 id="①簡易ペルソナ"><a href="#%E2%91%A0%E7%B0%A1%E6%98%93%E3%83%9A%E3%83%AB%E3%82%BD%E3%83%8A">①簡易ペルソナ</a></h3>
<p>農作業の現場作業をしていて記録を簡単に取りたい人。仕事終わりにじっくりと記録をとるのがなかなかめんどくさい人。細かい情報を記入する必要はなく、休憩時間などにちょちょっと記録してちょちょっと見返したり、圃場に出かけた際に簡単に過去の記録を見返したりしたい、というニーズのある自分自身や身近な農業者。</p>
<h3 id="②競合アプリの比較・考察"><a href="#%E2%91%A1%E7%AB%B6%E5%90%88%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E6%AF%94%E8%BC%83%E3%83%BB%E8%80%83%E5%AF%9F">②競合アプリの比較・考察</a></h3>
<p>既存のWEBアプリやスマホアプリは色々あり、農場の認証取得向けのしっかりした大規模なもの、資材管理なども同時にできるもの、農薬の散布制限なども自動で計算してくれるものなど。また個人向けのものや簡単入力ができるものもあるが、ただどうしても入力のところで課題を感じる。</p>
<p><strong>どんなにシンプルなものでも入力のステップが多い</strong><br />
- 毎日・複数回行う入力作業だが、そのステップがどうしても多くなってしまう。最短でもスマホを手に取ってから、①アプリを開いて ②入力ボタンを押して ③フォームに入力して ④投稿を押す<br />
- ものによってはフォーム入力で空欄があると投稿が出来ないものもあったり、画面遷移があるものも。<br />
- プルダウンによる入力も選択肢が多いとかえって不便なことも</p>
<p><strong>入力を習慣づけるのが難しい</strong></p>
<p><strong>初期設定が多く導入のハードルが高いものも</strong><br />
- 初期設定で圃場や所有機械の情報を入力したり使用する資材を入力しておくと便利!というものも多いが、まずそれを設定しないと入力ができないなど導入のハードルが高い。</p>
<p>このようなアプリはしっかり管理したい人にはメリットは多い。ただサクッとメモしたい程度の使い方をするにはストレスを感じる。</p>
<p><strong>一方で既存アプリで参考にしたい点も</strong><br />
- 出力面で、カレンダー表示やカテゴリ毎の表示は必須<br />
- 作業記録のテンプレートをユーザーが用意できて使いまわせるものもあり、それはすごく良い</p>
<h3 id="③このアプリの特徴"><a href="#%E2%91%A2%E3%81%93%E3%81%AE%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E7%89%B9%E5%BE%B4">③このアプリの特徴</a></h3>
<p><strong>入力の手間をとにかく排除</strong><br />
- ①LINEを開いて ②トークルームを開いて ③タップしたら指示に従って選択・入力、の3ステップ。あとは自動で投稿される。<br />
- サクッとメモしたものがそのまま記録される、くらいの手軽さ。twitter投稿くらいの感覚。</p>
<p><strong>日常で使用するツールを使用することで習慣づけがしやすい</strong><br />
- LINEは毎日コミュニケーションツールとして開くし、Googleカレンダーも普段の予定管理に活用しているのでこれらを活用。</p>
<p><strong>シンプルさを保つためにデメリットも</strong><br />
- 画像の登録はできない<br />
- 入力と出力以外の機能が無い(在庫管理などができない)</p>
<h2 id="(2)技術に関して"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89%E6%8A%80%E8%A1%93%E3%81%AB%E9%96%A2%E3%81%97%E3%81%A6">(2)技術に関して</a></h2>
<h3 id="①GAS(Google Apps Script)とは"><a href="#%E2%91%A0GAS%28Google+Apps+Script%29%E3%81%A8%E3%81%AF">①GAS(Google Apps Script)とは</a></h3>
<p>GASとはGoogleが提供するサーバーサイドのスクリプト言語です。Javascriptをベースにしており、Javascriptに既に触れている方や学ぶ予定がある人には習得しやすいかなって思います。(「プログラミング言語」という認識でいるのですが、合ってるのでしょうかね?もし違っていたらご指摘お願いします)<br />
古いJavascriptのバージョンにしか対応していなかったのが難点ですが、つい先日、2020/2/6にはV8ランタイムというものをサポートしたことでECMAScriptに対応し、より現在のJavascriptとの互換性が増したようです。<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-v8-runtime/">(参考リンク1)</a></p>
<p>Google Apps(Googleドキュメント、スプレッドシート、カレンダー、Gmailなど)と簡単に連携することができるので、これらを活用したちょっとしたツールが作りやすいです。Webアプリも作れます。また開発環境のインストールもいらず、Web上でコードを書いて操作するだけで簡単に公開して実行することもできます。</p>
<p>制限はありそうですが「ある程度ならなんでも簡単にできる」という印象で、仕事や趣味・生活などで「自分やチームで使うツールをちょっと作る」というのには最適かなって思います。</p>
<h3 id="②LINE Messaging APIとは"><a href="#%E2%91%A1LINE+Messaging+API%E3%81%A8%E3%81%AF">②LINE Messaging APIとは</a></h3>
<p>今では「LINE公式アカウント」でクーポン情報などを流すお店や企業が増えてますが、アレを作ることができるAPI(Application Programming Interface)です。様々な言語で活用できて、LINEを介して双方向のコミュニケーションをとることができるものを作ることができます。<br />
LINE botとして有名どころでLINEで女子高生AIと会話ができる「<a target="_blank" rel="nofollow noopener" href="https://www.rinna.jp/">りんな</a>」とか。面白いですね。</p>
<h2 id="(3)実装内容"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E5%AE%9F%E8%A3%85%E5%86%85%E5%AE%B9">(3)実装内容</a></h2>
<p>今回のLINE botで実装した内容からいくつか簡単に紹介します。</p>
<h3 id="①[GAS] LINEからの入力に対応"><a href="#%E2%91%A0%5BGAS%5D+LINE%E3%81%8B%E3%82%89%E3%81%AE%E5%85%A5%E5%8A%9B%E3%81%AB%E5%AF%BE%E5%BF%9C">①[GAS] LINEからの入力に対応</a></h3>
<p>バックエンドの処理はGASで記述し、実際にユーザーが使う部分は基本的にLINEになります。この点でまず僕自身でUIを考えてコーディングする必要がなく、機能を制作することに集中することができました。</p>
<p>LINEでユーザーがフォローしたりメッセージを送ったりと何か反応があったら、まず<code>doPost</code>関数が実行されます。そこからLINEアクションの種類(followだったりmessageだったりpostbackだったり)によって処理を分岐します。</p>
<p>GASでGoogleサービス以外の外部のWeb APIを使うためには、<code>UrlFetch</code>サービスの<code>fetch</code>というメソッドを活用します。これによりHTTPリクエストを送信してWeb APIを叩くことができます。</p>
<p>またLINEでの日報入力の際にユーザーが段階的に入力できるようにするために、GASのキャッシュサービスを活用しています。1つ処理を行ったら<code>Cache</code>オブジェクトに一時的にデータを保存しておくことで、ユーザーからのアクションをフラグで管理して分岐するようにしています。</p>
<h3 id="②[GAS] 日報をGoogleカレンダー・スプレッドシートへ書き込み"><a href="#%E2%91%A1%5BGAS%5D+%E6%97%A5%E5%A0%B1%E3%82%92Google%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E3%83%BB%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%B8%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF">②[GAS] 日報をGoogleカレンダー・スプレッドシートへ書き込み</a></h3>
<p>GASではGoogleの各サービスにアクセスするためのクラスが用意されています。<br />
- GmailAppクラス:Gmailを操作操作する<br />
- DocumentApp:Googleドキュメントを操作する<br />
- Spreadsheetクラス:Googleスプレッドシートを操作する<br />
- CalendarAppクラス:Googleカレンダーを操作する</p>
<p>これらは先程の外部APIとは違ってGASから使える内部サービスのため、何の準備なしに使うことができます。<br />
これらを利用して日報を専用のカレンダーとスプレッドシートに書き込む処理を作りました。</p>
<p>注意点としては、自分のGoogleアカウントに紐づいているGoogleカレンダーとしか連携できないことです。つまり、僕のアカウントでスクリプトを作成して、そのスクリプト上で他のユーザーが制作したカレンダーに書き込むことができないのです(スプレッドシートなら可能な様子)。</p>
<h3 id="③[GAS] カテゴリをユーザーがカスタマイズ:Webアプリの作成(HTML Service)"><a href="#%E2%91%A2%5BGAS%5D+%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%82%92%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%8C%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA%EF%BC%9AWeb%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90%EF%BC%88HTML+Service%EF%BC%89">③[GAS] カテゴリをユーザーがカスタマイズ:Webアプリの作成(HTML Service)</a></h3>
<p>毎回の入力の手間を省くために「作業カテゴリ」というものを用意しています。<br />
LINEから日報を入力する際にが選択肢(LINEのクイックリプライ機能)として選べるようにしたのですが、この内容をユーザーが自由に編集できるようにしました。</p>
<p>データの保管場所としてスプレッドシートでセルを用意しており、そのデータを取得することで作業カテゴリを配列で取り出せるようにしています。</p>
<p>またユーザーがカテゴリを編集しやすくするために、その部分だけWebアプリとして制作してみました。要件定義の段階では特に作る気はなく、スプレッドシートから直接編集できるからいいやと思っていたのですが、スプレッドシートに慣れていないユーザーだと編集しずらいだろうなと考えたためです(初期設定のハードル)<br />
これにはGASの<code>HtmlService</code>を活用して、最低限のHTMLの知識があれば簡単に制作することができます。</p>
<p>Webページには編集フォームとスプレッドシートに設定されている作業カテゴリが表示されているだけのシンプルなものです。<br />
Webページにアクセスするとまず<code>doGet</code>関数が実行されます。またフォームで作業カテゴリを編集して送信した場合は<code>doPost</code>関数が実行されます。<code>doPost</code>関数ではLINEからのアクションなのかWebからのカテゴリ変更処理なのかをイベントオブジェクト<code>e</code>の中身から判断して、Webからの場合なら送られてきた内容をスプレッドシートの作業カテゴリのセルに上書きする、というような処理を行っています。</p>
<h3 id="④[LINE] リッチメニューの作成"><a href="#%E2%91%A3%5BLINE%5D+%E3%83%AA%E3%83%83%E3%83%81%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%81%AE%E4%BD%9C%E6%88%90">④[LINE] リッチメニューの作成</a></h3>
<p>LINE messaging APIで一番理解が難しかったのが、LINE messaging APIを活用したリッチメニューの制作方法でした。</p>
<p>リッチメニュー制作にはLINE公式アカウントからの設定方法とmessaging APIを活用する方法の二種類があります。<br />
<a target="_blank" rel="nofollow noopener" href="https://developers.line.biz/ja/docs/messaging-api/using-rich-menus/">リッチメニューを使う | LINE Developers</a></p>
<p>LINE公式アカウントからの制作はとても簡単で、画像を用意さえしておけばブラウザ上で操作するだけで簡単にリッチメニューを作成してLINE botで活用できます。プログラムを書かなくとも誰でも直感的に作ることができるのですが、メニューボタンの配置を細かく決めることができなかったりアクションの種類が限られていたりと、できることに制限があります。</p>
<p>それでもちょっとしたものなら十分なのですが、LINE messaging APIを使うことでもうちょっと手の込んだ、例えばゲームコントローラーのようなボタン配置などをプログラムに落とし込んで作ることができます。</p>
<p>最初に公式アカウントで制作したところ一瞬でできて感動したのですが、魔が差してmessaging APIを活用する方法で実装し直すことにしました(コードで完結できればメンテナンス性も高い?という考えも)。ところがhttpリクエストやRESTの理解が浅かったため自力で実装するのが困難でした。<br />
この実装にはこちらのHASEKATSU様のnote<a target="_blank" rel="nofollow noopener" href="https://note.com/_hasekatsu/n/n83afa5ffee56">(参考リンク2)</a>を参考にさせていただきました。<br />
一応実装はできましたが、どう言う仕組みで実装できているのかまだちゃんと自分の言葉で説明できないので課題です。</p>
<h2 id="(4)技術的な課題"><a href="#%EF%BC%88%EF%BC%94%EF%BC%89%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AA%E8%AA%B2%E9%A1%8C">(4)技術的な課題</a></h2>
<p>紹介編にも書いたのですが、データベースの一意性がありません。これはほぼ自分用とはいえかなりの欠陥。<br />
一度入力した日報をスプレッドシートやカレンダーから修正できるのですが、それがもう一方に反映される仕組みが作れていないのです。誤って日報登録してしまうことも多々あるので、一度登録したものを安心して編集・削除できる仕組みが必要なところ。</p>
<p>この部分を解決するためにまず一番最初に考えたのが、Webアプリとしてスプレッドシートの内容を取り出して編集できるようにするというやり方。ところがこれ以上Webアプリ部分に力を割くのは、手間がかかる割にはどうにもGASで制作する意味が薄いように感じます(PHPやRubyでデータベースを用意して作る方が発展性がありそう)。</p>
<p>手軽さとGoogleのサービスとの連携が簡単というGASの良さを引き出すなら、スプレッドシート上で編集するのが良さそうです。と思って調べてみると、値の変更があった際に発動するトリガーがGASで設定できるとのこと。スプレッドシート上の日報履歴が編集・削除されたら、対応したGoogle Calendarのイベントを書き換える…ということが理論上できそうです(Calendarのイベント固有のIDでデータベースを管理する)。</p>
<p>一方でGoogleカレンダーの方で修正したいこともあるのですが、こちらがなんだか一手間二手間かかりそうです。これもGASのトリガー機能を使うことで、カレンダーの編集があったらスプレッドシートにもデータが反映されるようにすれば可能かなと思えたのですが、それにはGASのCalendarAppサービスだけでは出来ない様子。calendar APIという外部APIを使えうことで、なんとかできなくもなさそうな感じです。</p>
<p>というわけで、技術的な課題は現状あるもののクリアできそうです。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>今回の制作では、初めて念願のAPIというものを活用することができました。<br />
RailsでWEBアプリ開発を学んでいた時には、エラーの対処やフロントエンド作り、環境構築などでいっぱいいっぱいでしたが、デバッグの感覚を学び、今回は比較的とっつきやすい技術を選んで目標を最小限にしたことで、余裕を持って制作できたと思います。</p>
<p>反省としては、GASやLINE botの仕様を把握出来ていなくて、ユーザーに活用してもらうにあたり移行作業に手間がかかったことです。<br />
自分以外のアカウントのGoogleカレンダーへの書き込みが出来ないことに完成間際で気づいて、どうやって他ユーザーに使ってもらうかを考え直すことになりました。結局身近で興味を持ってくれた仲間限定ということで、僕が新規作成したカレンダーを共有して使用してもらう方法をとることに。<br />
また、人数分のLINE botを用意しなければならずそれに対応した使い回しのきくスクリプトに書き換える作業が発生してしまいました。<br />
完成したぜ!という段階からだいぶうだうだやってたのがもったいない。</p>
<p>今後の方針としては、もうちょっとGASでこのアプリの改善と日常で必要なアプリを作りつつ他のAPIの活用のやり方も学んでみたいなーと思っています。</p>
<h2 id="MEMO"><a href="#MEMO">MEMO</a></h2>
<p>僕の過去記事でGASとLINE messaging API学習に役立った教材・ブログをまとめてあります。<br />
もしこれから取り組む方がいましたら参考にしていただけたら嬉しいです!<br />
<a href="https://crieit.net/posts/GAS-LINE-messaging-API">GAS & LINE messaging APIの学習便利帳 - Crieit</a></p>
<h3 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h3>
<ul>
<li>参考リンク1<br />
<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-v8-runtime/">祝!Google Apps Scriptが「V8ランタイム」をサポート!モダンなECMAScript構文が使えるようになった</a></li>
<li>参考リンク2<br />
<a target="_blank" rel="nofollow noopener" href="https://note.com/_hasekatsu/n/n83afa5ffee56">Postmanでリッチメニューを設定する方法【LINE Messaging API×GAS】|HASEKATSU|note</a></li>
</ul>
<p>またこちら、本文には特記してないのですが、使用言語としてGASを検討する際の参考に。<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/mistolteen/items/c206d5970ae1a6c7813d">GASを使うべきか否かの判断材料 - Qiita</a></p>
Massa
tag:crieit.net,2005:PublicArticle/15738
2020-02-27T23:28:56+09:00
2020-03-06T23:45:06+09:00
https://crieit.net/posts/GAS-LINE-messaging-API
GAS & LINE messaging APIの学習便利帳
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>ノンプログラマの僕がGAS(Google Apps Script)とLINE messaging APIを活用したツールを制作するにあたり、全くよくわからない状態から何となくそれらしいものが作れるようになるまでに学習した、主な教材とブログをまとめておきます。<br />
これからGASを習得したい! LINE botを制作したい! という方の情報収集の参考にしてもらえたら嬉しいです。</p>
<h2 id="(1)GASの習得のために"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89GAS%E3%81%AE%E7%BF%92%E5%BE%97%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AB">(1)GASの習得のために</a></h2>
<h3 id="Udemy教材"><a href="#Udemy%E6%95%99%E6%9D%90">Udemy教材</a></h3>
<p>●<a target="_blank" rel="nofollow noopener" href="https://www.udemy.com/course/gas_for_operational_efficiency/">ビジネスパーソンに贈る業務効率化大全 〜Google Apps Scriptによる業務の自動化〜 | Udemy</a></p>
<p>もはやGAS入門の決定版と言いたい。実際に業務効率化にすぐに活用できるスクリプトを書きながら、GASの基本から段階を追って体系的に習得できます。<br />
とても解説がわかりやすいし、一通りの必要な操作(Googleカレンダー、Googleフォーム、Googleメール、LINEやSlackへの通知、スプレッドシート・ドキュメントの扱いなど)を実戦形式で学べます。<br />
最後は駆け足ながらGASでのWebスクレイピングの講座もあるので、これを足がかりにもっと深く学びたくなりました。</p>
<h3 id="ブログ"><a href="#%E3%83%96%E3%83%AD%E3%82%B0">ブログ</a></h3>
<p>●<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/">いつも隣にITのお仕事 | 毎日の業務が楽チンに!</a></p>
<p>書籍「詳解!GoogleAppsScript完全入門」の著者さま。こちらも基本から解説してくれていて文章がとてもわかりやすく、コードだけでなくプログラミング共通の基本の考え方に通じる学びがあります。<br />
作業をしていると「GAS + やりたいこと」「Javascript + やりたいこと」で検索して解決することが多いのですが、その際によくヒットしてきます。<br />
GASのPropaty Serciceで秘匿情報を隠すという考え方と実際のやり方はこちらで学びました。この初心者向けGASのシリーズで体系的にGASを学ぶことができるのも面白そうです。<br />
ちなみに僕は次にこちらに連載されている「Gmailの添付ファイルをGoogleドライブに自動保存する」というツールの作成に挑戦してみようと思っています。</p>
<p>→ <a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-property-store/">【初心者向けGAS】プロパティストアの概要とスクリプトプロパティの編集方法</a><br />
→ <a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-gmail-attachment-drive/">Google Apps ScriptでGmailの添付ファイルをGoogleドライブに保存する</a></p>
<h2 id="(2)GASでLINE messaging APIを操作するために"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89GAS%E3%81%A7LINE+messaging+API%E3%82%92%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB">(2)GASでLINE messaging APIを操作するために</a></h2>
<h3 id="Udemy教材"><a href="#Udemy%E6%95%99%E6%9D%90">Udemy教材</a></h3>
<p>●<a target="_blank" rel="nofollow noopener" href="https://www.udemy.com/course/line-bot-x/">はじめてのLINEBOTの作り方 秘書ボットを作ろう | Udemy</a></p>
<p>こちらはGASでLINE botを制作するプロセスを学べる講座。<br />
コードの解説ももちろんありますが、それよりも初めてLINE botを作る際にサンプルコードを活用して「動くものを作って流れを体感する」という部分でとても勉強になりました。<br />
基本の「おうむ返しボット」(ユーザーがLINEで投稿したメッセージをそのまま返答する)からスタートして、Googleカレンダーと連携して予定を追加したり毎日決まった時間に予定をLINEに通知してくれる「秘書ボット」を作成するという講座。作成したボットはそのまま自分でも活用しています。</p>
<p>僕はLINE bot制作にあたってこの講座を一番最初に学んだのですが、それによりLINE messaging APIの動きを体感することができました。<br />
細かいコード部分はこの講座だけだと理解が難しいのですが、作りたいものを作っていく過程で改めてこのサンプルコード読み解いていくという作業を行いました。<br />
GASのCache Serviceというものを利用してLINE botの処理を分岐する方法はこの講座で知ることができました。</p>
<h3 id="ブログ"><a href="#%E3%83%96%E3%83%AD%E3%82%B0">ブログ</a></h3>
<p>●<a target="_blank" rel="nofollow noopener" href="https://arukayies.com/">30歳からの「くら」のブログ</a></p>
<p>LINE messaging APIをGASのコードに落とし込むための書き方をだいぶ参考にしました。<br />
特に「日時選択アクション」「クイックリプライ」「テンプレートメッセージ」などを使うためのシンプルなコードが紹介されているので、公式ドキュメントだけだとどうGASで書いたら良いかわからなかったものも実際にコードに落とし込むことができます。</p>
<p>●<a target="_blank" rel="nofollow noopener" href="https://note.com/_hasekatsu">HASEKATSU|note</a></p>
<p>GAS + LINE botに特化して数年にわたり独学を続けてきた血と涙と汗の結晶を感じます!<br />
これまでに学んできた大量のコードを公開&販売しつつ、noteの記事では惜しみなく実際のコードと積み上げてきた情報を出して解説しています。フレームワーク的なものも自作していてやばい。これからも楽しみです。</p>
<p>今回何よりも僕が助かったのが、リッチメニューをLINE公式アカウントからではなくLINE messaging APIを使ってGASのスクリプトで実装するやり方です。これは他にほとんど情報がなく、大いに頼ることになりました。<br />
またCoconaraでのサービス提供の実績もあり、実践的に色々試行錯誤している姿勢を僕も学んでトライしていきたいと思います。</p>
<p>→ <a target="_blank" rel="nofollow noopener" href="https://note.com/_hasekatsu/n/n83afa5ffee56">Postmanでリッチメニューを設定する方法【LINE Messaging API×GAS】|HASEKATSU|note</a><br />
→ <a target="_blank" rel="nofollow noopener" href="https://note.com/_hasekatsu/n/n37fc2985aa0d?creator_urlname=_hasekatsu">GASでLINE Messaging APIのリッチメニューオブジェクトをGoogleスライドから作成する【実戦GoogleAppsScriptプログラミング講座~天空闘技場~】|HASEKATSU|note</a><br />
→ <a target="_blank" rel="nofollow noopener" href="https://note.com/_hasekatsu/n/n66277fe34638?creator_urlname=_hasekatsu">GASでLINE Messaging APIのリッチメニュー画像を反映させる【実戦GoogleAppsScriptプログラミング講座~天空闘技場~】|HASEKATSU|note</a></p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>以上です。<br />
入門(何も知らない段階)〜初級(基礎を理解する段階)〜中級(実際に制作する段階)に到るまで、これらの教材とブログ記事で学べばかなりいいところまで行けると思います。<br />
自分がこれまで学ばせていただいた感謝の意も込めて。ありがとううう(そしてこれからもよろしくお願いします)</p>
Massa
tag:crieit.net,2005:PublicArticle/15736
2020-02-26T12:37:47+09:00
2020-02-29T14:45:42+09:00
https://crieit.net/posts/GAS-LINE-bot
GASで「農作業記録アシスタント」LINE bot作った(紹介編)
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>エンジニアでもなんでもないただの畑作農家が、普段の農作業の記録を簡単に記録するためのLINE botを作りました。<br />
今回はその紹介編です!</p>
<p><strong>(2020/2/29追記)技術編もまとめました。</strong><br />
<a href="https://crieit.net/posts/GAS-LINE-bot-5e59f5af2ba2b">GASで「農作業記録アシスタント」LINE bot作った(技術編) - Crieit</a></p>
<p>農家にとって細かく作業の記録を残しておくことは大事で、作業ごとに把握しておくべき項目が多いのです。<br />
例えば種蒔き。年に1回(ほんの数日間)しか行わない作業ですが、種子を落とす量・肥料の銘柄と量・それに合わせた機械のセッティングと走る速度など、細かな調整がその年の収量(=収入)を左右します。</p>
<p>また複数の作物を管理していると、定期的に農薬を散布するなど天気を見ながら計画を立てることになるので、前回いつ・どんな薬を撒いたか、などを把握しておくことが大事になります。数年前の記録を引っ張り出してきたい時もあります。</p>
<p>メモしておかないと、だいたい1年経つと全て忘れます\(^o^)/</p>
<p>ただ、メモするのがめんどくさいんですよね。<br />
紙の日誌だと多く情報を残せますが、検索性に欠けるし。<br />
必要な時に必要なものだけちゃちゃっと記入して、必要な時に必要なものだけちゃちゃっと取り出せる、そんなツールが欲しいなあと思っていました。</p>
<p>皆さんおそらく同じように悩まれていることで、農作業日誌アプリにはスマホアプリやWEBアプリが既に多々存在します。農家の経営を助けるために、いろんな方がいろんな想いを込めて作ってくれています。<br />
ところが、僕にはどうも上手く使いこなせないのでした。実際に使ってみても中々しっくりくるものがなかったというのが正直なところで。</p>
<p>まあ、そんなんで、作りました。<br />
自分が普段メモ書きなどでも使っているLINEと予定管理に使っているGoogle Calendarに着目して、入力の手間を極限まで省いた農作業記録アプリをLINE botで作成。</p>
<h2 id="こんなアプリ"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%82%A2%E3%83%97%E3%83%AA">こんなアプリ</a></h2>
<p>LINEを開いてメニューをタップしたら入力開始。メッセージに従って「日時」「作業カテゴリ」を選択した後にチャットで作業内容を入力。</p>
<p>入力した内容は指定のGoogleカレンダーとスプレッドシートに自動で登録されるので、それらのツールで登録した記録を見返すことができます。</p>
<div class="iframe-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/G7_aKwVEjTs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>
<h3 id="(1)LINEを開いて簡単に今日の作業日報を入力"><a href="#%EF%BC%88%EF%BC%91%EF%BC%89LINE%E3%82%92%E9%96%8B%E3%81%84%E3%81%A6%E7%B0%A1%E5%8D%98%E3%81%AB%E4%BB%8A%E6%97%A5%E3%81%AE%E4%BD%9C%E6%A5%AD%E6%97%A5%E5%A0%B1%E3%82%92%E5%85%A5%E5%8A%9B">(1)LINEを開いて簡単に今日の作業日報を入力</a></h3>
<p>作業の日報入力を専用のアプリでやるのではなくLINEで入力するというのが肝。<br />
コミュニケーションツールとしてLINEを開かない日は無い、という人が多いかと思います。LINEを立ち上げる度に嫌でもこのbotのアイコンが目に入るため、思い出した時にちゃちゃっと入力をしてやればいいのです。</p>
<p><strong>↓メニューの左「日報を入力」をタップすると、日報登録のボタンが出てくる</strong><br />
<a href="https://crieit.now.sh/upload_images/704d5676a24bb5091ddb305676ac996f5e55e34d74b65.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/704d5676a24bb5091ddb305676ac996f5e55e34d74b65.png?mw=700" width="200px"></a></p>
<p><strong>↓日時を選択して登録できるので昨日の記入忘れも安心</strong><br />
<a href="https://crieit.now.sh/upload_images/6c48c898f6f32a4537bb20b6d6ab9a2f5e55e3ed0e142.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6c48c898f6f32a4537bb20b6d6ab9a2f5e55e3ed0e142.png?mw=700" width="200px"></a></p>
<p><strong>↓作業カテゴリは選択肢を選ぶだけ</strong><br />
<a href="https://crieit.now.sh/upload_images/b1e66934331753526993140f48a0ac695e55e594c7429.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b1e66934331753526993140f48a0ac695e55e594c7429.png?mw=700" width="200px"></a></p>
<p><strong>↓作業内容を入れる時だけテキスト入力が必要</strong><br />
<a href="https://crieit.now.sh/upload_images/99c8c1d24bccead863d92d4477857f245e55e3fc1eb2b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/99c8c1d24bccead863d92d4477857f245e55e3fc1eb2b.png?mw=700" width="200px"></a></p>
<p><strong>↓登録できました!</strong><br />
<a href="https://crieit.now.sh/upload_images/630219fae38678f3d7061d6f86c50f5d5e55e4fac84df.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/630219fae38678f3d7061d6f86c50f5d5e55e4fac84df.png?mw=700" width="200px"></a></p>
<p>また、LINEを使うと過去にあげた日報の履歴がLINE上からも参照できるというのは何気に便利。コピペしてきて文面を使い回して使用しても◎</p>
<p>ただ、LINEからの一度登録した予定の編集・削除は現状備わっておりません。手動でカレンダーとスプレッドシートで編集する必要があります。</p>
<h3 id="(2)Googleカレンダーに記録して見返す"><a href="#%EF%BC%88%EF%BC%92%EF%BC%89Google%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E3%81%AB%E8%A8%98%E9%8C%B2%E3%81%97%E3%81%A6%E8%A6%8B%E8%BF%94%E3%81%99">(2)Googleカレンダーに記録して見返す</a></h3>
<p>こちらも日常使いのツールをそのまま使用。<br />
予定管理を全てGoogleカレンダーで行なっているので、スマホのウィジェットとして登録しておくことで、作業履歴もそこに表示してくれることになります。<br />
他のカレンダーアプリにGoogleカレンダーを連携して使うこともできそうですよね。僕の周りでは「Time Tree」を予定管理に使っている人が多いのですが、どうやら連携が可能そうです(僕自身使ってないので正確にはわからず)。</p>
<p><a href="https://crieit.now.sh/upload_images/5fa9ec01f71efe346deaf216045b13835e55e63397e81.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5fa9ec01f71efe346deaf216045b13835e55e63397e81.png?mw=700" width="200px"></a></p>
<h3 id="(3)スプレッドシートでも記録を管理"><a href="#%EF%BC%88%EF%BC%93%EF%BC%89%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%A7%E3%82%82%E8%A8%98%E9%8C%B2%E3%82%92%E7%AE%A1%E7%90%86">(3)スプレッドシートでも記録を管理</a></h3>
<p>これはおまけというか、主にパソコン上で過去の履歴を一覧にして参照したい時があるので、そのために作りました。ここは比較的アナログ感が強いのですが、参照したい時はスプレッドシート上で操作をします。フィルタで特定の作業カテゴリのみを一覧表示にしたり、全体を検索をかけたり。ここをWEBアプリでもうちょっとしっかりすると良い感じがします。</p>
<p><a href="https://crieit.now.sh/upload_images/3de1a13f3cacaba3532e84c82897f5bd5e55e69a160f0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3de1a13f3cacaba3532e84c82897f5bd5e55e69a160f0.png?mw=700" width="400px"></a></p>
<h3 id="(4)作業カテゴリをユーザーがカスタマイズ"><a href="#%EF%BC%88%EF%BC%94%EF%BC%89%E4%BD%9C%E6%A5%AD%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%82%92%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%8C%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA">(4)作業カテゴリをユーザーがカスタマイズ</a></h3>
<p>日報入力時に作業カテゴリを選択肢として出るようにしているのですが、これはユーザーによって色々変わってくると思います。専用のWEBアドレスにアクセスすると、そこからユーザー自身でカテゴリの編集を行えるようにしています(ここはWEBアプリ)</p>
<p><a href="https://crieit.now.sh/upload_images/c67771d31977854f9b49d34c1e2abb6c5e55e7102f475.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c67771d31977854f9b49d34c1e2abb6c5e55e7102f475.png?mw=700" width="300px"></a></p>
<h2 id="現状の課題とこれからの展望"><a href="#%E7%8F%BE%E7%8A%B6%E3%81%AE%E8%AA%B2%E9%A1%8C%E3%81%A8%E3%81%93%E3%82%8C%E3%81%8B%E3%82%89%E3%81%AE%E5%B1%95%E6%9C%9B">現状の課題とこれからの展望</a></h2>
<p>あくまで自分が欲しいものを作ったのですが、その延長で「人に使ってもらいフィードバックをもらう」ところまでは今回ずっと考えて作ってきました。身近な同業者に使ってもらえることになったので、反応が楽しみです。</p>
<p>ひとつ大きな課題としては、データの編集・削除に問題があります。<br />
カレンダーとスプレッドシート両方を編集する必要があるので、単に不便なだけでなくデータの一意性が保てていません。ここはもうちょっと考える必要あり。</p>
<p>自分専用で使うなら最低限の機能を持ったアプリが完成した感じなので、これで満足といえば満足です。<br />
仮に発展が望めるなら、別言語でサーバーを準備して本格的にWEBアプリ化?スマホアプリ化?</p>
<p>もうすぐ春から本格的に始まる農作業で実際に活用しつつ、使ってもらえる方の反応を聞きながら、今後の展開を考えていきたいと思います。</p>
<h2 id="おわりに:農作業記録アシスタントLINE botを使ってみたいという方へ"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB%EF%BC%9A%E8%BE%B2%E4%BD%9C%E6%A5%AD%E8%A8%98%E9%8C%B2%E3%82%A2%E3%82%B7%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%88LINE+bot%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F%E3%81%84%E3%81%A8%E3%81%84%E3%81%86%E6%96%B9%E3%81%B8">おわりに:農作業記録アシスタントLINE botを使ってみたいという方へ</a></h2>
<p>残念ながら一般公開はできません。ただ、もし「使ってみたい!!!」というアツいお方がもしいらっしゃいましたら、是非ぜひお声がけいただけたら嬉しいです。システム的には農業に限らず業界関係なく使用できると思います。また、</p>
<ul>
<li>Googleアカウントが必要(Gmailアドレス)</li>
<li>自分専用(複数人での使用は登録処理がおそらく無理。カレンダー共有は可)(とは言いつつも少人数のメンバーが固定してるグループならLINE botを人数分立てていけるのかも)</li>
<li>登録情報の秘密性には欠ける(僕の方で参照・編集することができてしまう)</li>
</ul>
<p>この点だけご注意を願いします。テストユーザーとして色々遊んでもらって、使い勝手などご意見いただきたい気持ち。<br />
LINE botを個別に作成する必要があるため多くの対応は難しいですが、まあそんなことはないと思うので、何か気軽にお話いただけたらとても喜びます。</p>
<h2 id="MEMO"><a href="#MEMO">MEMO</a></h2>
<p>使用技術等はこんな感じ(別記事で詳しく書く予定です)<br />
- 言語はGoogle Apps Script(GAS)<br />
- LINE messaging API 使用</p>
<p>サーバーレス。<br />
Googleカレンダーとスプレッドシートを連携。WEBアプリ部分はGASのHTML Serviceを活用して作成。</p>
<p>また、今回のLINE botのアイコンイラストは、こちらの「ねこぺんね」様の素材を使用させていただいてます。ありがとうございます!<br />
<a target="_blank" rel="nofollow noopener" href="https://www.ac-illust.com/main/detail.php?id=1624420&word=%E9%A1%94%E3%81%A4%E3%81%8D%E9%87%8E%E8%8F%9C">顔つき野菜イラスト - No: 1624420/無料イラストなら「イラストAC」</a></p>
Massa
tag:crieit.net,2005:PublicArticle/15714
2020-02-11T00:26:28+09:00
2020-07-13T22:02:25+09:00
https://crieit.net/posts/GAS-API-ID
【GAS】コードにAPIトークンやIDのベタ書きを避ける!(プロパティサービスの活用)
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p><a href="https://crieit.net/posts/GitHub-GAS">GASでの開発でGithubリポジトリと連携する方法について</a>書きましたが、リポジトリにプッシュする前に、一般に公開しないほうが良い情報が入っていないか気をつける必要があります。例えば、</p>
<ul>
<li>APIトークン</li>
<li>メールアドレス</li>
<li>なんかのパスワード</li>
<li>特定のIDやURL</li>
</ul>
<p>これまでコードにLINE messanger APIのチャンネルアクセストークンや自身のメールアドレス、LINEのIDなどをベタ書きしており、Githubのリポジトリにアップする際にどうするのが正しいのか悩みました。リポジトリをprivateに設定すれば大丈夫かな?とかやってみたんですが、なんか落ち着かないしスマートじゃない気が。</p>
<p>調べてみると、どうやらGASでは「プロパティ」として公開したくない情報を管理できるやり方があるようです。<br />
またメンターさんに教えていただいたところによると、一般的には秘匿情報を</p>
<ul>
<li>プロパティファイルに記述する</li>
<li>環境変数に入れる</li>
</ul>
<p>とするのが対策になってくるようです。どの言語でも関係なくこの考え方は理解しておく必要がありそうですね(個人の趣味レベルでもセキュリティ意識は大事だと思う)。</p>
<p>というわけで今回はGASのプロパティサービスを活用することで対応してみたので、それについてまとめてみました。</p>
<h2 id="GASのプロパティサービスとは"><a href="#GAS%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A8%E3%81%AF">GASのプロパティサービスとは</a></h2>
<p><strong>イメージ</strong></p>
<ul>
<li>APIトークンやメールアドレスなど一般に公開したくない情報をコードに書く必要がある!</li>
<li>そんな時にコードにこれらの情報をベタ書きせず別の場所(プロパティストア)に入れておいて、コードではそこから情報を取り出す命令だけ書いておく(プロパティサービス)。</li>
<li><p>これによって隠したい情報を見れないようにして安全に管理できるよ!</p>
<p>用語<br />
ざっくり解説</p>
<p>プロパティストア<br />
IDなどの公開したくない情報を格納しておく場所のこと。情報に名前をつけて格納しておくことができる。権限がないと中身を見ることはできない。</p>
<p>プロパティサービス<br />
プロパティストアに格納した情報をGASスクリプトから取り出したり編集したりすることができる仕組み。PropatiesServiceやPropertiesなどをコードに書くことで色々できる。</p></li>
</ul>
<p>誤解を恐れずわかりやすさ重視でまとめるとこんなイメージです。<br />
(詳細は<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-property-store/">参照リンク1</a>がとてもわかりやすく大いに参考にさせていただきました)</p>
<p>ちなみにプロパティストアにも「スクリプトプロパティ」「ユーザープロパティ」「ドキュメントプロパティ」と3種類ありますが、詳しくはここでは割愛。初めのうちは「スクリプトプロパティ」だけを考えて大丈夫でしょう。</p>
<h2 id="実装手順"><a href="#%E5%AE%9F%E8%A3%85%E6%89%8B%E9%A0%86">実装手順</a></h2>
<p>具体的に実装してみます。とってもかんたん。</p>
<h3 id="1.問題のコード"><a href="#%EF%BC%91%EF%BC%8E%E5%95%8F%E9%A1%8C%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89">1.問題のコード</a></h3>
<p>例えばこういうコード。</p>
<pre><code>var ACCESS_TOKEN = "hogehoge1234......";
var calendar = CalendarApp.getCalendarById("hogehoge");
</code></pre>
<p>このAPIトークンの中身(hogehoge1234......の部分)はGithubに公開したりブログに書いちゃったりすると誰かに使われてしまう可能性がありそうです。<br />
またカレンダーID(hogehoge)も個人情報なので出来れば隠したいですね。</p>
<h3 id="2.プロパティを登録する"><a href="#%EF%BC%92%EF%BC%8E%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B">2.プロパティを登録する</a></h3>
<p>詳細は<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-property-store/">参照リンク1</a>を参考に。</p>
<p>(1)GASのスクリプトエディタのメニューで、「ファイル」>「プロジェクトのプロパティ」を開く<br />
(2)「スクリプトのプロパティ」タブを選択<br />
(3)「+行を追加」をクリック<br />
(4)左の欄に値を取り出す時に使う名前(キー)、右の欄にAPIトークンなどの値を入れて「保存」をクリック</p>
<p><img width="734" alt="スクリーンショット 2020-02-08 22.14.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499640/cfa64e88-4e4b-eed3-a126-069850ee2b9d.png"></p>
<p>この名前の付け方は何でも良いのですが、一目で何を意味しているのかわかる名前にするのが良いですね。<br />
ここでは「アクセストークン」と「カレンダーID」をそれぞれ登録しておきましょう(参考画像はちょっと違うプロパティを登録してますがすんません)。</p>
<p>※(余談)変数に使用する記号やキャメルケースorスネークケースなどあるので、一貫した命名規則を簡単に学んでおくと名付けで迷うことが無くなるかと思います。<br />
僕はJavascriptの命名規則の一つとしてざっくり「変数名はキャメルケース」「定数名はスネークケース」と学んだので、今回のプロパティは定数と考えてスネークケースで名付けてあります(詳しくは調べてみてね)。</p>
<h3 id="3.登録したプロパティをコードに記述する方法"><a href="#%EF%BC%93%EF%BC%8E%E7%99%BB%E9%8C%B2%E3%81%97%E3%81%9F%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E8%A8%98%E8%BF%B0%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95">3.登録したプロパティをコードに記述する方法</a></h3>
<p>詳細は<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-properties-script-property/">参照リンク2</a>を参考に。</p>
<p>プロパティストアの登録が終わったら、問題のコードを書き換えてみます。</p>
<pre><code>var ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty("ACCESS_TOKEN");
var calendar = PropertiesService.getScriptProperties().getProperty("CALENDAR_ID");
</code></pre>
<p>最初のコードと全く同じ内容になるのですが、うまく情報を隠して記述することができました!<br />
ただこれだと少し冗長なので次のようにスッキリと書くこともできます。<br />
(プロパティが2つだけだと余計長くなってる感じですが…。プロパティを複数使う場合はだいぶスッキリするはずです)</p>
<pre><code>var prop = PropertiesService.getScriptProperties().getProperties();
var ACCESS_TOKEN = prop.ACCESS_TOKEN;
var calendar = CalendarApp.getCalendarById(prop.CALENDAR_ID);
</code></pre>
<h2 id="解説・補足"><a href="#%E8%A7%A3%E8%AA%AC%E3%83%BB%E8%A3%9C%E8%B6%B3">解説・補足</a></h2>
<p>最終的に書き換えたコードの解説をざっくりとしてみます。</p>
<pre><code>var prop = PropertiesService.getScriptProperties().getProperties();
</code></pre>
<p>まずプロパティサービスを使うためには、<code>PropertiesService</code>というクラスを使用します。<br />
次に<code>getScriptProperties</code>というメソッドを使用して、プロパティストアに登録したスクリプトプロパティ達を取得します。そこから<code>getPropaties()</code>というメソッドを使うことで、変数<code>prop</code>にオブジェクトとしてプロパティ達をセットしています。<br />
(この<code>prop</code>には登録したプロパティ(キーと値のセット)が全部入っている、というイメージですかね)</p>
<pre><code>var ACCESS_TOKEN = prop.ACCESS_TOKEN;
var calendar = CalendarApp.getCalendarById(prop.CALENDAR_ID);
</code></pre>
<p>登録したAPIトークンやIDなどの値を実際に取り出すには、先ほど用意した<code>prop</code>変数を使って<code>prop.キー名</code>という形で自由に取り出すことができます。やったね!</p>
<p>クラスやメソッドは色々あるので、詳しくは公式ドキュメントを参考に。<br />
なかなか初学者の方にとっては慣れるまではドキュメントの見方が分からず難しいかと思うので、少し慣れた段階や詰まった時に見るようにするといいかもしれません。僕もまだ苦手です。<br />
<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/properties?hl=ja">Properties Service | Apps Script | Google Developers</a></p>
<h2 id="あとがきMEMO"><a href="#%E3%81%82%E3%81%A8%E3%81%8C%E3%81%8DMEMO">あとがきMEMO</a></h2>
<p>どうしても独学ベースだと、セキュリティ面まで意識するのってなかなか難しいんじゃないでしょうか(自分だけ?)<br />
今回のような「秘匿情報を別の場所に格納しておく」という視点は当たり前のことなのかもしれませんが、調べていて目からウロコでした。<br />
今回具体的なGASのプロパティサービスの使い方を書きましたが、それだけに限らない大事な視点だなーと思ってまとめてみました。抽象的な概念と具体的な使い方をセットで覚えておくと理解が深まりますね。</p>
<h3 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h3>
<ul>
<li><p>参照リンク1<br />
<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-property-store/">【初心者向けGAS】プロパティストアの概要とスクリプトプロパティの編集方法</a></p></li>
<li><p>参照リンク2<br />
<a target="_blank" rel="nofollow noopener" href="https://tonari-it.com/gas-properties-script-property/">【初心者向けGAS】スクリプトプロパティを操作してそのデータを取り出す方法</a></p></li>
<li><p>GAS公式ドキュメントのProperties Servise<br />
<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/properties?hl=ja">Properties Service | Apps Script | Google Developers</a></p></li>
</ul>
Massa
tag:crieit.net,2005:PublicArticle/15707
2020-02-04T12:25:53+09:00
2020-02-08T21:35:09+09:00
https://crieit.net/posts/GitHub-GAS
テキストエディタとGitHubでコード管理できるGAS開発環境を作る
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p>GASでの開発に取り組む中で、コードをテキストエディタで書き、Githubで進捗を管理したいなと思いました。</p>
<p>普通にGASで開発する場合は、ブラウザからGoogleDriveのスクリプトエディタを起動してその中でコードを記述します。<br />
このブラウザ上で簡単に書けてすぐに実行できるという開発環境いらずなのがGASの良いところなのですが、<br />
このコードをローカルのテキストファイルとして作り、Githubを使って管理できたら良いなあと思ったわけです。</p>
<p>僕の場合は<br />
- 以前に書いたコードと見比べたい時に、テキストファイルだと参照しやすい<br />
- Githubにコードを残してポートフォリオやメンターの方と共有したい<br />
- 以前Githubのプルリクを使って開発する流れを教わったので活用したい(行き当たりばったりの開発にならないように)</p>
<p>そんなところから自分のやりやすいように開発環境を作ってしまえと考えてやってみた結果、<br />
1. VSCodeでコードを作成・修正してGithubにpush<br />
2. GASのscriptエディタでそれをpullして実行<br />
3. 1に戻ってデバッグ</p>
<p>という流れで作業をすることができるようになり、<br />
例えば学習教材から学んだサンプルコードと見比べて吟味したりコピペしたり学びながらコードを書くのがとても便利になりました。</p>
<p>正直この開発環境がベストプラクティスかどうかは怪しいですが、ノンプログラマの僕が最低限の使い方ができれば良し!という感じでやっています。<br />
同じく最低限で使えれば良いよーという方は参考にしてみたらいただけたら嬉しいです。<br />
また、もっと良いやり方があるよーというのがあれば教えていただけると嬉しいです。</p>
<h3 id="この記事の対象者"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%AF%BE%E8%B1%A1%E8%80%85">この記事の対象者</a></h3>
<ul>
<li>GASを既に使っている方</li>
<li>gitやGithubの基本的な使い方もある程度理解している方</li>
</ul>
<h3 id="使用ツール等"><a href="#%E4%BD%BF%E7%94%A8%E3%83%84%E3%83%BC%E3%83%AB%E7%AD%89">使用ツール等</a></h3>
<ul>
<li>Google Chrome(ブラウザ)</li>
<li>VSCode(テキストエディタ)</li>
<li><p>Github<br />
<br />
GASでのgithubとの連携にはChrome拡張機能を使用します。<br />
前提として、以下のアカウント登録やインストールなどは先に済ませておきます。それぞれのやり方はここでは割愛。</p></li>
<li><p>Githubのアカウントは登録済み</p></li>
<li>GitをPCにインストール済み(<code>$ git --version</code>でバージョンが出ればOK)</li>
</ul>
<h2 id="開発環境づくり"><a href="#%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%A5%E3%81%8F%E3%82%8A">開発環境づくり</a></h2>
<h3 id="1.プロジェクトフォルダとファイルの作成"><a href="#%EF%BC%91%EF%BC%8E%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%A8%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">1.プロジェクトフォルダとファイルの作成</a></h3>
<p>これから制作するプロジェクトのフォルダと、コードを記述するファイルを作っておきます。</p>
<p>プロジェクトフォルダはどこに作っても大丈夫です(試しに作るならデスクトップで良いでしょう)。<br />
そしてその中に<code>main.js</code>という空のテキストファイルをjs拡張子で作成しておきます。<br />
(実際には拡張子は<code>.gs</code>なんですが、ここは<code>.js</code>に。理由は後ほど)</p>
<p>※(余談)プロジェクトフォルダの名前や保存先は自由に決めて問題ないですが、あまり階層を深くしないのと、プロジェクトは一箇所(僕の場合は<code>Documents/Projects</code>)にまとめておき、その中にプロジェクトごとにフォルダを作るのが便利かなって思います。</p>
<h3 id="2.ターミナルでgitの初期設定とGithubとの連携"><a href="#%EF%BC%92%EF%BC%8E%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E3%81%A7git%E3%81%AE%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A%E3%81%A8Github%E3%81%A8%E3%81%AE%E9%80%A3%E6%90%BA">2.ターミナルでgitの初期設定とGithubとの連携</a></h3>
<p>ここから、<a target="_blank" rel="nofollow noopener" href="https://www.micknabewata.com/entry/github/vscode-sync-after-coding">参照リンク1</a>を大いに参考にさせて頂いてます。<br />
ターミナル(Mac)を起動してコマンドを打ち込んでいっても良いのですが、せっかくなのでVSCodeのターミナル機能を使ってみます。</p>
<p>VSCodeを開いたら、「表示」タブから「外観>パネルを表示>ターミナル」もしくは「ターミナル」タブから「新しいターミナル」で開けます。これでターミナルも別窓にならないのでとても便利。<br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499640/97122f54-6533-f932-9c12-5987a0a0bca7.png" alt="スクリーンショット 2020-02-04 11.52.51.png" /></p>
<p>さて、このターミナルにコマンドを打ち込んでいって、先ほど作成したプロジェクトフォルダに入ります。<br />
フォルダを作った場所によって違いますが、僕の場合は<code>Documents/Projects/(プロジェクト名)</code>がプロジェクトフォルダなので、</p>
<pre><code>$ cd Documents
$ cd Projects
$ cd (プロジェクト名)
</code></pre>
<p>次にgitの初期化を行い</p>
<pre><code>$ git init
</code></pre>
<p>GitHubとの連携のための初期設定を行っていきます。</p>
<pre><code>$ git config --global user.name "ユーザー名を入力"
$ git config --global user.email "メールアドレスを入力"
</code></pre>
<p>ダブルクォーテーション("")の中には、自分のGithubにアカウント登録しているユーザー名とメールアドレスを入力するようにします。エラーが出ないようなら成功。</p>
<h3 id="3.Githubでリポジトリ作成"><a href="#%EF%BC%93%EF%BC%8EGithub%E3%81%A7%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E4%BD%9C%E6%88%90">3.Githubでリポジトリ作成</a></h3>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/">GitHub</a>にログインして<code>New</code>ボタンからリポジトリを作っておきます(ここでReadMeを作っておくと良いようですが僕はすっ飛ばしてしまいました)。</p>
<p>リポジトリが完成したら、とりあえずHTTPSを選択(下の画像の赤丸)<br />
<code>…or push an existing repository from the command line</code><br />
の下の2行のコマンドをコピーしておきます。一番右のマークをクリックしたら一発でコピーできます。<br />
(下の画像の赤四角の部分)<br />
<img width="1025" alt="スクリーンショット 2020-01-30 21.54.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499640/b9b6e1da-d0f4-f242-53b6-dc89e3e4ae52.png"></p>
<p>※(余談)ほんとはSSHを選択して鍵の作成や設定をしておくと安全だったりするっぽいのですが今回はHTTPSで。もうちょっと勉強しておきます。</p>
<h3 id="4.リモートリポジトリの登録・コミットとプッシュ"><a href="#%EF%BC%94%EF%BC%8E%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E7%99%BB%E9%8C%B2%E3%83%BB%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%A8%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5">4.リモートリポジトリの登録・コミットとプッシュ</a></h3>
<p>ターミナルに戻り、一旦今の状態でコミット。<br />
コミットメッセージは最初はとりあえず"First Commit"とでもしておきます。</p>
<pre><code>$ git add .
$ git commit -m "First Commit"
</code></pre>
<p>次に、ローカルディレクトリにリポートリポジトリを登録します。<br />
先ほど作成したGithubリモートリポジトリからコピーした2行のコマンドを入れます。</p>
<pre><code>$ git remote add origin https://github.com/アカウント名/リポジトリ名.git
$ git push -u origin master
</code></pre>
<p>するとユーザーネームとパスワードを聞かれるので、Githubのアカウントに使用しているものを入力します。</p>
<pre><code>(上のコマンドを入力後の応答)
Username for 'https://github.com': (Githubのユーザーネームを入力)
Password for 'https://ユーザーネーム@github.com': (Githubのパスワードを入力)
</code></pre>
<p>もしパスワード入力に失敗したりしても、慌てずに<code>$ git push -u origin master</code>と入れれば再度入力できます。</p>
<p>ユーザーネームとパスワード入力に成功すれば、見事リモートリポジトリの登録完了!</p>
<h3 id="5.Chrome拡張機能の導入・GASでGithubと連携"><a href="#%EF%BC%95%EF%BC%8EChrome%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD%E3%81%AE%E5%B0%8E%E5%85%A5%E3%83%BBGAS%E3%81%A7Github%E3%81%A8%E9%80%A3%E6%90%BA">5.Chrome拡張機能の導入・GASでGithubと連携</a></h3>
<p>次にGASのスクリプトエディタ側でGithubと連携し、push/pullをできるようにします。<br />
これにはChromeの拡張機能である「Google Apps Script GitHub アシスタント」を利用します。<br />
この導入には<a target="_blank" rel="nofollow noopener" href="https://qiita.com/20731057hh/items/7f76f9e53e9da5c85ae9">参照2</a>を参考にさせていただきました。</p>
<p>導入は簡単で、まず<a target="_blank" rel="nofollow noopener" href="https://chrome.google.com/webstore/category/extensions?hl=ja">Chrome ウェブストア</a>から「Google Apps Script GitHub アシスタント」を検索してインストール(Chromeに追加)します。<br />
次にGASのスクリプトエディタを開いて、<code>Login SCM</code>をクリックして、<code>username</code>と<code>password</code>を入力すればGithubと連携が完了です(<code>Repository</code>が選択可能な状態になっていればOKです)。<br />
<img width="844" alt="スクリーンショット 2020-01-31 17.21.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499640/7c119fa4-470c-9fe8-17f4-383a3436112b.png"></p>
<h3 id="6.GASでいったんpullしてみる"><a href="#%EF%BC%96%EF%BC%8EGAS%E3%81%A7%E3%81%84%E3%81%A3%E3%81%9F%E3%82%93pull%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">6.GASでいったんpullしてみる</a></h3>
<p><code>Repository</code>タブから今回作成したGithubリポジトリを選択して、<br />
その右のほうにある⬇︎ボタンをクリックしてpullしておきます。<br />
<strong>(⬇︎がpull、⬆︎がpush)</strong></p>
<p>ただ、そのままpullしようとしても上手くいきません。上部に「<code>There is nothing to pull</code>」とアラートが出てしまいます。</p>
<p><code>Repository</code>の右の方にある歯車マークをクリックして、<code>File type to sync</code>の項目を<code>.js</code>に変更します。<br />
これはテキストファイルを作った時に<code>js</code>拡張子で作っていたためです。こうすることで<code>gs</code>ファイルとして読み込むことができます。</p>
<p>これにより⬇︎をクリックするとちゃんとpullできるようになっています。</p>
<p>以上で設定は完了です!わーい!</p>
<h2 id="開発の流れ"><a href="#%E9%96%8B%E7%99%BA%E3%81%AE%E6%B5%81%E3%82%8C">開発の流れ</a></h2>
<p>以降、</p>
<ol>
<li>VSCodeでコードを作成・修正してGithubにpush</li>
<li>GASのscriptエディタでそれをpullして実行</li>
<li>1に戻ってデバッグ</li>
</ol>
<p>を繰り返して開発を進めていきます。</p>
<p>VSCodeでテキストファイルを読み込んで、実際にコーディングしていきましょう。<br />
ある程度コードを書いていき、ここらでGASで実行してみたいなーと思ったら、ターミナルでプロジェクトフォルダに入ってることを確認してから、</p>
<pre><code>$ git add .
$ git commit -m "(コミットメッセージを入力)"
$ git push
</code></pre>
<p>続いてGASのスクリプトエディタ側で、⬇︎クリックでpullして、関数の実行なりアプリ公開なりしてみてください。<br />
しっかり「テキストエディタ&Github&GASのスクリプトエディタ」が連携しているはずです!!</p>
<p>※(余談)コミットの管理とかプッシュはVSCodeの「ソース管理画面」なるものからクリックで直感的にできるようです。僕はターミナルを叩きたくて使ってませんが、そのやり方も<a target="_blank" rel="nofollow noopener" href="https://www.micknabewata.com/entry/github/vscode-sync-after-coding">参照リンク1</a>を参考にできそうです。</p>
<h2 id="解説・補足"><a href="#%E8%A7%A3%E8%AA%AC%E3%83%BB%E8%A3%9C%E8%B6%B3">解説・補足</a></h2>
<p>ただこのやり方、開発の際に注意しなければいけないのが、コンフリクトが起きないように気をつけることです。</p>
<p>例えばデバッグする際にGASのスクリプトエディタ側のコードを修正してしまうと、テキストエディタで記述しているものと差異が生まれてしまいます。<br />
その都度push&pullするなら良いのですが、うっかり忘れがちなので結構面倒です。</p>
<p>僕はひとまず<br />
<strong>「コードの修正は必ずテキストエディタの方で行う」</strong><br />
と決めて、GASスクリプトエディタでは基本的にコードを直接いじらないようにして回避しています(いじるとしたらあくまで関数の動作チェックのため)。</p>
<p>そこがちょっと煩わしいのですが、一応今の所は不自由なく目的を果たしています。<br />
この辺上手く使いこなしている方がいれば、ぜひやり方を教えてください。</p>
<p>また、テキストエディタはVSCodeに限る必要はなさそうなので、自身の慣れたテキストエディタでアレンジしてみても面白そうです。</p>
<h2 id="あとがきMEMO"><a href="#%E3%81%82%E3%81%A8%E3%81%8C%E3%81%8DMEMO">あとがきMEMO</a></h2>
<p>今回のやり方はさほど手間もかからずに1時間程度でできるし、自分の使い慣れているツール・やり方に落とし込む作業というのは、ハマればとても楽しいものですね。</p>
<p>gitでのバージョン管理やGithubはこれまでも使っていたものの、調べて書いてある通りにやっていただけで実際によくわかっていなかったので、これを機に理解が深まった気がします。</p>
<h3 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h3>
<ul>
<li><p>参照リンク1<br />
<a target="_blank" rel="nofollow noopener" href="https://www.micknabewata.com/entry/github/vscode-sync-after-coding">【初心者向け】Visual Studio Codeで書いたコードをGitHubに公開する方法 - 鍋綿ブログ</a></p></li>
<li><p>参照リンク2<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/20731057hh/items/7f76f9e53e9da5c85ae9">GASをGitHubで簡単管理可能なChrome拡張機能 ~導入時に躓いたこと~ - Qiita</a></p></li>
</ul>
Massa