日付形式の問題解決とイベント抽出とフィルタリング
レスポンスに含まれている日付形式が「令和6年10月26日」などの和暦表記や、「2024-10-08」などのISO表記と混在している
このような場合、統一的に処理するためのフォーマット変換を行う必要がある
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from datetime import datetime import re def convert_japanese_date(japanese_date): # 和暦の例: "令和6年10月26日" pattern = r "令和(\d+)年(\d+)月(\d+)日" match = re.match(pattern, japanese_date) if match: year = int(match.group(1)) + 2018 # 令和元年は2019年に相当 month = int(match.group(2)) day = int(match.group(3)) return f "{year}-{month:02d}-{day:02d}" # ISO形式のチェック try: datetime.strptime(japanese_date, '%Y-%m-%d' ) return japanese_date except ValueError: return None # 無効な日付形式 |
また
日付形式の変換後、無効な日付やイベント名が空のエントリをフィルタリングするように変更
eventsリストから有効なエントリのみを抽出
1 2 3 4 5 6 7 8 9 | def filter_events(events): valid_events = [] for event in events: date = convert_japanese_date(event[ 'date' ]) if date and event[ 'event' ]: valid_events.append({ 'date' : date , 'event' : event[ 'event' ]}) else : print(f "無効な日付形式: {event['date']}" ) return valid_events |
日付の変換やイベントのフィルタリングを他のスクリプトでも使うため
モジュール化する
event_utils.pyというモジュールを作成し
これに記述する
1 | touch event_utils.py |
内容は
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 | from datetime import datetime import re def convert_japanese_date(japanese_date): pattern = r "令和(\d+)年(\d+)月(\d+)日" match = re.match(pattern, japanese_date) if match: year = int(match.group(1)) + 2018 # 令和元年は2019年に相当 month = int(match.group(2)) day = int(match.group(3)) return f "{year}-{month:02d}-{day:02d}" # ISO形式のチェック try: datetime.strptime(japanese_date, '%Y-%m-%d' ) return japanese_date except ValueError: return None # 無効な日付形式 def filter_events(events): valid_events = [] for event in events: date = convert_japanese_date(event[ 'date' ]) if date and event[ 'event' ]: valid_events.append({ 'date' : date , 'event' : event[ 'event' ]}) else : print(f "無効な日付形式: {event['date']}" ) return valid_events |
これをmain4.pyで
event_utils.pyをインポートして使用する
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 | from drive_pdf_extractor import extract_texts_from_folder from ollama_module import parse_text_with_ollama from google_calendar_module import add_events_to_calendar from event_utils import convert_japanese_date, filter_events # フォルダIDを指定して処理を開始 folder_id = "" #Folder ID texts = extract_texts_from_folder(folder_id) if not texts: print( "フォルダ内に解析するテキストがありません。" ) else : for text_content in texts: raw_events = parse_text_with_ollama(text_content, model_name= 'elyza:jp8b' ) print( "抽出されたイベント:" ) for event in raw_events: print(event) # イベントのフィルタリングとフォーマット events = filter_events(raw_events) print( "有効なイベント:" ) for event in events: print(event) if events: add_events_to_calendar(events, calendar_id= 'primary' , token_file= 'token.json' , credentials_file= 'credentials.json' ) else : print( "有効なイベントがありません。" ) |
これで
eventsリストからNoneのイベントを削除するフィルタリング処理が追加
Noneを削除したイベントのみをカレンダーに追加
1 | events = [event for event in events if event[ 'event' ] is not None] |
の部分がその処理
また
eventsリストが空でないかを確認し、有効なイベントがある場合のみカレンダーに追加
もしイベントがなければ、カレンダーには何も追加されず、
「有効なイベントがありません。」というメッセージが表示される
このチェックによって、無駄な処理が減り、空のイベントがカレンダーに追加されるのを防ぐ
1 2 3 4 | if events: add_events_to_calendar(events, calendar_id= 'primary' , token_file= 'token.json' , credentials_file= 'credentials.json' ) else : print( "有効なイベントがありません。" ) |
これで実行すると
運動会のイベントが失敗している
これは
OllamaからのレスポンスがJSON形式として期待されているのに対し、
不正なJSON形式
(複数のJSONオブジェクトがコンマで区切られている形式や、余分なテキストが含まれている形式)
が返ってきていることが考えられます。
これを解決するために、
Ollamaのレスポンスを受け取る際に、
例外処理や、JSON以外の余計な部分を取り除く前処理を追加
レスポンスを前処理する関数を追加し、JSON形式に整形
JSON解析エラーの例外処理を追加して、不正な形式のデータをスキップ
llamaからのレスポンスを受け取って前処理し、JSON形式に変換するには
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import json import re def parse_ollama_response(response_text): # 正規表現でJSON配列形式のみを抽出 json_pattern = re.compile(r '\[.*?\]' , re.DOTALL) match = json_pattern.search(response_text) if not match: print( "JSON形式のレスポンスが見つかりません。" ) return [] json_data = match.group(0) # 最初のJSON形式の部分を取得 try: events = json.loads(json_data) return events except json.JSONDecodeError as e: print( "Ollamaからのレスポンスの解析に失敗しました:" , str(e)) return [] |
parse_ollama_response関数を
parse_text_with_ollama関数の後処理として使用し、Ollamaからのレスポンスを整形
これにより、Ollamaからの不正なレスポンス形式を取り除く
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 58 | from drive_pdf_extractor import extract_texts_from_folder from ollama_module import parse_text_with_ollama from google_calendar_module import add_events_to_calendar import json import re # OllamaからのレスポンスをJSON形式に変換 def parse_ollama_response(response_text): # JSON配列形式のみを抽出する正規表現 json_pattern = re.compile(r '\[.*?\]' , re.DOTALL) match = json_pattern.search(response_text) if not match: print( "JSON形式のレスポンスが見つかりません。" ) return [] json_data = match.group(0) # 最初のJSON形式の部分を取得 try: events = json.loads(json_data) return events except json.JSONDecodeError as e: print( "Ollamaからのレスポンスの解析に失敗しました:" , str(e)) return [] # Google DriveのSchoolフォルダID folder_id = "" # フォルダ内のPDFファイルからテキストを抽出 texts = extract_texts_from_folder(folder_id) # テキストが抽出できているか確認 if not texts: print( "フォルダ内に解析するテキストがありません。" ) else : for text_content in texts: # Ollamaでテキストを解析(モデル名を指定) raw_response = parse_text_with_ollama(text_content, model_name= 'elyza:jp8b' ) # Ollamaのレスポンスを前処理してJSONに変換 events = parse_ollama_response(raw_response) print( "抽出されたイベント:" ) for event in events: print(event) # Noneのイベントを除外 events = [event for event in events if event[ 'event' ]] # フィルタリング後のイベントを表示 print( "有効なイベント:" ) for event in events: print(event) # 有効なイベントがある場合のみGoogleカレンダーに追加 if events: add_events_to_calendar(events, calendar_id= 'primary' , token_file= 'token.json' , credentials_file= 'credentials.json' ) else : print( "有効なイベントがありません。" ) |
としたが
TypeError: expected string or bytes-like object というエラーは、
response_textのデータ型が文字列やバイト列ではない場合に発生
これは、response_textが予期せず辞書(dict)型や他の非文字列データ型である場合に起こることがよくある
response_textの型が文字列であることを確認し、
もし辞書型など別の型が返ってきている場合には、JSONエンコードして文字列に変換する処理を追加
レスポンスが辞書型か確認し、文字列に変換
parse_text_with_ollamaから返ってくるレスポンスが文字列であるかをチェックし、
もし辞書型など別の型であれば文字列に変換する処理を追加
型チェックを行う
parse_ollama_response関数内で
response_textが文字列であることをチェックし、文字列でない場合には変換
以下修正
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 | import json import re def parse_ollama_response(response_text): # レスポンスが辞書型なら文字列に変換 if isinstance(response_text, dict): response_text = json.dumps(response_text) # 辞書をJSON文字列に変換 elif not isinstance(response_text, str): print( "不明なデータ形式のレスポンスを受け取りました:" , type (response_text)) return [] # JSON配列形式のみを抽出する正規表現 json_pattern = re.compile(r '\[.*?\]' , re.DOTALL) match = json_pattern.search(response_text) if not match: print( "JSON形式のレスポンスが見つかりません。" ) return [] json_data = match.group(0) # 最初のJSON形式の部分を取得 try: events = json.loads(json_data) return events except json.JSONDecodeError as e: print( "Ollamaからのレスポンスの解析に失敗しました:" , str(e)) return [] |
という処理を追加
response_textが辞書型かを確認します。辞書型の場合はjson.dumps()を使って文字列に変換
次に、正規表現を使用してレスポンス内のJSON配列形式を抽出
json.loadsを使って抽出した部分をJSONとしてパースし、
もしエラーが発生した場合にはエラーメッセージを表示
逆にエラーになった
このため一度
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 | from drive_pdf_extractor import extract_texts_from_folder from ollama_module import parse_text_with_ollama from google_calendar_module import add_events_to_calendar from event_utils import convert_japanese_date, filter_events # フォルダIDを指定して処理を開始 folder_id = "" texts = extract_texts_from_folder(folder_id) if not texts: print( "フォルダ内に解析するテキストがありません。" ) else : for text_content in texts: raw_events = parse_text_with_ollama(text_content, model_name= 'elyza:jp8b' ) print( "抽出されたイベント:" ) for event in raw_events: print(event) # イベントのフィルタリングとフォーマット events = filter_events(raw_events) print( "有効なイベント:" ) for event in events: print(event) if events: add_events_to_calendar(events, calendar_id= 'primary' , token_file= 'token.json' , credentials_file= 'credentials.json' ) else : print( "有効なイベントがありません。" ) |
に戻したら
となって無事にイベントが追加された
次は
メールから読み込んで実行できるようにする