機能の統合
メールからURLを抽出する
gmail_url_extractor.py
でURLを取得し
image_downloader.py
で画像を取得
ocr_list.py
で vision api でOCR
この中で
line_notify.py
を使うことで
OCRした結果をワードリストに一致したものと画像をLINEで送信
とりあえずここまで作成したらgithubで公開
残る機能は
Yolov8のモデルを作成し画像認識させること
キーワードリストを効率的に作成するため
レシートをOCRしたものから
店名
価格
商品名
日付
を取り出しCSVファイルに保存すること
CSVファイルを元にDBを作成し
在庫管理と連携するようにして無駄な買い物を減らすこと
とりあえずまずは
ocr_list.py
で
gmail_url_extractor.py
でURLを取得し
image_downloader.py
で画像を取得
この画像に対してOCRすればOK
ただし
そのままソースを変えるとテストできなくなるので
別のファイルを作成することにする
なお今日は
おいしい牛乳
が割引らしいのでリストに加える
1 2 3 4 5 6 7 8 9 10 11 12 | { "keywords" : [ "麻婆豆腐" , "キッチンタオル" , "ほんだし" , "ぶなしめじ" , "レタス" , "キャベツ" , "おいしい牛乳" ] } |
そしてOCRするファイルを作成する
1 | touch image_ocr_notifier.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 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 59 60 61 62 63 64 | # example_usage.py from gmail_url_extractor import get_first_unread_email_url from image_downloader import download_and_merge_images from google.cloud import vision import io import json def load_settings(file_path= 'settings.json' ): with open (file_path, 'r' , encoding= 'utf_8' ) as settings_json: return json.load(settings_json) def detect_text(image_path): "" "OCRで画像からテキストを抽出" "" client = vision.ImageAnnotatorClient() with io. open (image_path, 'rb' ) as image_file: content = image_file. read () image = vision.Image(content=content) response = client.document_text_detection(image=image) full_text_annotation = response.full_text_annotation if response.error.message: raise Exception( '{}\nFor more info on error messages, check: ' response.error.message)) return full_text_annotation.text def search_words(text, keywords): "" "抽出したテキストからキーワードを検索" "" hitwords = [] for keyword in keywords: if keyword in text: hitwords.append(keyword) return hitwords def main(): # 設定を読み込む settings = load_settings() # GmailからURLを取得 url = get_first_unread_email_url( '【Shufoo!】お気に入り店舗新着チラシお知らせメール' ) # '特売情報'はメールの件名に含まれるキーワード if url: print(f "Processing URL: {url}" ) # 画像をダウンロードしてOCRを実行 output_path = download_and_merge_images( 'config.json' , url) if output_path: extracted_text = detect_text(output_path) hitwords = search_words(extracted_text, settings[ "keywords" ]) if hitwords: message = "特売リスト: " + ", " . join (hitwords) send_line_notify(message, output_path) else : print( "マッチしたキーワードはありませんでした。" ) else : print( "未読メールが見つかりませんでした。" ) if __name__ == '__main__' : main() |
実行したけど
1 | Processing URL: https: //www .shufoo.net /pntweb/shopDetail/860323/ ?cid=nmail_pc |
となるだけ
おそらくxpathが変更になっているので確認
1 2 | /html/body/div [1] /div [3] /div [1] /div/div [2] /div [3] /html/body/div [1] /div [3] /div [1] /div/div [2] /div [3] |
同じだが動作していない
image_downloader.pyの動作確認
1 2 | ログの追加: image_downloader.pyの中にログを追加して、どこで処理が失敗しているかを特定 print(f 'Checking image source: {src}' ) # ログ追加 |
となるように
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def get_images_from_container(driver, base_xpath): "" "指定されたXPathから画像URLを取得する" "" image_urls = [] try: container = WebDriverWait(driver, 10). until ( EC.presence_of_element_located((By.XPATH, base_xpath)) ) images = container.find_elements(By.TAG_NAME, 'img' ) for img in images: src = img.get_attribute( 'src' ) print(f 'Checking image source: {src}' ) # ログ追加 if 'index/img' in src: image_urls.append(src) print(f 'Found image: {src}' ) except Exception as e: print(f 'Error finding images: {e}' ) return image_urls |
というように
変更
また
CR処理のデバッグ: detect_text関数において、Cloud Vision APIのレスポンスが正常であるか確認
image_ocr_notifier.py
の中に
1 2 | # OCRの結果をデバッグ表示 full_text_annotation = response.full_text_annotation print( "Extracted Text:" , full_text_annotation.text) |
を追加するので
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def detect_text(image_path): "" "OCRで画像からテキストを抽出" "" client = vision.ImageAnnotatorClient() with io. open (image_path, 'rb' ) as image_file: content = image_file. read () image = vision.Image(content=content) response = client.document_text_detection(image=image) if response.error.message: raise Exception( '{}\nFor more info on error messages, check: ' response.error.message)) # OCRの結果をデバッグ表示 full_text_annotation = response.full_text_annotation print( "Extracted Text:" , full_text_annotation.text) return full_text_annotation.text |
しかし動作しない
1 | /html/body/div [1] /div [3] /div [1] /div/div [2] /div [2] /div [2] /div [1] |
ではなく
1 | /html/body/div [1] /div [3] /div [1] /div/div [2] /div [2] /div [2] |
の
1 | <div id = "cv_1" class= "ChirashiView" style= "position: absolute; left: 0px; top: -30px; z-index: 1; opacity: 1; cursor: url("https://www.shufoo.net/site/chirashi_viewer_js/js/../images/openhand_8_8.cur"), default; transition: opacity 200ms ease-in-out;" ><div class= "ChirashiView_tempDiv" style= "position: absolute; overflow: hidden; width: 750px; height: 603px; left: 0px; top: 0px; z-index: 100;" >< /div ><div class= "ChirashiContainer" style= "position: absolute; left: 0px; top: 0px; width: 750px; height: 603px; z-index: 0; opacity: 1;" ><div class= "inDiv" style= "position: absolute; left: 0px; top: 0px; z-index: 1;" ><div id = "-2_-2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -1004px; top: -977.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 512px;" >< /div ><div id = "-1_-2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -492px; top: -977.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 512px;" >< /div ><div id = "0_-2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 20px; top: -977.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 512px;" >< /div ><div id = "1_-2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 532px; top: -977.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 198px; height: 512px;" >< /div ><div id = "2_-2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 1044px; top: -977.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; height: 512px;" >< /div ><div id = "-2_-1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -1004px; top: -465.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 512px;" >< /div ><div id = "-1_-1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -492px; top: -465.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 512px;" >< /div ><div id = "0_-1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 20px; top: -465.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 512px;" >< /div ><div id = "1_-1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 532px; top: -465.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 198px; height: 512px;" >< /div ><div id = "2_-1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 1044px; top: -465.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; height: 512px;" >< /div ><div id = "-2_0" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -1004px; top: 46.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 510px;" >< /div ><div id = "-1_0" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -492px; top: 46.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 510px;" >< /div ><div id = "0_0" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 20px; top: 46.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://ipqcache2.shufoo.net/c/2024/08/08/25295137072090/index/img/0_100_0.jpg" style= "border: 0px; padding: 0px; margin: 0px; width: 512px; height: 510px;" >< /div ><div id = "1_0" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 532px; top: 46.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://ipqcache2.shufoo.net/c/2024/08/08/25295137072090/index/img/0_100_1.jpg" style= "border: 0px; padding: 0px; margin: 0px; width: 198px; height: 510px;" >< /div ><div id = "2_0" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 1044px; top: 46.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; height: 510px;" >< /div ><div id = "-2_1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -1004px; top: 558.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px;" >< /div ><div id = "-1_1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -492px; top: 558.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px;" >< /div ><div id = "0_1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 20px; top: 558.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px;" >< /div ><div id = "1_1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 532px; top: 558.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 198px;" >< /div ><div id = "2_1" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 1044px; top: 558.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px;" >< /div ><div id = "-2_2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -1004px; top: 1070.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px;" >< /div ><div id = "-1_2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: -492px; top: 1070.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px;" >< /div ><div id = "0_2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 20px; top: 1070.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 512px;" >< /div ><div id = "1_2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 532px; top: 1070.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px; width: 198px;" >< /div ><div id = "2_2" style= "position: absolute; opacity: 1; transition: opacity 200ms ease-out; left: 1044px; top: 1070.5px; width: 512px; height: 512px;" ><img draggable= "false" src= "https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png" style= "border: 0px; padding: 0px; margin: 0px;" >< /div >< /div ><div class= "linkDiv" style= "position: absolute; left: 0px; top: 0px; z-index: 2;" >< /div >< /div >< /div > |
のソース部分が正解らしい
config.jsonの中身を
1 | "base_xpath_images" : "/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]/div[2]" |
とxpathを変更することで
とりあえず画像の取得はできたが
1 | File "/Users/snowpool/aw10s/store_adversting_list/image_ocr_notifier.py" , line 67, in <module> main() File "/Users/snowpool/aw10s/store_adversting_list/image_ocr_notifier.py" , line 60, in main send_line_notify(message, output_path) NameError: name 'send_line_notify' is not defined |
となる
1 2 3 | # # OCRの結果をデバッグ表示 # full_text_annotation = response.full_text_annotation # print("Extracted Text:", full_text_annotation.text) |
というようにコメントアウト
1 | from line_notify import send_line_notify |
が抜けていたので
これを追記
なぜかGPTでソースを生成すると
モジュールのインポートが削除されることがよくある
無事に処理ができたがログに
1 | WARNING: All log messages before absl::InitializeLog() is called are written to STDERR I0000 00:00:1723322571.066745 14856910 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache Sent images /combined_image_20240811_054251 .jpg: 200 { "status" :200, "message" : "ok" } |
と出る
GPTによれば
1 | WARNING: All log messages before absl::InitializeLog() is called are written to STDERR |
このメッセージは、abseil (Absl) ライブラリが初期化される前にログメッセージが標準エラー出力(STDERR)に書き込まれることを示しています。これは、Google CloudのPythonクライアントライブラリやgRPCなどのバックエンドでAbseilライブラリが使用されている場合によく見られます。この警告は通常、以下の点で心配する必要はありません:
初期化前ログの出力: Abseilが初期化される前に生成されるログメッセージが一時的にSTDERRに出力されるという技術的な情報を示しています。
影響なし: このメッセージは一般的にはアプリケーションの動作に影響を与えないため、無視しても問題ありません
1 | I0000 00:00:1723322571.066745 14856910 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache |
このメッセージは、gRPCライブラリの内部で実験的な機能が有効になっていることを示す情報ログです。これも通常、開発者が特に指定しない限り、デフォルトで有効になっている設定です。これらの実験的な機能は、以下のような効果を持ちます:
* 実験的機能の有効化: gRPCの内部機能が特定の実験的な設定で動作していることを示します。
* 通常の動作: 通常、gRPCの標準的な利用に影響はなく、情報提供のためのログです。
これらのログメッセージは、技術的な情報や警告を提供するためのものです。特にabsl::InitializeLog()やgRPCに関する警告は、ログの設定やライブラリの内部動作に関するもので、アプリケーションの正常動作には通常影響を与えません
とのこと