見出し画像

絶望から希望へ~9ヶ月間のプログラミング学習/挑戦と成長~

はじめに


 療養中に、プログラム入門書や入門サイトなどを見て学習してきましたが、学習に行き詰まったときに、「どのように対処したのか」、「どのように感じていたのか」など当時の感想を含めて振り返りをすることで、自身の成長を感じ、少しでも自信に繋げることができればいいと考え、備忘録として、2023.4~12の9ヶ月間のプログラミング学習について、内容をまとめてみました。
 体調不良により、社会から取り残された「絶望の淵」に叩き落され、「未来が閉ざされた真っ暗な世界」から、「未来のかたち」さんに出会ったことで、まさしく、「未来への光明」が見えた9ヶ月間となり、数年ぶりに充実した日々を送ることができました。
 同じような境遇に悩まれる方の参考になれば幸いです。


プログラミングを学習することになった経緯


  • 体調不良により、正業であった柔道整復師(整骨院の先生)を続けられなくなった。

  • 療養を始めた数年は、体重も落ち、無気力で、1日中ほぼ寝たきりの生活をしていた(この頃は、趣味のスポーツ観戦や、お笑い番組の視聴、読書もできないほどの状態だった)。

  • 療養に入って2年ほどした頃に、犬を飼い始め、癒やしを得るとともに、2回/日の散歩が日課となり、体力も少しずつ増強していく。

  • 家族の支えと、愛犬により、笑顔が増え、スポーツやお笑い番組の視聴ができるようになっていく(ニュースやドキュメンタリーは気持ちが引っ張られるので見れなかった)

  • 体力的に柔道整復師業務への復帰は厳しいと感じ、社会復帰への他の道筋を模索し始めた。

  • もともとプログラムに興味があり、独学でPythonを学習するも、結局、Pythonで何が作成できるのかを見いだせず挫折。

  • その後、就労移行支援という支援施設でサポートを受けながら、在宅で学習できる方法を知る。

  • 支援施設で学習を始めたが、印象としては、在宅では、ほぼ独学と変わらず、利用すればするほど、費用(当時、私の場合で上限9,300円/月)だけが嵩むことに罪悪感を受け体調も悪化し、挫折。

  • たまたま眺めていたネットニュースの合間にあった、就労継続支援B型事業所の広告を発見し、調べたところ、作業代をもらいながら、プログラミング学習ができることを知る(作業代の高い、就労継続支援A型事業所もあったが、面談をした際に、通所間隔など、当時の私には継続できないと感じ諦めた)

  • 在宅での通所が可能な施設を調べ、縁あって2023.4月から「未来のかたち」にお世話になり、現在に至る

なぜ、就労移行支援施設で挫折したのに、就労継続支援B型事業所「未来のかたち」で継続できたのか?


  • 毎月の利用料(当時、私の場合で上限9,300円/月)が、作業代と相殺され無料となる(通所回数によっては少しプラスになる)ことで、家族への負担という罪悪感が無くなったから

  • 在宅での通所が認められ、自分のペースで作業日、作業時間を調整できて、ストレスが少ない状態でスタートできたから

  • サポートをしてくれる講師が、自分にあった学習を都度提案してくれたから


9ヶ月間で取り組んだ内容


  1. 学習アプリ「Progate」を活用したプログラミング基礎学習

    • Python

    • HTML

    • CSS

  2. 業務効率化のデスクトップアプリ作成

    • Excel

    • Python

    • Pandas

    • Tkinter

  3. レシピ提案のWebアプリ作成

    • Python

    • HTML

    • BootStrap(CSS)

    • 大手レシピサイトからレシピをスクレイピング

    • スクレイピングしたレシピをデータベースに保存

    • デプロイはせず、ローカル環境での動作のみ

  4. 施術サポートのWebアプリ作成

    • Python

    • Django

    • HTML

    • BootStrap(CSS)

    • デプロイし一般公開まで完了

  5. データ分析と機械学習の学習

    • 書籍「Python実践データ分析100本ノック 第2版」を学習

  6. プロンプトエンジニアリング

    • 生成AIに意図した出力をさせるためのプロンプト(指示文)を作成

    • 活用した生成AI(すべて無料利用範囲)

      • Chat GPT

      • Wrtn(リートン)

      • Google Bard

      • MicroSoft Bing


1. プログラミング基礎学習(2023.4~2週間弱)


1.1 学習で意識したこと


① 「何ができるのか?」

  • 「こういうものを作成できるかな?」と思えるようになること

  • 世の中のシステムは誰かが作成しているものなので、「プログラミングに関する習熟度」や、「個人開発」、「組織開発」という開発の規模による問題は出てくるが、単純作業のほとんどは作成できる

② 暗記ではなく、こんな用語があると認識し、何を意味するかを理解すること

  • 最初から覚えられなくても、学習を続けていけば頻繁に出てくるようになるので、都度確認すれば覚えられる

  • 後で見返すかもしれないと、自身でも用語集を作成したが、理解の手助けにはなっても、ほとんど見返していない(見返さなくても都度、ネットで用語検索すれば解決するし、そうすることで覚えていく。まとめてくれているサイトがある)

③ 「コードを覚えること」よりも「そのコードが何をしているのかを理解すること」

  • 「こんな動作をさせたい」というものがあれば、ネット検索するとほとんどのものは作成できるようになる(後述するが、生成AIを活用すれば、ほぼ作成できる)

④ 実践してみる

  • 自身が「業務で困っていること」、「もっと効率化できないか?」と感じている身近なことをプログラミングで解決できないか考える
    例)
    私の場合、妻が職場で困っていることを聞いて、プログラミングで解決できないかを探った

⑤ できるだけ自分で調べる


1.2 活用したアプリ(サイト)


  1. Progate

    • 「未来のかたち」と利用契約をしたことで、有料版も使用することができたが、無料範囲を繰り返すことで、基礎部分は理解できるようにはなる

  2. YouTube

    • 自身の学習するプログラミング言語を検索すると、「超入門」、「入門」といったシリーズの学習動画がある。私はいくつかの動画を見て、「わかりやすい」、「ききやすい」ものをチャンネル登録している。


2. 実践編(アプリの作成)


2.1 業務効率化のデスクトップアプリ作成(2023.4月半ば~2023.5月半ば)


 プログラミング基礎学習の繰り返しに飽き始めていた頃に、妻から業務内容で困っていることを聞き、実際の業務効率化に取り組むことで、プログラミング学習を効率的に進めたいと感じ、挑戦することになった。
  YouTubeなどでプログラミング入門学習をしていると、「Pythonで業務効率化」といった内容を見聞きするようになっていたことも、挑戦への後押しをしてくれた。

<アプリの概要>
 親会社から指示される、「在庫として保持しておかないといけない品目(独自システム)」と、「自社の在庫管理(Excel)品目」を比較し、在庫保持しなくてよい商品の返品と、保持しなければならない商品の入荷、保持の在庫管理をする

<アプリ開発の背景(業務上の困りごと)>

  • 品目の入れ替えが半年に一度程度のため、確認業務の作業工程を毎回、最初から確認して行わないといけない

  • 在庫として保持しておかないといけない品目が独自システムで運用されており、CSVファイルなどに変換できず、Excelにコピペして確認しないといけない

  • 業務担当者がExcelに詳しければExcel関数を活用してすぐに確認できるが、Excel関数を教えるのにも時間がかかる上、半年に一度程度の業務のため、業務担当者が、教えられても覚えていられない

  • Excel関数を扱える(IT関連作業に従事できる)社員が1人しかおらず、業務の偏りが生じるので、他の社員でもできるようにしたい

  • 確認作業の効率化とともに、人為ミスを最小限にしたい

<開発の流れ>
 
本来は、要件定義書などを作成し、必要な機能を洗い出して進めていくのが正しい行程。
 当時は、思いついた業務効率化の機能をプログラミングして、実際に動かしてみることを繰り返していた。

① PythonでExcel操作をする方法を学習

  • 「Python エクセル操作」と検索し、出てきたサイトやYouTubeを参考にしながら学習

    • openpyxl:
      Excel操作を行うPythonのライブラリ

    • Pandas:
      Pythonでデータ分析を効率的に行うためのライブラリ。
      当時は、Excelのデータを列の順番を移動(下記コード内の、def file_operation():関数の部分)したり、在庫の比較(下記コード内の、def hikaku():関数の部分)をすることに使用していた。

    • styleframe:
      pandasでExcel書込み時に幅/高さ,中央揃え,折り返しを指定するライブラリ

    • 当時のコーディングの一部は下記

import PySimpleGUI as sg
import openpyxl
import datetime as dt
import os
import subprocess
import pandas as pd
from styleframe import StyleFrame, Styler, utils
  

def date():

        now = dt.datetime.now()  # 今の日付時刻を取得
        time = now.strftime('%Y%m%d_%H%M')  # time を定義(yyyymmdd_hhmm)
        return time

  
def path():
    return os.path.expanduser('~/Desktop')  # desktop の Path を取得


# デスクトップにExcelファイルを作成するクラス
class FileCreat:

    def creat():
        wb = openpyxl.Workbook()
        ws = wb.active
        return wb, ws

    # データ参照元ファイルの作成

    def OGfile_creat():

        wb, ws = FileCreat.creat()
        ws['B1'] = '部品コード'
        ws['C1'] = 'OG部品名'
        wb.save(path()+'\データ参照元_{}.xlsx'.format(date()))
        wb.close()

    #会社にある部品一覧ファイルの作成

    def zfile_creat():

        wb, ws = FileCreat.creat()
        ws['A1'] = 'ロケーション'
        ws['B1'] = '部品コード'
        ws['C1'] = '在庫部品名'
        wb.save(path()+'\会社にある部品一覧_{}.xlsx'.format(date()))
        wb.close()

  
class Select:

    def og_open():

        try:  # 実行してみて成功すればそのまま、エラーがあれば except の処理をする

            EXCEL = values['-OG_FILE_INPUT-']
            if EXCEL == "":
                return "break"
            subprocess.Popen(['start',EXCEL], shell=True)
            return EXCEL

        except Exception as e: # Exception as e ほとんどのエラーに対して対応できる
            sg.popup_error('"エラーが発生しました","画面を閉じて初めからやり直してください"')  # エラーボタンを表示()

    def z_open():

        try:  # 実行してみて成功すればそのまま、エラーがあれば except の処理をする

            EXCEL = values['-ZAIKO_FILE_INPUT-']
            if EXCEL == "":
                return "break"

            subprocess.Popen(['start',EXCEL], shell=True)
            return EXCEL

        except Exception as e: # Exception as e ほとんどのエラーに対して対応できる

            sg.popup_error('"エラーが発生しました","画面を閉じて初めからやり直してください"')  # エラーボタンを表示()
  

class Hikaku:

    def hikaku():

        window['-ACT2-'].update('処理中です')
        old_file = values["-OG_INPUT-"]
        new_file = values["-ZAIKO_INPUT-"]
  

        try: # try 以下の文でプログラムを実行しexcept でエラーなど例外が発生した際の処理を書く

            # excel ファイルの読み込み
            df1 = pd.read_excel(old_file)
            df2 = pd.read_excel(new_file)

            # ファイルを '部品コード' 列で比較
            df = pd.merge(
                df1,
                df2,
                on= '部品コード',
                how="outer",
                indicator=True
                )

            return df

        except Exception as e: # Exception as e ほとんどのエラーに対して対応できる

            sg.popup_error('"エラーが発生しました","画面を閉じて初めからやり直してください"')  # エラーボタンを表示()

    def file_operation():

        df=Hikaku.hikaku()
        # _merge 列の left_only を OGファイルのみ に置換
        df["_merge"] = df["_merge"].str.replace('left_only', 'OGファイルのみ')
        # _merge 列の right_only を 在庫ファイルのみに変換
        df["_merge"] = df["_merge"].str.replace('right_only', '在庫ファイルのみ')
        # _merge 列の both を 返却部品に変換
        df["_merge"] = df["_merge"].str.replace('both', '返却部品')
        # df の 'Unnamed: 0' に該当する列を削除する
        df_1 = df.drop(columns='Unnamed: 0')
  
        # df_1 の '_merge' 列をA列に移動
        first_column = df_1.pop('_merge')
        df_1.insert(0,'_merge',first_column)

        # df_1 の '_merge' 列をB列に移動
        second_column = df_1.pop('ロケーション')
        df_1.insert(1,'ロケーション',second_column)

        return df_1


    def merge():

        # _merge 列の '返却部品' だけを抽出
        df_2 = Hikaku.file_operation()[Hikaku.file_operation()["_merge"] == '返却部品']
        df_3 = Hikaku.file_operation()[Hikaku.file_operation()["_merge"] == 'データ参照元ファイルファイルのみ']
        df_4 = Hikaku.file_operation()[Hikaku.file_operation()["_merge"] == '在庫一覧ファイルのみ']

        return df_2, df_3, df_4


    def save():

        df1, df2, df3 = Hikaku.merge()

        # style に number_format の 文字列に変換(utils.number_formats.general_integer) を定義する
        style = Styler( number_format = utils.number_formats.general_integer)
        sf1 = StyleFrame(df1)
        sf2 = StyleFrame(df2)
        sf3 = StyleFrame(df3)
        sf_list = [sf1, sf2, sf3]

        # '部品コード','ロケーション' 列の出力時の幅を 20 にする
        for s in sf_list:
            s=s.set_column_width(columns=['部品コード','ロケーション'], width=20)

        # '部品名','部品名称' 列の出力時の幅を 45 にする
        for s in sf_list:
            s=s.set_column_width(columns=['OG部品名','在庫部品名'], width=45)

        # apply_column_style で '部品コード' 列に style を使う
        for s in sf_list:
            s=s.apply_column_style(cols_to_style='部品コード', styler_obj=style)

        # デスクトップに保存
        with StyleFrame.ExcelWriter( path() + '\返却部品確認_{}.xlsx'.format(date()) ) as writer:
            sf1.to_excel(writer, sheet_name = '返却部品',startrow=1, startcol=1)
            sf2.to_excel(writer, sheet_name = 'データ参照元ファイルにのみある部品',startrow=1, startcol=1)
            sf3.to_excel(writer, sheet_name = '在庫一覧ファイルにのみある部品',startrow=1, startcol=1)

        window['-ACT2-'].update('処理が終了しました。')
        window['-ACT3-'].update('デスクトップに「返却部品確認_{今日の日付_時刻}.xlsx」のファイルが作成されました')
        window['-ACT4-'].update('このウィンドウを閉じてください')

② 上記のコードに行き着くまでに検索したワード
 はっきりとは覚えていないが、おそらく下記のワード検索で参考サイトを探した記憶がある

  • 「Python エクセル操作 列移動」

  • 「Python 在庫管理 比較」

  • 「Python ユーザーのデスクトップに保存」

  • 「Python Pandas 文字変換」

  • 「Python Pandas スタイル指定」

  • 「Python エラー」

などなど、
1. 「関数ごとにプログラミング」
2. 「思い通りにいかないこと」、「実行したい動作」の検索
1→2 をひたすら繰り返していた。

③ デスクトップアプリにする
 上記の各関数が思い通りに動くことが確認できたあと、「Python デスクトップアプリ」で 検索
下記2つに行き着いた。

  • Tkinter

  • PySimpleGUI

 当初は、カスタマイズがし易いということで「Tkinter」を学習しながら作成していたが、シンプルな方がユーザーにもわかりやすいだろうと、最終的に「PySimpleGUI」を学習しながら作成した。
 半年に一度程度という頻度のため、操作方法を教えることが困難という課題があり、アプリを立ち上げて、手順通りに進めれば、必要な情報がわかるように「手順書形式」にした。

「PySimpleGUI」で作成したコードが下記

# ウィンドウの内容を定義する

og_layout = [

   [sg.Text('データ参照元ファイルを作成します', font=(26))],
   [
    sg.Text('1. ファイルの作成ボタンをクリックしてください', font=(18)),
    sg.Button('ファイルの作成', key='-OG_FILE_CREATE-')
    ],
   [sg.Text('', key='-ACT-')],
   [
    sg.Text('2. ファイルを開くボタンをクリックし、「データ参照元_今日の日付_時刻.xlsx」ファイルを開いてください', font=(18)),
    sg.InputText('「データ参照元_今日の日付_時刻.xlsx」を選択してください', key="-OG_FILE_INPUT-", s=(55,1)),
    sg.FileBrowse('ファイルの選択', key="-OG_FILE_SELECT-", target="-OG_FILE_INPUT-")
    ],
   [sg.Text('3.ファイルの確定ボタンをクリックしてください', font=(18)),sg.Button('ファイルの確定', key='-OG_FILE_CONFIRM-')],
   [sg.Text('4. エクセルファイルへの貼付', font=(18))],
   [sg.Text('  OGからの返却部品リストがある場所を開く  ⇒  部品のコードが書かれている部分をコピー  ⇒ エクセルの部品コードの下に貼付\n  部品の名前が書かれている部分をコピー  ⇒  エクセルのOG部品名の下に貼付')],
   [sg.Text('5. エクセルファイルを上書き保存して閉じてください', font=(18))],
   [sg.Text('データ参照元ファイルの作成を終了します', font=(18)), sg.Button('終了', key='-OG_FILE_FINSH-')]

]

# -----OGウィンドウを作成する-----
og_window = sg.Window(
    "データ参照元ファイルの作成",  # ウィンドウタイトル
    og_layout,  # 定義した layout を仕様
    grab_anywhere = True,  # ウィンドウのどこを掴んでも移動できる
    keep_on_top = True,  # 他のファイルを作業していても常に前に表示
    location = (35, 380)
    )

# イベントループを使用してウィンドウを表示し、対話する
while True:

    # イベントの読み込み
    event, values = og_window.read()
    # ウィンドウの×ボタンクリックで終了
    if event == sg.WIN_CLOSED:
        break

    elif event == "-CANCEL-":
        break

    elif event == "-OG_FILE_CREATE-":
        FileCreat.OGfile_creat()
        og_window['-ACT-'].update('デスクトップに「データ参照元_今日の日付_時刻.xlsx」のファイルが作成されました')

    elif event == "-OG_FILE_CONFIRM-":
        # values["-OG_FILE_INPUT-"] にあるエクセルファイルを開く
        Select.og_open()

    elif event == "-OG_FILE_FINSH-":
        break

og_window.close()

# -----ZAIKOウィンドウを作成する-----
zaiko_layout = [
   [sg.Text('会社にある部品一覧ファイルを作成します', font=(26))],
   [
    sg.Text('1. ファイルの作成ボタンをクリックしてください', font=(122)),
    sg.Button('ファイルの作成', key='-ZAIKO_FILE_CREATE-')
    ],
   [sg.Text('', key='-ACT1-')],
   [sg.Text('2. ファイルの選択ボタンをクリックし、「会社にある部品一覧_今日の日付_時刻.xlsx」ファイルを開いてください', font=(10))],
   [
    sg.InputText('「会社にある部品一覧_今日の日付_時刻.xlsx」を選択してください', key="-ZAIKO_FILE_INPUT-", s=(60,1)),
    sg.FileBrowse('ファイルの選択', key="-ZAIKO_FILE_SELECT-", target="-ZAIKO_FILE_INPUT-")  
   ],
   [sg.Text('3. ファイルの確定ボタンをクリックしてください', font=(10)),sg.Button('ファイルの確定', key='-ZAIKO_FILE_CONFIRM-')],
   [sg.Text('4. エクセルファイルへの貼付', font=(10))],
   [sg.Text('  在庫状況を保存している場所を開く  ⇒  部品のコードが書かれている部分をコピー  ⇒ エクセルの部品コードの下に貼付\n  部品の名前が書かれている部分をコピー  ⇒  エクセルの在庫部品名の下に貼付')],
   [sg.Text('5. エクセルファイルを上書き保存して閉じてください', font=(10))],
   [sg.Text('データ参照元ファイルの作成を終了します', font=(10)), sg.Button('終了', key='-ZAIKO_FILE_FINSH-')]
]

# ウィンドウを作成する
zaiko_window = sg.Window(
    "会社にある部品一覧ファイルの作成",  # ウィンドウタイトル
    zaiko_layout,  # 定義した layout を仕様
    grab_anywhere = True,  # ウィンドウのどこを掴んでも移動できる
    keep_on_top = True,  # 他のファイルを作業していても常に前に表示
    location = (200, 405)
    )

# イベントループを使用してウィンドウを表示し、対話する
while True:
    # イベントの読み込み
    event, values = zaiko_window.read()
    # ウィンドウの×ボタンクリックで終了
    if event == sg.WIN_CLOSED:
        break

    elif event == "-CANCEL-":
        break

    elif event == "-ZAIKO_FILE_CREATE-":
        FileCreat.zfile_creat()
        zaiko_window['-ACT1-'].update('デスクトップに「会社にある部品一覧_今日の日付_時刻.xlsx」のファイルが作成されました')

    elif event == "-ZAIKO_FILE_CONFIRM-":
        Select.z_open()

    elif event == "-ZAIKO_FILE_FINSH-":
        break

zaiko_window.close()

# -----HIKAKUウィンドウを作成する-----
layout = [
   [sg.Text('返却部品の確認', font=(26))],
   [sg.Text("データ参照元ファイル選択ボタンをクリックしてください")],
   [sg.InputText('「データ参照元_今日の日付_時刻.xlsx」のファイルを選択', key="-OG_INPUT-", s=(55,1))],
   [sg.FileBrowse('データ参照元ファイル選択', key="-OG_FILE-", target="-OG_INPUT-")],
   [sg.Text("在庫ファイル選択ボタンをクリックしてください")],
   [sg.InputText('「会社にある部品一覧_今日の日付_時刻.xlsx」のファイルを選択', key="-ZAIKO_INPUT-", s=(55,1))],
   [sg.FileBrowse('在庫ファイル選択', key="-ZAIKO_FILE-", target="-ZAIKO_INPUT-")],
   [sg.Text('ファイルを2つ選択したらファイルの確定ボタンをクリックしてください', key='-ACT5-')],
   [sg.Submit('ファイルの確定', key="-SUBMIT-")],
   [sg.Button('開始', key='-START-')],
   [sg.Text('', key='-ACT2-')],
   [sg.Text('', key='-ACT3-')],
   [sg.Text('', key='-ACT4-')],
   [sg.Button('閉じる', key='-CLOSE-'), sg.Cancel('キャンセル',key="-CANCEL-")]
]

# ウィンドウを作成する
window = sg.Window("返却部品の確認",  layout)

# イベントループを使用してウィンドウを表示し、対話する
while True:
    # イベントの読み込み
    event, values = window.read()
    # ウィンドウの×ボタンクリックで終了
    if event == sg.WIN_CLOSED:
        break

    elif event == "-CANCEL-":
        break

    elif event == "-SUBMIT-":
        # values["-OG_INPUT-"] にあるエクセルファイルを old_file に定義し開く
        old_file = values["-OG_INPUT-"]
        # values["-ZAIKO_INPUT-"] にあるエクセルファイルを new_file に定義し開く
        new_file = values["-ZAIKO_INPUT-"]
        window['-ACT2-'].update('開始ボタンをクリックしてください')

    elif event == '-START-':
        Hikaku.hikaku()
        Hikaku.file_operation()
        Hikaku.save()

    elif event == '-CLOSE-':
        break

# 画面から削除して終了
window.close()

④  デスクトップアプリの配布
 完成したデスクトップアプリを、ユーザーに配布する方法を検索
検索ワード:「Python アプリ化」
「pyinstaller」という方法があるので、学習し、アプリ化してユーザーに配布し完了。

⑤ まとめ

  • 学習を開始して1ヶ月半ほどで、デスクトップアプリを開発することができた。

  • 実践することで、プログラミング基礎学習のどの部分の知識が不足しているかを自覚することができ、再学習に繋がった。

  • 自信になった

  • プログラミング基礎学習を再実践した際に、前回学習時より理解することができた


2.2 1週間の献立を作成するWebアプリ作成(2023.6~2023.7)


 業務効率化アプリの作成後、YouTubeなどでプログラミング基礎を再学習していたときに、Webサイトから自分に必要な情報を自動で取得するという「スクレイピング入門」の学習をしていた。そのとき、ふと、妻から「献立考えるのが家事の中で一番面倒くさい」という話を聞き、スクレイピング学習の実践がてら挑戦してみようと作成を始めた。

<アプリの概要>
 
大手レシピサイトからスクレイピングで、レシピを取得して、メイン料理と副菜の献立を1週間分作成する

<アプリ開発の背景(日常の困りごと)>

  • 献立を考え、買い物リストを作成するのが面倒くさい

  • メイン料理(牛肉・豚肉・鶏肉・魚)が連日重ならないように選択できるようにしたい

  • 副菜もメイン料理に合わせて何度でも再選択できるようにしたい

<開発の流れ>
 
2.1のときにも触れたが、本来は、要件定義書などを作成し、必要な機能を洗い出して進めていくのが正しい行程。
 このときも、思いついた献立作成機能をプログラミングして、実際に動かしてみることを繰り返していた。
 定期的にサポートしていただいている講師と面談をする(この面談が独学との大きな違いと実感)が、その際に「次はレシピを自動生成するものを作ってみたい」と話すと、「Webアプリケーションを作成してはどうか」と提案をいただき、当アプリの開発を始めた。

① Pythonでスクレイピングする方法を学習
 「Python入門」のYouTubeを参考にしながら基礎を再学習していたときに、同じチャンネルにあった「スクレイピング入門」を学習

  • スクレイピングで必要になるライブラリ

    • Requests

    • BeautifulSoup

    • time

    • (Random)

    • Selenium JavaScript が使われているサイトからスクレイピングする場合に使用

  • 当時のコーディングの一部は下記


from bs4 import BeautifulSoup
import requests
from time import sleep
import pandas as pd
  

# サイトからレシピのタイトルを取得する
def contents(text):

    r = requests.get(text)
    soup = BeautifulSoup(r.text, 'html.parser')
    content_titles = soup.find_all('a', class_='recipe-title')

    return content_titles
  

# サイトからレシピを取得する
def recipes_acquisition(text):  # acquisition(取得)

    link = requests.get(text)

	# サイトに負荷がかからないように少し間をとる
    sleep(3)

    soup = BeautifulSoup(link.text)

	# レシピのタイトルを取得する
	recipes_title = soup.find('h1')
    recipes_title = recipes_title.text
 
	# 取得した情報から、不要な文字などを削除する
    recipes_title = recipes_title.replace('\n', '').replace('\u3000', '')

	# レシピに必要な材料を取得する
	recipe_headings = soup.find('div', class_='content')
    material_titles = recipe_headings.text
    
    # 取得した情報から、不要な文字などを削除する
    materials_title = material_titles.replace('\n', '')
    
    materials = soup.find_all('div', class_ = 'ingredient_row')
    materials_list = [m.text for m in materials]
    # 取得した情報から、不要な文字などを削除する
    material = [s.replace('\n', '').replace('\u3000', '').replace('\xa0', '') for s in materials_list]

    return recipes_title, materials_title, material
  
# サイトから食材に関するレシピを表示するURLを指定する
beef_url = 'サイトURL'
pork_url = 'サイトURL'
chicken_url = 'サイトURL'
fishes_url = 'サイトURL'
subs_url = 'サイトURL'

  
# 取得するWebページの数を指定する
beef_urls = [beef_url.format(i) for i in range(1,11)]

sleep(1)

pork_urls = [pork_url.format(i) for i in range(1, 11)]

sleep(1)

chicken_urls = [chicken_url.format(i) for i in range(1, 11)]

sleep(1)

fishes_urls = [fishes_url.format(i) for i in range(1, 11)]

sleep(1)

  
beef_contents = []
pork_contents = []
chicken_contents = []
fish_contents = []

base_url = 'https://cookpad.com/'

# 取得するURLを1ページずつ取得し、上記のそれぞれのリストに格納する
for (b_u, p_u, c_u, f_u) in zip(beef_urls, pork_urls, chicken_urls, fishes_urls):

    b_contents = contents(b_u)
    p_contents = contents(p_u)
    c_contents = contents(c_u)
    f_contents = contents(f_u)

	# 取得したURLから情報を取得する
    for (b_titles, p_titles, c_titles, f_titles) in zip(b_contents, p_contents, c_contents, f_contents):

        b_title = b_titles.get('href')
        b_url = f'{base_url}{b_title}'
        beef_contents.append(b_url)

        sleep(1)

        p_title = p_titles.get('href')
        p_url = f'{base_url}{p_title}'
        pork_contents.append(p_url)

        sleep(1)

        c_title = c_titles.get('href')
        c_url = f'{base_url}{c_title}'
        chicken_contents.append(c_url)

        sleep(1)

        f_title = f_titles.get('href')
        fish_url = f'{base_url}{f_title}'
        fish_contents.append(fish_url)

        sleep(1)

subs_urls = [subs_url.format(i) for i in range(1, 21)]

sleep(1)

sub_contents = []

for s_u in subs_urls:

    s_contents = contents(s_u)

	for s_titles in s_contents:
        s_title = s_titles.get('href')
        sub_url = f'{base_url}{s_title}'
        sub_contents.append(sub_url)

        sleep(1)
  
# 取得できているか確認するために出力する
print(len(beef_contents))
print('----------------')
print(len(pork_contents))
print('----------------')
print(len(chicken_contents))
print('----------------')
print(len(fish_contents))
print('----------------')
print(len(sub_contents))

  

beef_recipes = []
pork_recipes = []
chicken_recipes = []
fish_recipes = []

# ○○_contentsにそれぞれ格納した情報から、1ページずつURLを取得する
for (b_url, p_url, c_url, f_url) in zip(beef_contents, pork_contents, chicken_contents, fish_contents):

	# beef_contentsから取得したb_urlから必要情報を取得し、beef_recipes = []に格納する
    recipe_title, material_title, material = recipes_acquisition(b_url)

    recipe = {
        'title': recipe_title,
        'recipe_url': b_url,
        'material_title': material_title,
        'material': material
    }

    beef_recipes.append(recipe)

    sleep(1)

	# pork_contentsから取得したp_urlから必要情報を取得し、pork_recipes = []に格納する
    recipe_title, material_title, material = recipes_acquisition(p_url)

    recipe = {
        'title': recipe_title,
        'recipe_url': p_url,
        'material_title': material_title,
        'material': material
    }

    pork_recipes.append(recipe)

    sleep(1)
  
	# chicken_contentsから取得したc_urlから必要情報を取得し、chicken_recipes = []に格納する
    recipe_title, material_title, material = recipes_acquisition(c_url)

    recipe = {
        'title': recipe_title,
        'recipe_url': c_url,
        'material_title': material_title,
        'material': material
    }

    chicken_recipes.append(recipe)

    sleep(1)
  
	# fish_contentsから取得したf_urlから必要情報を取得し、fish_recipes = []に格納する
    recipe_title, material_title, material = recipes_acquisition(f_url)

    recipe = {
        'title': recipe_title,
        'recipe_url': f_url,
        'material_title': material_title,
        'material': material
    }

    fish_recipes.append(recipe)

    sleep(1)
  
# 以下副菜(sub)でも同様
sub_recipes = []
  
for s_url in sub_contents:

    recipe_title, material_title, material = recipes_acquisition(s_url)

    recipe = {
        'title': recipe_title,
        'recipe_url': s_url,
        'material_title': material_title,
        'material': material
    }

    sub_recipes.append(recipe)

    sleep(1)
  
# 取得できているか確認するために出力する
print(beef_recipes)
print('----------------')
print(pork_recipes)
print('----------------')
print(chicken_recipes)
print('----------------')
print(fish_recipes)
print('----------------')
print(sub_recipes)

  
# 取得したレシピ集をCSV形式で保存する
df_b = pd.DataFrame(beef_recipes)
df_b.to_csv(r'C:\保存先のパス\beef_recipes.csv', index=False, encoding='utf-8-sig')

df_p = pd.DataFrame(pork_recipes)
df_p.to_csv(r'C:\保存先のパス\pork_recipes.csv', index=False, encoding='utf-8-sig')

df_c = pd.DataFrame(chicken_recipes)
df_c.to_csv(r'C:\保存先のパス\chicken_recipes.csv', index=False, encoding='utf-8-sig')

df_f = pd.DataFrame(fish_recipes)
df_f.to_csv(r'C:\保存先のパス\fish_recipes.csv', index=False, encoding='utf-8-sig')

df_s = pd.DataFrame(sub_recipes)
df_s.to_csv(r'C:\保存先のパス\sub_recipes.csv', index=False, encoding='utf-8-sig')

② 取得したレシピを保存しておくデータベース操作の学習

  • SQLite

  • SQLコマンド

  • 当時のコーディングの一部は下記

import sqlite3
import pandas as pd

df_b = pd.read_csv(r'C:\保存先のパス\beef_recipes.csv')
df_p = pd.read_csv(r'C:\保存先のパス\pork_recipes.csv')
df_c = pd.read_csv(r'C:\保存先のパス\chicken_recipes.csv')
df_f = pd.read_csv(r'C:\保存先のパス\fish_recipes.csv')
df_s = pd.read_csv(r'C:\保存先のパス\sub_recipes.csv')

db_name = 'recipe.db'  # 'recipe.db' という名前のデータベース作成

with sqlite3.connect(db_name) as conn:

    df_b.to_sql('beef', con=conn, if_exists='replace')
    df_p.to_sql('pork', con=conn, if_exists='replace')
    df_c.to_sql('chicken', con=conn, if_exists='replace')
    df_f.to_sql('fish', con=conn, if_exists='replace')
    df_s.to_sql('sub', con=conn, if_exists='replace')

conn = sqlite3.connect(db_name)

# データベースに格納できているか、データのうちの1つを取得して表示
beef = conn.execute('SELECT * FROM beef ORDER BY RANDOM()').fetchone()
print(beef)

③ レシピを作成するための関数を考える
 試したこと

  • 指定した食材からレシピを取得できるかどうか

  • Webページに表示する際に、不要な文字列を削除したり、分割したりする方法

  • 当時のコーディングの一部は下記

import pandas as pd
from flask import request
  
# スクレイピングで取得し作成したCSVをそれぞれの変数に代入する
df_b = pd.read_csv(r'C:\保存先のパス\beef_recipes.csv')
df_p = pd.read_csv(r'C:\保存先のパス\pork_recipes.csv')
df_c = pd.read_csv(r'C:\保存先のパス\chicken_recipes.csv')
df_f = pd.read_csv(r'C:\保存先のパス\fish_recipes.csv')
df_s = pd.read_csv(r'C:\保存先のパス\sub_recipes.csv')


# 不要な記号の削除
def material(text):
    return text['material'].str.replace('[', '').str.replace(']', '')

# materialを1材料ごとに「,」で区切る
def split(text):
    return text['material'].str.split(',')
  

def name(text):

    names = request.form.get(text)
    return names

def menu_create():
	# メイン料理のリスト
    recipe_m = []
	# 副菜のリスト
    s_recipes = []

    # データフレームから任意の行を抽出
    # df = df.sample(n=行数)

    if name == '牛肉':
        name_b = df_b.sample(n=1)
        recipe_b = {
            'title': name_b[1],
            'url': name_b[2],
            'material_title': name_b[3],
            'materials': name_b[4]
        }

        recipe_m.append(recipe_b)

    elif name == '鶏肉':
        name_c = df_c.sample(n=1)
        recipe_c = {
            'title': name_c[1],
            'url': name_c[2],
            'material_title': name_c[3],
            'materials': name_c[4]
        }

        recipe_m.append(recipe_c)

    elif name == '豚肉':
        name_p = df_p.sample(n=1)
        recipe_p = {
            'title': name_p[1],
            'url': name_p[2],
            'material_title': name_p[3],
            'materials': name_p[4]
        }

        recipe_m.append(recipe_p)

    elif name == '魚':
        name_f = df_f.sample(n=1)
        recipe_f = {
            'title': name_f[1],
            'url': name_f[2],
            'material_title': name_f[3],
            'materials': name_f[4]
        }

        recipe_m.append(recipe_f)
  

    elif name == '副菜':
        name_s = df_f.sample(n=1)
        recipe_s = {
            'title': name_s[1],
            'url': name_s[2],
            'material_title': name_s[3],
            'materials': name_s[4]
        }

        s_recipes.append(recipe_s)

	return recipe_m, s_recipes
  

df_b['material'] = material(df_b)
df_b['material'] = split(df_b)

df_p['material'] = material(df_p)
df_p['material'] = split(df_p)

df_c['material'] = material(df_c)
df_c['material'] = split(df_c)

df_f['material'] = material(df_f)
df_f['material'] = split(df_f)

df_s['material'] = material(df_s)
df_s['material'] = split(df_s)

④ Webアプリ作成の学習

  • 講師との面談でWebアプリケーションフレームワーク「Flask」に挑戦してみることが決定

  • 「Flask」の公式チュートリアルや、YouTubeで学習

  • Webページ作成のため、Progateで「HTML」、「CSS」を少し学習

  • 講師から「Bootstrap」というサイトで、体裁を整えられることを教えていただき、学習

  • 当時のコーディングの一部が下記

from menu_create import app
from flask import render_template, request, redirect, url_for
import sqlite3
import ast

db = 'recipe.db'
  

def recipe_create(main, sql_command):

    name = request.form.get('main')
    day = request.form.get('day_of_week')

	with sqlite3.connect(db) as conn:
	
        m_r = conn.execute(sql_command).fetchone()
        materials = m_r[4]
        materials = ast.literal_eval(materials)
    
		if name == main and day == day:
            return {'day_of_week': day,
                    'title': m_r[1],
                    'url': m_r[2],
                    'material_title': m_r[3],
                    'materials': materials}
  

def index_display_content(day_of_week):

    with sqlite3.connect(db) as conn:
        main_recipes = conn.execute(
            'SELECT * FROM main_recipes order by day_of_week').fetchall()
    return [{
            'day_of_week': r[0],
            'title': r[1],
            'url': r[2],
            'material_title': r[3],
            'materials': ast.literal_eval(r[4])}

            for r in main_recipes if r[0] == day_of_week]
  

@app.route('/')

def index():

    sun_recipes = index_display_content('日曜')
    mon_recipes = index_display_content('月曜')
    tue_recipes = index_display_content('火曜')
    wed_recipes = index_display_content('水曜')
    thu_recipes = index_display_content('木曜')
    fri_recipes = index_display_content('金曜')
    sat_recipes = index_display_content('土曜')

    return render_template('index.html',
                           sun_recipes=sun_recipes,
                           mon_recipes=mon_recipes,
                           tue_recipes=tue_recipes,
                           wed_recipes=wed_recipes,
                           thu_recipes=thu_recipes,
                           fri_recipes=fri_recipes,
                           sat_recipes=sat_recipes
                           )
  

@app.route('/form')

def form():
    recipe = []
    return render_template('form.html', recipe=recipe)


@app.route('/form', methods=['POST'])

def recipe():
    name = request.form.get('main')

    match name:

        case '牛肉':
            recipe = recipe_create('牛肉', 'SELECT * FROM beef ORDER BY RANDOM()')
            return render_template('form.html', recipe=recipe)

        case '豚肉':
            recipe = recipe_create('豚肉', 'SELECT * FROM pork ORDER BY RANDOM()')
            return render_template('form.html', recipe=recipe)

        case '鶏肉':
            recipe = recipe_create('鶏肉', 'SELECT * FROM chicken ORDER BY RANDOM()')
            return render_template('form.html', recipe=recipe)

        case '魚':
            recipe = recipe_create('魚', 'SELECT * FROM fish ORDER BY RANDOM()')
            return render_template('form.html', recipe=recipe)

        case '副菜':
            recipe = recipe_create('副菜', 'SELECT * FROM sub ORDER BY RANDOM()')
            return render_template('form.html', recipe=recipe)
  

@app.route('/register', methods=['POST'])

def register():

    day_of_week = request.form.get('r_day_of_week')
    title = request.form.get('r_title')
    recipes_url = request.form.get('r_url')
    material_title = request.form.get('r_material_title')
    material = request.form.get('r_material')

    with sqlite3.connect(db) as conn:

        conn.execute('INSERT INTO main_recipes VALUES(?,?,?,?,?)',
                     [day_of_week, title, recipes_url, material_title,
                      material])

        conn.commit()

    return redirect(url_for("form"))
  
  
@app.route('/', methods=['POST'])

def completion():
    return redirect(url_for("index"))

 
@app.route('/delete', methods=['POST'])

def delete_db():

    delete_btn = request.form.get('delete_recipe')
    with sqlite3.connect(db) as conn:

        if delete_btn == 'sun':
            conn.execute("DELETE FROM main_recipes WHERE day_of_week='日曜'")
            conn.commit()

        elif delete_btn == 'mon':
            conn.execute("DELETE FROM main_recipes WHERE day_of_week='月曜'")
            conn.commit()

        elif delete_btn == 'tue':
            conn.execute("DELETE FROM main_recipes WHERE day_of_week='火曜'")
            conn.commit()

        elif delete_btn == 'wed':
            conn.execute("DELETE FROM main WHERE day_of_week='水曜'")
            conn.commit()

        elif delete_btn == 'thu':
            conn.execute("DELETE FROM main_recipes WHERE day_of_week='木曜'")
            conn.commit()

        elif delete_btn == 'fri':
            conn.execute("DELETE FROM main_recipes WHERE day_of_week='金曜'")
            conn.commit()

        else:
            conn.execute("DELETE FROM main_recipes WHERE day_of_week='土曜'")
            conn.commit()

    return redirect(url_for("form"))
<!DOCTYPE html>
<html lang='ja'>
<head>
    <meta charset='UTF-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
    <title>献立をつくろう</title>
</head>
    <body>
        <nav class="navbar navbar-light bg-light">
          <div class="container-fluid p-3">
            <a class="navbar-brand" href="/">1週間の献立表</a>
          </div>
        </nav>

        {% block body %}{% endblock %}

    </body>
</html>
{% extends 'base.html' %}

{% block body %}

    <div>
        <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピ選択画面に戻る</a>

        <table class="m-3">
            <tr>
                <td valign="top">
                    <div class="card" style="width:20rem;">
                        <div class="card-body">
                            <h5 class="card-title" align="center">日曜</h5>
                                {% if sun_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>
                                {% else %}

                                    {% for sun_recipe in sun_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" align="center">{{ sun_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ sun_recipe.material_title }}</p>
                                        <table>

                                            {% for m in sun_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>

                                            {% endfor %}
                                        </table>
                                    {% endfor %}

                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="sun">
                                            レシピを選びなおす
                                        </button>
                                    </form>
                                {% endif %}

                          </div>
                    </div>
                </td>

                <td valign="top">
                    <div class="card" style="width: 20rem;">
                        <div class="card-body">
                            <h5 class="card-title" align="center">月曜</h5>

                                {% if mon_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>

                                {% else %}
                                    {% for mon_recipe in mon_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" align="center">{{ mon_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ mon_recipe.material_title }}</p>
                                        <table>

                                            {% for m in mon_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>
                                            {% endfor %}
                                        </table>
                                    {% endfor %}

                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="mon">
                                            レシピを選びなおす
                                        </button>

                                    </form>
                                {% endif %}
                        </div>
                    </div>
                </td>
                <td valign="top">
                    <div class="card" style="width: 20rem;">
                        <div class="card-body">
                            <h5 class="card-title" align="center">火曜</h5>

                                {% if tue_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>
                                {% else %}
                                    {% for tue_recipe in tue_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" align="center">{{ tue_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ tue_recipe.material_title }}</p>
                                        <table>

                                            {% for m in tue_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>

                                            {% endfor %}
                                        </table>
                                    {% endfor %}

                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="tue">
                                            レシピを選びなおす
                                        </button>
                                    </form>
                                {% endif %}
                        </div>
                    </div>
                </td>

                <td valign="top">
                    <div class="card" style="width: 20rem;">
                        <div class="card-body">
                            <h5 class="card-title" align="center">水曜</h5>

                                {% if wed_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>
                                {% else %}
                                    {% for wed_recipe in wed_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" align="center">{{ wed_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ wed_recipe.material_title }}</p>
                                        <table>
                                            {% for m in wed_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>
                                            {% endfor %}
                                        </table>
                                    {% endfor %}

                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="wed">
                                            レシピを選びなおす
                                        </button>
                                    </form>
                                {% endif %}
                        </div>
                    </div>
                </td>
                <td valign="top">
                    <div class="card" style="width: 20rem;">
                        <div class="card-body">
                            <h5 class="card-title" align="center">木曜</h5>
                                {% if thu_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>
                                {% else %}
                                    {% for thu_recipe in thu_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" align="center">{{ thu_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ thu_recipe.material_title }}</p>
                                        <table>
                                            {% for m in thu_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>
                                            {% endfor %}
                                        </table>
                                    {% endfor %}
                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="thu">
                                            レシピを選びなおす
                                        </button>
                                    </form>
                                {% endif %}
                        </div>
                    </div>
                </td>
                <td valign="top">
                    <div class="card " style="width: 20rem;">
                        <div class="card-body">
                            <h5 class="card-title" align="center">金曜</h5>
                                {% if fri_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>
                                {% else %}
                                    {% for fri_recipe in fri_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" align="center">{{ fri_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ fri_recipe.material_title }}</p>
                                        <table>
                                            {% for m in fri_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>
                                            {% endfor %}
                                        </table>
                                    {% endfor %}
                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="fri">
                                            レシピを選びなおす
                                        </button>
                                    </form>
                                {% endif %}
                        </div>
                    </div>
                </td>
                <td valign="top">
                    <div class="card" style="width: 20rem;">
                        <div class="card-body">
                            <h5 class="card-title">土曜</h5>
                                {% if sat_recipes == [] %}
                                    <a href="{{ url_for('recipe') }}" class="btn btn-primary p-2 m-3">レシピを選ぶ</a>
                                {% else %}
                                    {% for sat_recipe in sat_recipes %}
                                        <h6 class="card-subtitle m-3 text-muted" style="font-size:16px;" align="center">{{ sat_recipe.title }}</h6>
                                        <p class="card-text" style="font-size:14px; padding-left:20px;">{{ sat_recipe.material_title }}</p>
                                        <table>
                                            {% for m in sat_recipe.materials %}
                                            <tr>
                                                <td class="card-text" style="font-size:12px; padding-left:20px;">{{ m }}</td>
                                            </tr>
                                            {% endfor %}
                                        </table>
                                    {% endfor %}
                                    <form action="{{ url_for('delete_db') }}"  method="POST" enctype="multipart/form-data">
                                        <button  class="btn btn-secondary p-2 m-3" name="delete_recipe" value="sat">
                                            レシピを選びなおす
                                        </button>
                                    </form>
                                {% endif %}
                          </div>
                    </div>
                </td>
            </tr>
        </table>
    </div>
{% endblock %}
{% extends 'base.html' %}

{% block body %}
<h4 style="margin-left: 20px;">レシピの選択</h4>
    <p style="margin-left: 20px;">各曜日でメイン食材と副菜をそれぞれ登録してください</p>
    <div class="list-group list-group-numbered">
        <form action="{{ url_for('recipe') }}"  method="POST" enctype="multipart/form-data">
            <p class="list-group-item" style='font-size:14px; margin-left: 30px;'>
                1. 曜日にチェックを入れてください<br>
                <label for='sun' style="margin-left: 30px;"><input type="checkbox" name="day_of_week" id='sun' value="日曜">日曜</label>
                <label for='mon'><input type="checkbox" name="day_of_week" id='mon' value="月曜">月曜</label>
                <label for='tue'><input type="checkbox" name="day_of_week" id='tue' value="火曜">火曜</label>
                <label for='wed'><input type="checkbox" name="day_of_week" id='wed' value="水曜">水曜</label>
                <label for='thu'><input type="checkbox" name="day_of_week" id='thu' value="木曜">木曜</label>
                <label for='fri'><input type="checkbox" name="day_of_week" id='fri' value="金曜">金曜</label>
                <label for='sat'><input type="checkbox" name="day_of_week" id='sat' value="土曜">土曜</label>
            </p>
            <p class="list-group-item" style='font-size:14px; margin-left: 30px;'>
                2. 下記のいずれか1つにチェックを入れてください<br>
                <label style="margin-left: 30px;">メイン食材</label>
                <label for='beef' style="margin-left: 30px;"><input type="radio" name="main" id='beef' value="牛肉">牛肉</label>
                <label for='pork'><input type="radio" name="main" id='pork' value="豚肉">豚肉</label>
                <label for='chicken'><input type="radio" name="main" id='chicken' value="鶏肉">鶏肉</label>
                <label for='fish'><input type="radio" name="main" id='fish' value="魚">魚</label><br>
                <label style="margin-left: 30px;">副  菜</label>
                <label for='sub' style="margin-left: 30px;"><input type="radio" name="main" id='sub' value="副菜">副菜</label>
            </p>
            <p class="list-group-item" style='font-size:14px; margin-left: 30px;'>
                「レシピ検索」を押してください ( お好みのレシピが出るまで1.2.を繰り返してください )<br>
                <input  style="margin-left: 30px;" type="submit" name='search' value="レシピ検索">
            </p>
        </form>
        <form action="{{ url_for('register') }}"  method="POST" enctype="multipart/form-data">
            <div  style="margin-left: 50px;">
                <table>
                    <tr>
                        <td>
                            {{ recipe.day_of_week }}
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <h5>{{ recipe.title }}</h5>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            {{ recipe.material_title }}
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <table>
                                {% for m in recipe.materials %}
                                    <tr>
                                        <td>{{ m }}</td>
                                    </tr>
                                {% endfor %}
                            </table>
                        </td>
                    </tr>
                </table>
            </div>
            <p>
                <input type="hidden" name="r_day_of_week" value="{{ recipe.day_of_week }}">
                <input type="hidden" name="r_title" value="{{ recipe.title }}">
                <input type="hidden" name="r_url" value="{{ recipe.url }}">
                <input type="hidden" name="r_material_title" value="{{ recipe.material_title }}">
                <input type="hidden" name="r_material" value="{{ recipe.materials }}"><br>
            </p>
            <p style='font-size:14px; margin-left: 50px;'>
                お好みのレシピが決まったら、登録ボタンを押してください
                <input type="submit" value="登録">
            </p>
        </form>
    </div>
    <form action="{{ url_for('completion') }}"  method="POST" enctype="multipart/form-data">
        <p style="margin-left: 50px">
            <input type="submit" value="献立表を確認する">
        </p>
    </form>
{% endblock %}

⑤ 上記のコードに行き着くまでに試行錯誤したこと

  • HTMLへの表示で、苦労した記憶があること

    • スクレイピングで取得したテキスト情報に改行や、空白などの指示をする文字情報が含まれていた事

    • その文字情報がそのまま表示されるので、どの段階で処理をするコードを記述するか

    • 食材表示を行ずつにするのに、どのようにコーディングするか(結局これは、何が作用したのか不明のまま、表示ができたので突き詰めていない)

  • if関数と同様のcase関数が新たに出てきたので使ってみた

  • データベースの接続終了(ファイルを閉じる)を忘れないためのwithの使い方を知った

  • データベースからすべてのデータ、任意のデータを抽出する方法

⑥ まとめ

  • 当アプリは、ローカル環境で動作確認をして、デプロイ(一般公開)はせずに終了した(レシピ取得に使用したサイトの機能を参考にした、あくまでも学習のための作成であるため)。

  • 学習を開始して2ヶ月ほどで、2.1のデスクトップアプリと2.2のWebアプリを開発することができた。

  • 実践することで、プログラミング基礎学習のどの部分の知識が不足しているかを自覚することができ、再学習に繋がった。

  • さらに自信になった

  • プログラミング基礎学習を再実践した際に、前回学習時より理解することができた

  • Webアプリケーションの仕組みが理解できた

  • データベース、SQLの仕組みが少し理解できた

  • スクレイピングは、サイトの規約を確認して、負荷を掛けないよう注意が必要なことを学んだ

  • WebページのHTML構造を見ることができると知った

  • 可読性(コードの読みやすさ)を意識することを知った


2.3 施術サポートWebアプリ作成(2023.8~2023.9)


 2つのアプリを作成し終えた後の講師との面談で、「次に取り組みたいことはあるか?」との質問に、正業であった柔道整復師業務に従事していた際に考えていたことを伝えた。今まで作成したものは、学習過程で検索したものや、書籍の内容をコピペして、それらをもとにコーディングすることで完成させたものだったが、今回は、それらの学習の集大成として、自分で考えて作成した。

<アプリの概要>
 
施術を行う際の参考として、関節の動きに関連する筋群を表示する

<アプリ開発の背景(正業であった業務で実現したかったこと)>

  • スタッフ教育の効率化

    • 施術時に、どの筋群をターゲットにすればいいかの参考にする

    • 筋肉の働きを学習する

<開発の流れ>

① アプリに必要な情報の洗い出し

  • 筋のデータ(筋名、起始停止、作用など)をまとめたものが必要

    • データベースの作成

  • どのように表示させるか?

    • HTMLの作成(Bootstrapでの体裁)

  • ユーザーの操作はどうするか?

    • 体の部位別に表示するには、どのような関数が必要か?

      • ユーザーが頭部を選択したら、頭部に関連する筋群を表示する

      • 実際の画面

Topページ


筋群選択の様子


上腕筋群を選択表示したページ

② データベースの準備

  • 各筋の一覧情報をExcelで作成し、そのExcelをもとにデータベースを作成した。

  • リレーショナル・データベースの学習

Djangoの学習

④ デプロイ

  • 「Python デプロイ法」と選択して、無料でできるサイトの「Pythonanywhere」を選択した

  • デプロイで困ったこと

    • 「Django」のディレクトリ(フォルダ)をローカル環境に作成して、ディレクトリでアップロードする方法は失敗した(原因は未だに不明)

    • Djangoのディレクトリ構造に従って、Pythonanywhere上にディレクトリとファイルを作成して、実際に作成したコードをコピペしてなんとかデプロイ完了した

⑤ まとめ

  • 約半年で、オリジナルのWebアプリケーションをデプロイできた

  • かなり自信になった

  • 実践することで、プログラミング基礎学習のどの部分の知識が不足しているかを自覚することができ、再学習に繋がった。

  • リレーショナルデータベースが少し理解できた


2.4 データ分析と機械学習の学習(2023.10~2023.11月初旬)


① 書籍「Python実践データ分析100本ノック 第2版」を学習
② なぜこの書籍を学習したのか?
 正業であった柔道整復師業務の中で、患者の通院状況を分析しようと考えていたことを面談で話したことがきっかけ
③ まとめ

  • すでにあるデータ(起業等が作成しているExcelデータ等)を分析するもので、参考にはなった

  • どのような分析をするのか、判断できる基礎知識(数学の再学習)が必要と感じ、継続して学習することを断念(画像認識は今後学習するかもしれないと感じた)


おまけ(業務効率化のデスクトップアプリ作成)


 この頃に、また妻から、「毎日、親会社の売上表からCSVを抽出し、Excelで当日の社員別売上一覧を作成している。15~20分ほどの作業をなんとかできないか?」と業務効率化の相談があった。復習になると思い、取り組んだところ、1日で作成できてしまったことに、自身の成長を感じた。

  • 「Tkinter」で作成

  • 抽出したCSVファイルを選択し、ボタンをクリックするだけで必要な情報を計算できるようにした

  • 作業を数十秒程度に短縮できた

from tkinter import filedialog, ttk  # ユーザーがファイルを選択するメソッド
import tkinter as tk  # メソッド呼び出し時のオブジェクト名 Tk. を省略する事ができる
import pandas as pd
import os  # どの os でも操作できるようにするモジュール
import datetime as dt  # 日付・時刻の取得用
import openpyxl


if __name__ == '__main__':

    # 出力ファイル名に日付を入れる準備
    def date():

        now = dt.datetime.now()  # 今の日付時刻を取得
        time = now.strftime('%Y%m%d_%H%M')  # time を定義(yyyymmdd_hhmm)
        return time
  

    def path():

        desktop_dir = os.path.expanduser('~/Desktop')  # desktop の Path を取得
        return desktop_dir

    root = tk.Tk()  # Tk() はクラス Tk のインスタンス(オブジェクト)を生成して返す。この root が画面上のメインウィンドウに対応。

    #  root.mainloop() との間に画面構成を記述

    # ----------- ①Window作成 ----------- #

    root.title('当日売上')  # Window タイトル設定
    root.geometry('100x50')  # Window サイズ設定
    root.configure(bg="#165E83")  # Window の背景色(bg)を "#165E83"

    # ----------- ②Frameを定義 ----------- #

    style = ttk.Style()
    style.configure("TLabelframe", background="#165E83", )
    # frame1 = CreatLabel.create_labelframe(root, "csvファイルの読み込み")

    frame = tk.Frame(
        root,
        bg='#165E83',
        borderwidth=2,
        relief='flat'
    )

    # Frameを配置(grid)
    frame.grid(column=0, row=1)  # 1列目、2行目
  

    def csv_operation():

        path = filedialog.askopenfilename()
        with open(path) as f:
            df = pd.read_csv(f)

        # 特定列の抽出
        df = df.iloc[:,22:28]

        # 列の合計を計算して新しい列を追加する
        df = df.assign(合計 = df.sum(axis=1, numeric_only=True)-df['担当者コード'])

        # 行の合計を計算して新しい行を追加する
        kiki = df['機器売上(当日)'].sum(axis=0, numeric_only=True)
        kouji = df['工事売上(当日)'].sum(axis=0, numeric_only=True)
        hihoshou = df['非保証修繕売上(当日)'].sum(axis=0, numeric_only=True)
        gyoumu = df['業務売上(当日)'].sum(axis=0, numeric_only=True)
        goukei = df['合計'].sum(axis=0, numeric_only=True)
        app_list = [['', '当日合計', kiki, kouji, hihoshou, gyoumu, goukei]]

        df1 = pd.DataFrame(data = app_list, columns=['担当者コード', '担当者名称', '機器売上(当日)', '工事売上(当日)', '非保証修繕売上(当日)', '業務売上(当日)', '合計'])
        df = pd.concat([df, df1], ignore_index=True)

        # df = df.append(df.sum(numeric_only=True), ignore_index=True)

        df.to_excel(os.path.expanduser('~/Desktop') + '\当日売上_{}.xlsx'.format(date()), sheet_name='当日売上', index=False)

        root.destroy()

    btn = tk.Button(
                root,
                text='当日売上を計算する',
                background='#00FA9A',
                activebackground='#D3D3D3',
                command=csv_operation
            )

    btn.grid()

    # 最後に root.mainloop() でイベントループを開始して、ユーザーからの要求(イベント)を処理
    root.mainloop()


2.5 プロンプトエンジニアリング(2023.11月初旬~2023.12)


① 学習のきっかけ

  • 妻から「機器のメンテナンス管理システムを作れないか?」との話で、業務効率化のアプリ作成をする予定だった。

  • 業務効率化のアプリ作成を開始し、データベースの設計をしている際に、講師から「Chat GPT(生成AI)」の利用を勧められた

  • Web検索の代わりに、「MicroSoft Bing」を使い始めていた

  • 生成AIについて調べると、指示(プロンプト)が大事で、指示を作成するためのプロンプトエンジニアリングという分野があると知った

  • データベースにサンプルデータを作成する方法を「Chat GPT(生成AI)」に尋ねると、具体的なコードを出力した

  • データベース設計に関して、生成AIにサンプルコードを出力するよう指示したところ、出力されたコードの中身を理解できるまでに、自身が成長できていると感じた

② 活用した生成AI(すべて無料利用範囲)

③ プロンプト作成の学習

1.学習の参考にしたもの

  • 「プロンプトエンジニアリング」について生成AIに質問

  • YouTube

2.生成AIとの質疑応答例(「Wrtn:リートン」)

Open AIのベストプラクティス

3.まとめ

  • 生成AIは便利

  • コード生成を指示した際に出力されるコードは、確認修正が必要

  • プロンプト作成時は、あれこれ悩むより、生成AIに「プロンプトにどのように記述すれば理解できるか」を尋ねるほうが解決が早い

  • 単発で完成することはなく、何度も質問を繰り返し、調整する必要がある

  • 同じプロンプトでも、生成AIの返答は少しずつ違う

  • 生成AIに意図する返答をしてもらうには、指示(プロンプト)の仕方が重要

  • 指示(プロンプト)をどのように書けばいいかも生成AIに質問すれば解決は早い

  • 生成AIに「役割」を与え、「ゴール」を具体的に指定すると、返答の精度が向上する

  • 参考サイト

Open AIが公式サイトでプロンプトのベストプラクティス(英語なので翻訳して確認)を発表している

「Prompt Engineering Guide(プロンプトエンジニアリングの教科書)」日本語版

さいごに


 最後までお読みいただきありがとうございました。
 作業日誌としてまとめていれば、もう少し詳しく当時の心境を書けたと思います。講師からはずっと作業日誌をつけるように言われていましたが、実践していませんでした。講師との面談の中で、「今までの学習の軌跡は、きちんと残しておいたほうがいい」と再度ご指摘いただき、まとめることになりました。「できるだけ、同時の困ったことや心境などを書いたほうがいい」との提案もいただいていたので、思い出しながら書き留めました。もっと、試行錯誤したコードがあったのですが、アプリ完成時に、ややこしいからと削除してしまったのを後悔しました。
 完成したものを講師に確認してもらったところ、「きちんとまとまっている。どせなら、サイトに記事として掲載してはどうか?」とのご提案をいただき、掲載しました。
 今後はプロンプトエンジニアリングと、生成AIを活用したコーディングを探求していこうと思っています。その様子を作業日誌として、このnoteで残していくつもりです。
 ちなみに、この記事の見出し画像は、Microsoftのcopilotに作成してもらいました。

いいなと思ったら応援しよう!