ソーラーパネル稼働状況取得（AWS shellcloud上でのpython実行）

前回、時前ラズパイで、ソーラーパネルの稼働状況をPythonで取得することができました。

# 自前ラズパイでの動作
自前ラズパイでの動作は、上手く言っていました。けど、サーバーレスで実行したいため、色々試行錯誤しています。

# GAS上での動作
GASでは、上手くCRFSを取得できず、断念しました。

# 今回はAWSで行きます。
これなら、サーバーレスで実行できます。
自前のラズパイ上での実行でもいいのですが、技術的好奇心からAWSの扉を叩きます。
また、以前作成したPythonのプログラムも基本的に動きます。
まずは、shellcloud上で、作業環境を構築します。

### スクレイピング用

    $ pip install requests
    $ pip install beautifulsoup4

### GAS用

    $ pip install gspread
    $ pip install oauth2client

と以前作ったプログラムをコピペで作ります。
当時とは、HTMLの構成が変わったようで、ログインこそできるものの、
スクレイピングが上手くいきません。
以前のプログラムは、動作確認のつもりで、HTML内の数値直前までを検索・削除し、数値を抜き出すものでした。
このため、webページがちょっと改修されただけで動かなくなるものでした。
できるだけ揺らぎに強いものにしようと思います。

下記のように編集しました。

    # -*- coding: utf-8 -*-
    # 20240406
    import requests
    from bs4 import BeautifulSoup
    import re
    import csv
    from datetime import datetime, timedelta
    
    # UTCから日本時間に変換（UTC+9時間）
    current_utc_time = datetime.utcnow()
    jst_offset = timedelta(hours=9)
    jst_time = current_utc_time + jst_offset
    
    # 1.ログインページにアクセスする
    # ref:https://rurukblog.com/post/requests-cookies-session/
    url_login = "https://ctrl.kp-net.com/settingcontrol/processLogin"
    session = requests.session()
    # ログインページへのアクセス完了
    req_before_login = session.get(url_login)
    
    # ログインするための情報を準備する
    login_data = {
       'loginid': 'xxxxxxxxxxxxx',
       'loginpassword': 'yyyyyyyyyyyyyy'
    }
    
    # ログインするためにcsrfトークンが必要となるため情報を取得
    bs = BeautifulSoup(req_before_login.text, 'html.parser')
    csrf_token = bs.find(attrs={'name':'_csrf'}).get('content')
    login_data['_csrf'] = csrf_token
    
    # 2. ログインページで認証を行い、管理者ページへ遷移する
    req_after_login = session.post(url_login, data=login_data)
    
    # 3. 認証完了後のページで他ページへ遷移を行う
    url_group = 'https://ctrl.kp-net.com/settingcontrol/remotevisualization/simplevisualization/enduser/'
    req_group = session.get(url_group)
    
    # print('--- ログイン情報 ---')
    # print(login_data)
    # print('---- 認証ページへのアクセス結果 ---')
    # print(re.search(r'<title.*', req_before_login.text).group(0))
    # print(req_before_login.status_code)
    print('--- 認証完了ページへのアクセス結果 ---')
    print(re.search(r'<title.*', req_after_login.text).group(0))
    # print(req_after_login.status_code)
    # print('--- 認証完了ページからgroupページへのアクセス結果 ---')
    # print(re.search(r'<title.*', req_group.text).group(0))
    # print(req_group.status_code)
    
    # print(req_group.text)
    
    # utifulSoupを使ってHTMLをパース
    soup = BeautifulSoup(req_group.text, 'html.parser')
    
    
    # 計測データを含む要素を抽出
    numeric_area = soup.find('div', class_='numeric_area')
    
    # CSVファイルのヘッダー
    header = ['DateTime', 'Generation_Current(kW)', 'Generation_Total(kW)', 'Consumption_Current(kW)', 'Consumption_Total(kW)', 
              'Battery(%)', 'Sell_Current(kW)', 'Sell_Total(kW)', 'Buy_Current(kW)', 'Buy_Total(kW)']
    
    # CSVファイルのパス
    csv_file = 'measurement_data.csv'
    
    # データを保持するリスト
    data_rows = []
    
    # 現在の日時を取得
    jst_time_str = jst_time.strftime('%Y-%m-%d %H:%M:%S')
    
    # 各計測データを取得し、リストに追加
    if numeric_area:
            tables = numeric_area.find_all('table')
            # CSVに書き込むデータをリストにまとめる
            data_row = [jst_time_str,'', '', '', '', '', '', '', '', '', '']  # 初期化
            for table in tables:
                    data_type = table.find('th').text.strip()  # データの種類（発電、消費、蓄電池、売電、買電）
                    rows = table.find_all('tr')[1:]  # 最初の行はヘッダーなので除外
                    for row in rows:
                            cells = row.find_all('td')
                            current_text = cells[0].text.strip()  # 現在値
                            total_text = cells[1].text.strip()  # 本日累計
                # 数値のみを抽出する正規表現パターン
                            number_pattern = r'[\d\.]+'  # 数字または小数点のみを抽出するパターン
    
                    # 現在値のテキストから数値のみを抽出
                            current_match = re.search(number_pattern, current_text)
                            if current_match:
                                    current_value = float(current_match.group())
                            else:
                                    current_value = current_text  # 数値が見つからない場合は蓄電池の状況にする
    
                    # 本日累計のテキストから数値のみを抽出
                            total_match = re.search(number_pattern, total_text)
                            if total_match:
                                    total_value = float(total_match.group())
                            else:
                                    total_value = None  # 数値が見つからない場合はNoneに
                            print(f"{data_type}: 現在値={current_value}, 本日累計={total_value}")
    
                # CSVに書き込むデータをリストにまとめる
    #               data_row = [current_datetime, '', '', '', '', '', '', '', '', '','']  # 初期化
                            if data_type == '発電':
                                    data_row[1] = current_value
                                    data_row[2] = total_value
                            elif data_type == '消費':
                                    data_row[3] = current_value
                                    data_row[4] = total_value
                            elif data_type == '蓄電池':
                                    data_row[5] = current_value
                                    data_row[6] = total_value
                            elif data_type == '売電':
                                    data_row[7] = current_value
                                    data_row[8] = total_value
                            elif data_type == '買電':
                                    data_row[9] = current_value
                                    data_row[10] = total_value
                            data_rows.append(data_row)
    
        # CSVファイルにデータを書き込む
            with open(csv_file, 'a', newline='') as f:
                    writer = csv.writer(f)
                    writer.writerow(data_row)
    
    else:
            print("計測データが見つかりませんでした")


とりあえず、リモートで各種値の取得ができることがわかりました。
これで、リモートAWS上のローカルのCSVに出力できます。
これを定期実行・Googleスプレッドシートなどに出力し、記録していきます。

# Googleスプレッドシートへ出力

### 下準備

https://mori-memo.hateblo.jp/entry/2023/05/06/152208

https://mori-memo.hateblo.jp/entry/2023/05/07/160626

などを参考に出力先ファイルを作っていましたが、初回起動時のブラウザの起動に失敗してしまいます。
念の為、SSH接続のラズパイでも同じことをしましたが、リモートでの初回起動はできないようです。CUIでブラウザの操作を行おうとしてる感じになるっぽいでした。

### 気を取り直して

https://www.teijitaisya.com/python-gsheets/

を参考にスプレッドシートへ出力の設定・pythonプログラムの改修を行います。



    # -*- coding: utf-8 -*-
    # 20240404
    import requests
    from bs4 import BeautifulSoup
    import re
    import csv
    from datetime import datetime, timedelta
    import gspread
    from google.oauth2.service_account import Credentials
    # お決まりの文句
    # 2つのAPIを記述しないとリフレッシュトークンを3600秒毎に発行し続けなければならない
    scope = ['https://www.googleapis.com/auth/spreadsheets','https://www.googleapis.com/auth/drive']
    #ダウンロードしたjsonファイル名をクレデンシャル変数に設定。
    credentials = Credentials.from_service_account_file("bbbbbbbbbb.json", scopes=scope)
    #OAuth2の資格情報を使用してGoogle APIにログイン。
    gc = gspread.authorize(credentials)
    #スプレッドシートIDを変数に格納する。
    SPREADSHEET_KEY = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'
    # スプレッドシート（ブック）を開く
    workbook = gc.open_by_key(SPREADSHEET_KEY)
    
    # スプレッドシート（ブック）を開く
    #workbook = gc.open_by_key(SPREADSHEET_KEY)
    # シートの一覧を取得する。（リスト形式）
    worksheets = workbook.worksheets()
    print(worksheets)
    # シートを開く
    worksheet = workbook.worksheet('power')
    
    
    # UTCから日本時間に変換（UTC+9時間）
    current_utc_time = datetime.utcnow()
    jst_offset = timedelta(hours=9)
    jst_time = current_utc_time + jst_offset
    
    # 1.ログインページにアクセスする
    # ref:https://rurukblog.com/post/requests-cookies-session/
    url_login = "https://ctrl.kp-net.com/settingcontrol/processLogin"
    session = requests.session()
    # ログインページへのアクセス完了
    req_before_login = session.get(url_login)
    
    # ログインするための情報を準備する
    login_data = {
       'loginid': 'xxxxxxxxxxxxxxxxxxxxx',
       'loginpassword': 'yyyyyyyyyyyyyyyyyyyyyyy'
    }
    
    # ログインするためにcsrfトークンが必要となるため情報を取得
    bs = BeautifulSoup(req_before_login.text, 'html.parser')
    csrf_token = bs.find(attrs={'name':'_csrf'}).get('content')
    login_data['_csrf'] = csrf_token
    
    # 2. ログインページで認証を行い、管理者ページへ遷移する
    req_after_login = session.post(url_login, data=login_data)
    
    # 3. 認証完了後のページで他ページへ遷移を行う
    url_group = 'https://ctrl.kp-net.com/settingcontrol/remotevisualization/simplevisualization/enduser/'
    req_group = session.get(url_group)
    
    # print('--- ログイン情報 ---')
    # print(login_data)
    # print('---- 認証ページへのアクセス結果 ---')
    # print(re.search(r'<title.*', req_before_login.text).group(0))
    # print(req_before_login.status_code)
    print('--- 認証完了ページへのアクセス結果 ---')
    print(re.search(r'<title.*', req_after_login.text).group(0))
    # print(req_after_login.status_code)
    # print('--- 認証完了ページからgroupページへのアクセス結果 ---')
    # print(re.search(r'<title.*', req_group.text).group(0))
    # print(req_group.status_code)
    
    # print(req_group.text)
    
    # utifulSoupを使ってHTMLをパース
    soup = BeautifulSoup(req_group.text, 'html.parser')
    
    
    # 計測データを含む要素を抽出
    numeric_area = soup.find('div', class_='numeric_area')
    
    # CSVファイルのヘッダー
    header = ['DateTime', 'Generation_Current(kW)', 'Generation_Total(kW)', 'Consumption_Current(kW)', 'Consumption_Total(kW)', 
              'Battery(%)', 'Sell_Current(kW)', 'Sell_Total(kW)', 'Buy_Current(kW)', 'Buy_Total(kW)']
    
    # CSVファイルのパス
    csv_file = 'measurement_data.csv'
    
    # データを保持するリスト
    data_rows = []
    
    # 現在の日時を取得
    jst_time_str = jst_time.strftime('%Y-%m-%d %H:%M:%S')
    
    # 各計測データを取得し、リストに追加
    if numeric_area:
    	tables = numeric_area.find_all('table')
    	# CSVに書き込むデータをリストにまとめる
    	data_row = [jst_time_str,'', '', '', '', '', '', '', '', '', '']  # 初期化
    	for table in tables:
    		data_type = table.find('th').text.strip()  # データの種類（発電、消費、蓄電池、売電、買電）
    		rows = table.find_all('tr')[1:]  # 最初の行はヘッダーなので除外
    		for row in rows:
    			cells = row.find_all('td')
    			current_text = cells[0].text.strip()  # 現在値
    			total_text = cells[1].text.strip()  # 本日累計
                # 数値のみを抽出する正規表現パターン
    			number_pattern = r'[\d\.]+'  # 数字または小数点のみを抽出するパターン
    
    		# 現在値のテキストから数値のみを抽出
    			current_match = re.search(number_pattern, current_text)
    			if current_match:
    				current_value = float(current_match.group())
    			else:
    				current_value = current_text  # 数値が見つからない場合は蓄電池の状況にする
    
    		# 本日累計のテキストから数値のみを抽出
    			total_match = re.search(number_pattern, total_text)
    			if total_match:
    				total_value = float(total_match.group())
    			else:
        				total_value = None  # 数値が見つからない場合はNoneに
    			print(f"{data_type}: 現在値={current_value}, 本日累計={total_value}")
    
                # CSVに書き込むデータをリストにまとめる
    #		data_row = [current_datetime, '', '', '', '', '', '', '', '', '','']  # 初期化
    			if data_type == '発電':
    				data_row[1] = current_value
    				data_row[2] = total_value
    			elif data_type == '消費':
    				data_row[3] = current_value
    				data_row[4] = total_value
    			elif data_type == '蓄電池':
    				data_row[5] = current_value
    				data_row[6] = total_value
    			elif data_type == '売電':
    				data_row[7] = current_value
    				data_row[8] = total_value
    			elif data_type == '買電':
    				data_row[9] = current_value
    				data_row[10] = total_value
    			data_rows.append(data_row)
    
        # CSVファイルにデータを書き込む
    	with open(csv_file, 'a', newline='') as f:
    		writer = csv.writer(f)
    		writer.writerow(data_row)
    #	worksheet.update('A2:K2', data_row)
    	index = 2
    	worksheet.insert_row(data_row, index)
    
    else:
    	print("計測データが見つかりませんでした。")


で、一旦、AWSのshellcloud上で、pythonを使って、ソーラーパネル状況をスプレッドシートへ出力できるようになりました。

# あとは
定期実行にまつわるエトセトラを設定・調整することが必要です。
簡単に調べた感じでは、crontabは実質使用できないなどの成約があります。
AWSのcloud9が有力です。