【Python,Streamlit】保育園の入所状況表示WEBスクレイピングアプリ

# はじめに
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/722467/652567c8-3e81-e24b-3863-18daf9fa9ffd.gif)

Pythonの勉強のためにまずWEBスクレイピングに挑戦してみました。Streamlitというサービスを使えば簡単にデプロイもできます。
https://otaruit-ebetsu-childcare-info-streamlit-app-7uabfx.streamlit.app/

# スクレイピングのコード
pandasを使って取得したページを読み取ります。
まずコンソールで表示してみます。
```python
import pandas as pd

# 取得先URL
url = 'https://www.city.ebetsu.hokkaido.jp/site/kosodate/72891.html'

# 合致するワードが入ったテーブルを取得
df_list = pd.read_html(url, match='【公立】やよい')

print(df_list[0])

```

実行結果
```
       Unnamed: 0     ０歳 １歳 ２歳 ３歳 ４歳 ５歳
0         【公立】やよい      ☓  ☓  ☓  ☓  △  ☓
1         【公立】よつば      ☓  ☓  ☓  ☓  ☓  ☓
2            あかしや      ☓  ☓  ☓  ☓  ☓  ☓
3           江別桃の花      ☓  ☓  ☓  ☓  ☓  ☓
4   memorytree西野幌      ☓  ☓  ☓  ☓  〇  △
5        夢ふうせん東野幌      ☓  ☓  ☓  ☓  ☓  ☓
6    こどもみらい保育園野幌園      ☓  ☓  ☓  ☓  △  ☓
7   ラブクローバーの保育園江別      ☓  ☓  ☓  ☓  ☓  ☓
8              誠染      △  ☓  ☓  ☓  ☓  ☓
9               愛      ☓  ☓  ☓  ☓  ☓  ☓
10            わかば      ☓  ☓  ☓  ☓  ☓  ☓
11           ゆめのみ  受入 なし  ☓  ☓  ☓  ☓  ☓
12       おひさまのっぽろ      ☓  ☓  ☓  ☓  ☓  ☓
13       きっずぱーく江別      ☓  ☓  ☓  ☓  △  △
14       きっずぱーく野幌      ☓  ☓  ☓  ☓  ☓  ☓
15         野幌みつばち      ☓  ☓  ☓  ☓  ☓  ☓
```


ここで一行ずつ取得して見やすくします。

```python
import pandas as pd

# 取得先URL
url = 'https://www.city.ebetsu.hokkaido.jp/site/kosodate/72891.html'
# 合致するワードが入ったテーブルを取得
df_list = pd.read_html(url, match='【公立】やよい')

# テーブルが見つかった場合、最初のテーブルを取得
if df_list:
    df = df_list[0]
    
    # 各行を表示
    for index, row in df.iterrows():
        print(row)
else:
    print("指定したテーブルが見つかりませんでした。")
```


実行結果
```
Unnamed: 0    【公立】やよい
０歳                  ☓
１歳                  ☓
２歳                  ☓
３歳                  ☓
４歳                  △
５歳                  ☓
Name: 0, dtype: object
Unnamed: 0    【公立】よつば
０歳                  ☓
１歳                  ☓
２歳                  ☓
３歳                  ☓
４歳                  ☓
５歳                  ☓
Name: 1, dtype: object
Unnamed: 0    あかしや
０歳               ☓
１歳               ☓
２歳               ☓
３歳               ☓
４歳               ☓
５歳               ☓
Name: 2, dtype: object
Unnamed: 0    江別桃の花
０歳                ☓
１歳                ☓
２歳                ☓
３歳                ☓
４歳                ☓
５歳                ☓
Name: 3, dtype: object
Unnamed: 0    memorytree西野幌
０歳                        ☓
１歳                        ☓
２歳                        ☓
３歳                        ☓
４歳                        〇
５歳                        △
Name: 4, dtype: object
Unnamed: 0    夢ふうせん東野幌
０歳                   ☓
１歳                   ☓
２歳                   ☓
３歳                   ☓
４歳                   ☓
５歳                   ☓
Name: 5, dtype: object
Unnamed: 0    こどもみらい保育園野幌園
０歳                       ☓
１歳                       ☓
２歳                       ☓
３歳                       ☓
４歳                       △
５歳                       ☓
Name: 6, dtype: object
Unnamed: 0    ラブクローバーの保育園江別
０歳                        ☓
１歳                        ☓
２歳                        ☓
３歳                        ☓
４歳                        ☓
５歳                        ☓
Name: 7, dtype: object
Unnamed: 0    誠染
０歳             △
１歳             ☓
２歳             ☓
３歳             ☓
４歳             ☓
５歳             ☓
Name: 8, dtype: object
Unnamed: 0    愛
０歳            ☓
１歳            ☓
２歳            ☓
３歳            ☓
４歳            ☓
５歳            ☓
Name: 9, dtype: object
Unnamed: 0    わかば
０歳              ☓
１歳              ☓
２歳              ☓
３歳              ☓
４歳              ☓
５歳              ☓
Name: 10, dtype: object
Unnamed: 0     ゆめのみ
０歳            受入 なし
１歳                ☓
２歳                ☓
３歳                ☓
４歳                ☓
５歳                ☓
Name: 11, dtype: object
Unnamed: 0    おひさまのっぽろ
０歳                   ☓
１歳                   ☓
２歳                   ☓
３歳                   ☓
４歳                   ☓
５歳                   ☓
Name: 12, dtype: object
Unnamed: 0    きっずぱーく江別
０歳                   ☓
１歳                   ☓
２歳                   ☓
３歳                   ☓
４歳                   △
５歳                   △
Name: 13, dtype: object
Unnamed: 0    きっずぱーく野幌
０歳                   ☓
１歳                   ☓
２歳                   ☓
３歳                   ☓
４歳                   ☓
５歳                   ☓
Name: 14, dtype: object
Unnamed: 0    野幌みつばち
０歳                 ☓
１歳                 ☓
２歳                 ☓
３歳                 ☓
４歳                 ☓
５歳                 ☓
```
加工しやすいデータになりました。

# 情報を適切な形式で格納する
下記コードでデータを整理しました。
それぞれの保育園の年齢ごとの対応状況を辞書型で格納しています。
Pythonはあまりなじみがなかったのですが、型は知っていたので「このようにデータを整理したい」というイメージがあれば割と簡単にできました。

```python
def get_nursary_info(match_string):
    df_list = pd.read_html(url, match=match_string)
    if df_list:
        df = df_list[0]
        nursary_info_list = []
        for index, row in df.iterrows():
            availability = {}
            for column in df.columns[1:]:
                availability[column] = row[column]

            nursary_info = {
                "施設名": row[df.columns[0]],
                **availability
            }
            nursary_info_list.append(nursary_info)
        return nursary_info_list
    else:
        return []
```

ところで先ほどの関数の引数には下記を格納します。
それぞれのテーブルに入っている文字列を入れます。それ以外のテーブル名などのキーワードがあればそちらを指定したほうがよさそうです。

```python
childcare = get_nursary_info('【公立】やよい')
community_childcare = get_nursary_info('あすかの森')
certified_childcare = get_nursary_info('ぞうさんハウス')
```


# 選択された条件で表示する
選択できる条件は２つです。年齢と対応状況（〇、×、△）。
選択された状態を保存する変数が下記です。

```python
age_options = ['０歳', '１歳', '２歳', '３歳', '４歳', '５歳']
availability_options = ['〇', '△', '☓']


current_month = str(int(datetime.now().strftime('%m'))) 
st.title(f'江別市保育園{current_month}月の入所状況')

st.write("〇：空きあり　△：若干空きあり　☓：空きなし")

if 'selected_age' not in st.session_state:
    st.session_state.selected_age = age_options[0]
if 'selected_availability' not in st.session_state:
    st.session_state.selected_availability = availability_options[0]

selected_age = st.selectbox('年齢を選択:', age_options, index=age_options.index(st.session_state.selected_age))
selected_availability = st.selectbox('対応状況を選択:', availability_options, index=availability_options.index(st.session_state.selected_availability))

st.session_state.selected_age = selected_age
st.session_state.selected_availability = selected_availability

col1, col2 = st.columns([2, 1])

def display_filtered_table(data, age, availability):
    st.write(f"条件 - 年齢: {age}, 対応状況: {availability}")
    filtered_data = [entry for entry in data if entry.get(age) == availability]
    
    if filtered_data:
        df = pd.DataFrame(filtered_data)
        st.dataframe(df)
        return df
    else:
        st.write("条件に一致するデータがありません")
        return None

st.header('保育所')
df_childcare = display_filtered_table(childcare, selected_age, selected_availability)

st.header('認定こども園')
df_community_childcare = display_filtered_table(community_childcare, selected_age, selected_availability)

st.header('地域型保育')
df_certified_childcare = display_filtered_table(certified_childcare, selected_age, selected_availability)

```

st.session_state　で年齢か対応状況の選択項目が変わることがトリガーとなってdisplay_filtered_table関数が実行されます。
条件に合った表を書き出すようになっています。