背景・実現したいこと
kintoneに入力したデータをPythonにてnormalize化し、csv出力を行う。
このcsv出力したものを再度読み込みOracleデータベースへデータ入力をしたいです。
現状、データベースへの入力までできていますが時々エラーが出てしまい困っております。
条件
kintone入力時にチェックボックスがあるのですが一つでもチェックを外しているとエラーになる。
(これまでは全ての項目を入力していたのでエラーが発生しませんでした。)
見立てではデータが格納されている’records’内の’CHECK_BOX’, ‘value’: [ ]の[ ]をnormalize化する際に’ 'にすればエラーはなくなると考えています。
しかし、kintoneでデータ入力時に行数を増やせるのですが、増やす際にチェックボックスを用いております。
このチェックボックスは毎回使うわけでもないにも関わらずエラーが出ていないことも気になる点です。
以下、抜粋したソースコードです。
----------------------------------------------------------------
import base64
import urllib.request
import json
import requests
import pandas as pd
from pandas.io.json import json_normalize
import csv
import time
from datetime import datetime as dt
from datetime import timedelta
import datetime
from io import BytesIO
import math
import cx_Oracle
import re
import sys
from logerrmsg import WarningMsg
from logerrmsg import ErrorMsg
from loginfomsg import InfoMsg
RequestQuery = "日付=YESTERDAY()"
RequestOffsetRecord = 100
## リクエスト処理を関数化
## offsetCount:queryで利用するoffsetを設定(limitで100件まで取得し、offsetでxxx件以降を取得する)
## offsetの上限はKINTONEで10,000に制限されているため、大量のデータを取得してセットする場合はカーソルを使用する必要あり
def GetKintoneRequest(offsetCount):
## パスワード認証 の部分
## LOGIN と PASSを「:」でつないでbase64でエンコード
#AUTH = base64.b64encode((LOGIN + ":" + PASS).encode())
## ヘッダ作成
headers = {
"Host":DOMAIN + ".cybozu.com:443",
"X-Cybozu-API-Token":API ,
"Content-Type": "application/json",
}
## body作成
body = {
"app":appno,
"id":record_no,
"query":RequestQuery + " limit " + str(RequestOffsetRecord) + " offset " + str(RequestOffsetRecord * offsetCount),
#"fields":["運行記録TBL","value"],
"totalCount":True
}
## リクエスト作成
req = urllib.request.Request(
url=uri, ## url
data=json.dumps(body).encode(), ## body
headers=headers, ## header
method="GET", ## GET
)
## リクエスト送信 結果受け取り ※※100件以上の場合は、ループで処理する必要あり
try:
response = urllib.request.urlopen(req)
except urllib.error.URLError as e:## エラーが生じた場合は補足する
# https://docs.python.org/ja/3/howto/urllib2.html tryの参考
#もしeに"reason"という文字列があればTrueを返す
if hasattr(e, "reason"):
res_error = (
"We failed to reach a server." + "\n" +
"Reason: " + e.reason + "\n"
)
print(res_error)
elif hasattr(e, 'code'):
res_error = (
'The server couldn\'t fulfill the request.' + "\n" +
'Error code: ', e.code + "\n"
)
print(res_error)
## エラーの場合は終了させる
exittotalCount
else:
#res_dict = json.load(response)
#print(res_dict)
return response
##リクエスト処理を呼び出し
res_dict = json.load(GetKintoneRequest(0))
print (res_dict)
print (res_dict["totalCount"])
#res_dictの"totalCount"が0のときシステムを抜ける
if (res_dict["totalCount"]) == '0':
print("前日データがないため終了します。")
sys.exit()
## read_jsonした結果だとネストしたjsonを展開できないのでjson_normalizeで展開させる
df = pd.read_json(json.dumps(res_dict))
## totalcountを確認
if df["totalCount"][0] > RequestOffsetRecord :
#totalCountがoffsetレコード以上ある場合は、ループで取得させる(ループ回数は totalCount / RequestOffsetRecordの切り捨て)
for i in range(math.floor(df["totalCount"][0] / RequestOffsetRecord)):
##リクエスト処理を呼び出し
res_dict2 = json.load(GetKintoneRequest(i+1))
## read_jsonした結果だとネストしたjsonを展開できないのでjson_normalizeで展開させる
df2 = pd.read_json(json.dumps(res_dict2))
## pandasライブラリを使用してjsonファイルを結合する
df = pd.concat([df,df2])
#[ ]を''に
if (res_dict["records"]) == [ ]:
[[ ]] = [' ']
## normalizeする前にチェック項目にnullが無いよう加工
#df['最新時刻_朝礼_開始'].fillna(df[''], inplace=True)
## 運行記録TBLはサブテーブルとなっているため、ヘッダー部、データ部を分けて取得する必要がある
## [ヘッダー部の項目一覧を取得(明細部の出力時に使用)]
output_Column_List = [ ]
## データフレーム変換
df_Keyjson = json_normalize(data=df["records"])
for i in df_Keyjson:
if i.find(".value") != -1 :
## valueの記載が見つかった場合
if i.find("運行記録TBL") == -1 :
## 運行記録テーブルは除外とする
output_Column_List.append(i.split("."))
print (output_Column_List)
## [明細部の項目一覧の取得]
## データフレームに変換
## record_pathで明細部のデータを展開させる
## metaに取り出したいヘッダー項目をセットする(ヘッダー部で取り出した値)
df_Datajson = json_normalize(data=df["records"]
,record_path=[["運行記録TBL","value"]]
,meta=output_Column_List)
## 加工用データフレームの準備print
output_json = df_Datajson
## データフレーム加工(データフレーム分ループさせる)
for i in df_Datajson:
## 項目名をチェックし、type項目は不要なため削除する
if i.find(".type") != -1 :
## typeの記載が見つかった場合
output_json = output_json.drop(i,axis=1)
## 項目名をわかりやすく加工(.value、value.の削除)
for i in output_json:
output_json = output_json.rename(columns={i:i.replace(".value","").replace("value.","")})
## ソート処理
#output_json = output_json.sort_values(["$id","id"], ascending=[True, True])
## CSV出力
output_json.to_csv("out.csv", encoding="utf-8_sig")
cnt_row = 0
meisai_no = 0
## 作業日を前日にセット
now = datetime.datetime.now() + timedelta(days=-1)
sagyo_date = now.strftime('%Y-%m-%d')
#Oracleに接続
#データベースに接続し、セッションを確立
try:
with cx_Oracle.connect(user=''
,password=''
,dsn=''
,encoding="UTF-8") as conn:
#SQL文をハンドリングするためのカーソルオブジェクトを作成
cur = conn.cursor()
#sql = 'select count(*) from '
csv_file_path = "out.csv"
# CSVファイルを開く
with open(csv_file_path, newline="", encoding="utf-8") as csvfile:
csv_reader = csv.reader(csvfile, delimiter=",")
header = next(csv_reader)
for row in csv_reader:
cnt_row += 1
meisai_no += 1
print('meisai_no=',meisai_no)
delimiters = "TZ"
# 正規表現パターンを作成する
pattern = '|'.join(map(re.escape, delimiters))
# re.split()を使って、複数のデリミタで文字列を分割する
kekka1 = re.split(pattern, row[56])
# 確認のみ取り出す
kekka1_3 = kekka1[0]+" "+kekka1[1]
dt2 = datetime.datetime.strptime(kekka1_3, '%Y-%m-%d %H:%M:%S')
# 結果を表示する
#print(kekka1_3)
#print(dt2)
delimiters = "TZ"
# 正規表現パターンを作成する
pattern = '|'.join(map(re.escape, delimiters))
# re.split()を使って、複数のデリミタで文字列を分割する
kekka2 = re.split(pattern, row[62])
# 確認のみ取り出す
kekka2_3 = kekka2[0]+" "+kekka2[1]
dt3 = datetime.datetime.strptime(kekka2_3, '%Y-%m-%d %H:%M:%S')
# 結果を表示する
#print(kekka2_3)
#print(dt3)
dt4 = (row[10].replace("['確認']",'確認').replace("[]",'')) # 卸場チェック
dt5 = (row[13].replace("['確認']",'確認').replace("[]",'')) # 補助ロック確認
dt6 = (row[17].replace("['✓']",'有').replace("[]",'')) # コピー
dt7 = (row[20].replace("['取得']",'取得').replace("[]",'')) # 最新時刻_卸
dt8 = (row[23].replace("['取得']",'取得').replace("[]",'')) # 最新時刻_積込
dt9 = (row[26].replace("['有']",'有').replace("[]",'')) # 試し計量
sql = 'insert into\
(COLUMN1, ID, KAKUDO_TATE, OROSIBA_KEIDO, KAKUDO_YOKO,\
HINSYU, TUMIKOMI_TIME, TAIKI_TIME, TUMIBA_KEIDO, OROSIBA_NAME,\
OROSIBA_CHK, KAISU, TUMIBA_IDO, HOJOLOCK_CHK, OROSIBA_LOOKUP,\
HINMEI, TUMIBA_LOOKUP, COPY_MRK, OROSIBA_AREA, HINMEI_LOOKUP,\
LATEST_TIME_OROSI, JURYO, TUMIBA_NAME, LATEST_TIME_TUMIKOMI, BIKO_TENYURYOKU,\
LINE_NO, TAMESIKEIRYO, OROSI_TIME, TUMIBA_AREA, OROSIBA_IDO,\
BIKO_LIST, CHOREI_STTIME, RECORD_NO, UPDATE_NAMECD, UPDATE_NAME,\
TENKEN_STTIME, UNKOKAISU, SYOZOKU, DRIVER_NAME, END_METER,\
YONS_STTIME, MEAL_CHK, DRIVE_DATE, TENKEN_EDTIME, CHOREI_EDTIME,\
CREATE_NAMECD, CREATE_NAME, VERSION, MLS_JUNSYU,UPDATE_DATETIME,\
START_METER, SYABAN, DISTANCE, YONS_EDTIME,ROKKOUROTAIOU,\
CREATE_DATETIME, RECORD_NO_CPY)\
VALUES\
(:1, :2, :3, :4, :5,\
:6, :7, :8, :9, :10,\
:11, :12, :13, :14, :15,\
:16, :17, :18, :19, :20,\
:21, :22, :23, :24, :25,\
:26, :27, :28, :29, :30,\
:31, :32, :33, :34, :35,\
:36, :37, :38, :39, :40,\
:41, :42, :43, :44, :45,\
:46, :47, :48, :49, :50,\
:51, :52, :53, :54, :55,\
:56, :57)'
if (len(row[1]) == 0):
row[1] = 0
if (len(row[2]) == 0):
row[2] = 0
if (len(row[3]) == 0):
row[3] = 0
if (len(row[4]) == 0):
row[4] = 0
if (len(row[7]) == 0):
row[7] = 0
if (len(row[8]) == 0):
row[8] = 0
if (len(row[11]) == 0):
row[11] = 0
if (len(row[12]) == 0):
row[12] = 0
if (len(row[21]) == 0):
row[21] = 0
if (len(row[25]) == 0):
row[25] = 0
if (len(row[29]) == 0):
row[29] = 0
if (len(row[32]) == 0):
row[32] = 0
if (len(row[40]) == 0):
row[40] = 0
if (len(row[43]) == 0):
row[43] = 0
if (len(row[53]) == 0):
row[53] = 0
if (len(row[58]) == 0):
row[58] = 0
if (len(row[59]) == 0):
row[59] = 0
dt=int(row[0]),int(row[1]),float(row[2]),float(row[3]),float(row[4]),\
row[5],row[6],float(row[7]),float(row[8]),row[9],\
dt4,int(row[11]),float(row[12]),dt5,row[14],\
row[15],row[16],dt6,row[18],row[19],\
dt7,int(row[21])/1000,row[22],dt8,row[24],\
int(row[25]),dt9,row[27],row[28],float(row[29]),\
row[30],row[31],int(row[32]),row[35],row[36],\
row[39],int(row[40]),row[41],row[42],float(row[43]),\
row[44],row[45],row[46],row[47],row[48],\
row[50],row[51],int(row[53]),row[54],dt2,\
row[57],row[58],float(row[59]),row[60],dt10,\
dt3,row[63]
以下、'records'の内容です。
---------------------------------------------------------------------------
{'records': [{'開始時刻_朝礼': {'type': 'TIME', 'value': None},
'レコード番号': {'type': 'RECORD_NUMBER', 'value': '599'},
'最新時刻_朝礼_終了': {'type': 'CHECK_BOX', 'value': []},
'最新時刻_点検_開始': {'type': 'CHECK_BOX', 'value': []},
'更新者': {'type': 'MODIFIER', 'value': {'code': , 'name':}},
'ユーザー選択': {'type': 'USER_SELECT', 'value': [{'code': , 'name': }]},
'最新時刻_4S_終了': {'type': 'CHECK_BOX', 'value': []},
'開始時刻_点検': {'type': 'TIME', 'value': None},
'運行回数': {'type': 'CALC', 'value': '1'},
'所属': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'氏名': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'到着メーター': {'type': 'NUMBER', 'value': ''},
'開始時刻_4S': {'type': 'TIME', 'value': None},
'食事': {'type': 'RADIO_BUTTON', 'value': '食事を取った'},
'日付': {'type': 'DATE', 'value': '2023-07-10'},
'終了時刻_点検': {'type': 'TIME', 'value': None},
'終了時刻_朝礼': {'type': 'TIME', 'value': None},
'最新時刻_朝礼_開始': {'type': 'CHECK_BOX', 'value': []},
'作成者': {'type': 'CREATOR', 'value': {'code':, 'name': }},
'最新時刻_点検_終了': {'type': 'CHECK_BOX', 'value': []},
'$revision': {'type': '__REVISION__', 'value': '2'},
'マストルール遵守結果': {'type': 'RADIO_BUTTON', 'value': '遵守できなかった'},
'最新時刻_4S_開始': {'type': 'CHECK_BOX', 'value': []},
'更新日時': {'type': 'UPDATED_TIME', 'value': '2023-07-11T01:45:00Z'},
'出発メーター': {'type': 'NUMBER', 'value': '10000'},
'車番': {'type': 'DROP_DOWN', 'value': None},
'距離': {'type': 'CALC', 'value': '-10000'},
'終了時刻_4S': {'type': 'TIME', 'value': None},
'運行記録TBL': {'type': 'SUBTABLE', 'value':
[{, 'value': {'角度縦': {'type': 'NUMBER', 'value': ''},
'卸_経度': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'角度横': {'type': 'NUMBER', 'value': ''},
'品種': {'type': 'DROP_DOWN', 'value': None},
'積込時刻': {'type': 'TIME', 'value': '00:00'},
'待機時間': {'type': 'NUMBER', 'value': ''},
'積込_経度': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'卸場名': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'卸場チェック': {'type': 'CHECK_BOX', 'value': []},
'回数': {'type': 'NUMBER', 'value': '1'},
'積込_緯度': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'補助ロック確認': {'type': 'CHECK_BOX', 'value': []},
'卸場ルックアップ': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'品名': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'積場ルックアップ': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'コピー': {'type': 'CHECK_BOX', 'value': []},
'卸場エリア': {'type': 'DROP_DOWN', 'value': None},
'品名ルックアップ': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'最新時刻_卸': {'type': 'CHECK_BOX', 'value': []},
'重量': {'type': 'NUMBER', 'value': ''},
'積場名': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'最新時刻_積込': {'type': 'CHECK_BOX', 'value': []},
'備考_手入力': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'行数': {'type': 'NUMBER', 'value': '1'},
'試計量': {'type': 'CHECK_BOX', 'value': []},
'卸時刻': {'type': 'TIME', 'value': '00:00'},
'積場エリア': {'type': 'DROP_DOWN', 'value': None},
'卸_緯度': {'type': 'SINGLE_LINE_TEXT', 'value': ''},
'備考_リスト': {'type': 'DROP_DOWN', 'value': None}}}]},
'作成日時': {'type': 'CREATED_TIME', 'value': '2023-07-10T06:31:00Z'},
'$id': {'type': '__ID__', 'value': '599'}}], 'totalCount': '1'}
以下、エラーメッセージです。
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[78], line 201
196 print('2=',df)
197 ## [明細部の項目一覧の取得]
198 ## データフレームに変換
199 ## record_pathで明細部のデータを展開させる
200 ## metaに取り出したいヘッダー項目をセットする(ヘッダー部で取り出した値)
--> 201 df_Datajson = json_normalize(data=df["records"]
202 ,record_path=[["運行記録TBL","value"]]
203 ,meta=output_Column_List)
204 ## 加工用データフレームの準備print
205 output_json = df_Datajson
File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\util\_decorators.py:63, in deprecate.<locals>.wrapper(*args, **kwargs)
60 @wraps(alternative)
61 def wrapper(*args, **kwargs) -> Callable[..., Any]:
62 warnings.warn(warning_msg, klass, stacklevel=stacklevel)
---> 63 return alternative(*args, **kwargs)
File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\io\json\_normalize.py:534, in _json_normalize(data, record_path, meta, meta_prefix, record_prefix, errors, sep, max_level)
530 if k in result:
531 raise ValueError(
532 f"Conflicting metadata name {k}, need distinguishing prefix "
533 )
--> 534 result[k] = np.array(v, dtype=object).repeat(lengths)
535 return result
File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\core\frame.py:3980, in DataFrame.__setitem__(self, key, value)
3977 self._setitem_array([key], value)
3978 else:
3979 # set column
-> 3980 self._set_item(key, value)
File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\core\frame.py:4174, in DataFrame._set_item(self, key, value)
4164 def _set_item(self, key, value) -> None:
4165 """
4166 Add series to DataFrame in specified column.
4167
(...)
4172 ensure homogeneity.
4173 """
-> 4174 value = self._sanitize_column(value)
4176 if (
4177 key in self.columns
4178 and value.ndim == 1
4179 and not is_extension_array_dtype(value)
4180 ):
4181 # broadcast across multiple columns if necessary
4182 if not self.columns.is_unique or isinstance(self.columns, MultiIndex):
File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\core\frame.py:4915, in DataFrame._sanitize_column(self, value)
4912 return _reindex_for_setitem(Series(value), self.index)
4914 if is_list_like(value):
-> 4915 com.require_length_match(value, self.index)
4916 return sanitize_array(value, self.index, copy=True, allow_2d=True)
File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\pandas\core\common.py:571, in require_length_match(data, index)
567 """
568 Check the length of data matches the length of the index.
569 """
570 if len(data) != len(index):
--> 571 raise ValueError(
572 "Length of values "
573 f"({len(data)}) "
574 "does not match length of index "
575 f"({len(index)})"
576 )
ValueError: Length of values (0) does not match length of index (1)