Gmailで指定の未読メールの取得

Gmailで指定の未読メールの取得

ラベルがshopで
お気に入り店舗新着チラシお知らせメール
の件名
の未読メールを取得する

本文のURLをクリックし
杏林堂なら
/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]
もしくは
/html/body/div[1]/div[3]/div[1]/div/div[2]
で画像をダウンロードし
複数画像の時には1つに統合する

とりあえずここまでできるようにする

まずは件名で未読のものを取得するようにした

クエリに is:unread を追加しました。この変更により、未読のメールのみが検索対象になります。

import os.path
import base64
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)

    # 未読メールを検索
    query = 'is:unread subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No unread messages found.')
    else:
        print(f'Found {len(messages)} unread messages:')
        for msg in messages:
            msg_id = msg['id']
            msg = service.users().messages().get(userId='me', id=msg_id).execute()
            msg_snippet = msg['snippet']
            print(f'Message snippet: {msg_snippet}')

if __name__ == '__main__':
    main()

変更点は

query = 'is:unread subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'

の部分

さらに最新の1件のみ取得するようにコード変更

変更点は
maxResults パラメータの追加:
service.users().messages().list メソッドに maxResults=1 を追加
これにより、検索結果として最新の1件のみが返される

これで実行すると

Found 1 unread message:
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、1件の新着チラシが掲載開始されました。 ・ピアゴ袋井店https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc ※Shufoo!PCサイトまたは、シュフーチラシアプリ(スマートフォン・タブレット端末用) からログインしてお店の
s

となる

次に本文からURLのみ抽出する
ただし複数存在するため最初のURLのみ抽出する

本文からURLを抽出するには、メールの本文を取得し、正規表現を使ってURLを抽出
複数のURLが含まれている場合は、最初のURLのみを抽出

import os.path
import base64
import re
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def extract_first_url(text):
    """テキストから最初のURLを抽出します。"""
    url_pattern = r'https?://[^\s]+'
    urls = re.findall(url_pattern, text)
    return urls[0] if urls else None

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)

    # 最新の未読メール1件を検索
    query = 'is:unread subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query, maxResults=1).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No unread messages found.')
    else:
        print(f'Found {len(messages)} unread message:')
        msg_id = messages[0]['id']
        msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
        msg_payload = msg['payload']
        msg_parts = msg_payload.get('parts', [])
        
        body_data = ""
        for part in msg_parts:
            if part['mimeType'] == 'text/plain':  # or 'text/html' depending on the format you need
                body_data = part['body']['data']
                break
        
        # Base64デコード
        body_text = base64.urlsafe_b64decode(body_data).decode('utf-8')

        # URLを抽出
        first_url = extract_first_url(body_text)
        if first_url:
            print(f'First URL found: {first_url}')
        else:
            print('No URL found in the message.')

if __name__ == '__main__':
    main()

ここで問題発生

Found 1 unread message:
No URL found in the message.

となる

ChatGPTで調べると

変更点
* extract_first_url関数: 正規表現を使ってテキストから最初のURLを抽出する関数を追加しました。
* メール本文の取得: メールのpayloadから本文のデータ部分を取得し、Base64でデコードしています。
* 本文の解析: デコードした本文から最初のURLを抽出しています。
注意点
* MIMEタイプの確認: メールの本文がtext/plainではなくtext/htmlの場合は、if part[‘mimeType’] == ‘text/plain’: を if part[‘mimeType’] == ‘text/html’: に変更する必要があります。
* 複数部品のメール: 一部のメールは複数のpartsに分かれている場合があります。このコードは最初のtext/plainパートからURLを抽出しますが、複雑なメール構造の場合は調整が必要です。

今回の場合

メール本文からURLを抽出するために、正確なデータ部分をデコードすることが必要です。
Gmail APIで取得するメールの本文は複数のパートに分かれていることがあり、
正しいパートからデコードする必要があります。
また、本文の形式がtext/plainかtext/htmlかを確認し、適切にデコードします

get_message_body 関数:
* メールの本文をすべてのパートから取得し、text/plainまたはtext/htmlの内容をデコードします。
* part[‘body’].get(‘data’) を使ってBase64エンコードされた本文データを取得し、デコードして連結します。
本文の抽出:
* get_message_body関数で取得した本文全体からURLを抽出します。
* 正規表現を使用して、最初に見つかったURLを返します。

メール本文のパートを正しく取得してURLを抽出するようにします。メールの本文パートを順番に確認して、デコード

import os.path
import base64
import re
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def extract_first_url(text):
    """テキストから最初のURLを抽出します。"""
    url_pattern = r'https?://[^\s]+'
    urls = re.findall(url_pattern, text)
    return urls[0] if urls else None

def get_message_body(parts):
    """メールの本文を取得します。"""
    body = ""
    for part in parts:
        if part['mimeType'] == 'text/plain' or part['mimeType'] == 'text/html':
            body_data = part['body'].get('data')
            if body_data:
                body += base64.urlsafe_b64decode(body_data).decode('utf-8')
    return body

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)

    # 最新の未読メール1件を検索
    query = 'is:unread subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query, maxResults=1).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No unread messages found.')
    else:
        print(f'Found {len(messages)} unread message:')
        msg_id = messages[0]['id']
        msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
        msg_payload = msg['payload']
        
        # メッセージ本文を取得
        msg_body = get_message_body(msg_payload.get('parts', []))

        # URLを抽出
        first_url = extract_first_url(msg_body)
        if first_url:
            print(f'First URL found: {first_url}')
        else:
            print('No URL found in the message.')

if __name__ == '__main__':
    main()

しかし結果は

Found 1 unread message:
No URL found in the message.

再度ChatGPTで調べる

メールの本文からURLが正しく抽出されない場合、
メールの構造が予想とは異なる可能性があります。

Gmail APIで取得したメールのペイロードを詳しく確認し、
メールの本文がどのように構造化されているかを理解する必要があります。

メールの内容は、partsとして分割されているか、
bodyに直接含まれている場合があります。

どちらの場合でも、メールの本文をデコードしてURLを抽出することが必要です。
以下は、メールペイロードを詳細に確認してURLを抽出するための修正コード

import os.path
import base64
import re
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def extract_first_url(text):
    """テキストから最初のURLを抽出します。"""
    url_pattern = r'https?://[^\s]+'
    urls = re.findall(url_pattern, text)
    return urls[0] if urls else None

def get_message_body(payload):
    """メールの本文を取得します。"""
    body = ""
    
    if 'data' in payload['body']:
        # 直接bodyに含まれる場合
        body = base64.urlsafe_b64decode(payload['body']['data']).decode('utf-8')
    elif 'parts' in payload:
        # partsに分割されている場合
        for part in payload['parts']:
            if part['mimeType'] == 'text/plain' or part['mimeType'] == 'text/html':
                body_data = part['body'].get('data')
                if body_data:
                    body += base64.urlsafe_b64decode(body_data).decode('utf-8')
    
    return body

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)

    # 最新の未読メール1件を検索
    query = 'is:unread subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query, maxResults=1).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No unread messages found.')
    else:
        print(f'Found {len(messages)} unread message:')
        msg_id = messages[0]['id']
        msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
        msg_payload = msg['payload']
        
        # メッセージ本文を取得
        msg_body = get_message_body(msg_payload)

        # URLを抽出
        first_url = extract_first_url(msg_body)
        if first_url:
            print(f'First URL found: {first_url}')
        else:
            print('No URL found in the message.')

if __name__ == '__main__':
    main()

修正点
1. get_message_body関数の改良:
* payload[‘body’]に直接データが含まれる場合も考慮しました。この場合、partsはありません。
* partsがある場合でも、本文がtext/plainかtext/htmlのいずれかである部分を探します。
2. URL抽出の再確認:
* extract_first_url関数で、取得した本文全体から最初のURLを抽出します。

これで

Found 1 unread message:
First URL found: https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc

というようにURLを抽出できた

これを
Seleniumで開き画像をダウンロードするモジュールの引数に当てるようにする

Xpathはcoopだけ違うようだが
実際には同じだった
ダウンロードファイルはタイムスタンプをつければかぶることはないし
そのままcloud vision api で処理してLINEで送信すれば問題ない

このためクリック処理は不要でそのまま画像をダウンロードするようにする

import os
import time
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from datetime import datetime

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    widths, heights = zip(*(img.size for img in images))

    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    
    # 画像を取得してダウンロードする
    base_xpath_images = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[3]'

    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            # 現在の日付を取得してフォーマット
            current_date = datetime.now().strftime('%Y%m%d')
            # カレントディレクトリにimagesフォルダを作成
            output_dir = 'images'
            os.makedirs(output_dir, exist_ok=True)  # ディレクトリが存在しない場合は作成
            output_path = os.path.join(output_dir, f'combined_image_{current_date}.jpg')
            merge_images(images, output_path)

if __name__ == '__main__':
    main()

これで無事に画像のダウンロードができた

ただし頻繁にxpathが変わるようなので
設定ファイルを作成し
そこで設定したxpathを

    base_xpath_images = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[3]'

で指定しているxpath になるようにコード変更

設定ファイルは
config.json
だが
既にLINE APIの設定で使っているので
これに

"base_xpath_images": "/html/body/div[1]/div[3]/div[1]/div/div[2]/div[3]"

の項目を追加する

import os
import time
import json
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from datetime import datetime

def load_config(config_file):
    """設定ファイルからコンフィグを読み込む"""
    with open(config_file, 'r') as file:
        config = json.load(file)
    return config

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    widths, heights = zip(*(img.size for img in images))

    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def main():
    # 設定ファイルを読み込む
    config = load_config('config.json')
    
    url = 'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    
    # 設定ファイルからXPathを取得して画像を取得
    base_xpath_images = config['base_xpath_images']
    
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            # 現在の日付を取得してフォーマット
            current_date = datetime.now().strftime('%Y%m%d')
            # カレントディレクトリにimagesフォルダを作成
            output_dir = 'images'
            os.makedirs(output_dir, exist_ok=True)  # ディレクトリが存在しない場合は作成
            output_path = os.path.join(output_dir, f'combined_image_{current_date}.jpg')
            merge_images(images, output_path)

if __name__ == '__main__':
    main()

またタイムスタンプをファイル名に追加すれば上書き防止になるので

タイムスタンプのフォーマット: datetime.now().strftime(‘%Y%m%d_%H%M%S’)で、
現在の日付と時刻をフォーマットし、年/月/日 時:分:秒の順にします。

これにより、ファイル名が一意になります。
ファイル名への追加: output_pathの生成時にタイムスタンプを含めることで、
同日に何度実行してもファイルが上書きされないようにしました。

フォルダの作成: 出力先のディレクトリが存在しない場合、自動的に作成されます。

これらを組み込み

import os
import time
import json
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from datetime import datetime

def load_config(config_file):
    """設定ファイルからコンフィグを読み込む"""
    with open(config_file, 'r') as file:
        config = json.load(file)
    return config

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    widths, heights = zip(*(img.size for img in images))

    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def main():
    # 設定ファイルを読み込む
    config = load_config('config.json')
    
    url = 'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    
    # 設定ファイルからXPathを取得して画像を取得
    base_xpath_images = config['base_xpath_images']
    
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            # 現在の日付と時刻を取得してフォーマット
            current_timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            # カレントディレクトリにimagesフォルダを作成
            output_dir = 'images'
            os.makedirs(output_dir, exist_ok=True)  # ディレクトリが存在しない場合は作成
            output_path = os.path.join(output_dir, f'combined_image_{current_timestamp}.jpg')
            merge_images(images, output_path)

if __name__ == '__main__':
    main()

これで実行すると

Found image: https://ipqcache2.shufoo.net/c/2024/08/09/96510937073938/index/img/0_100_0.jpg
Found image: https://ipqcache2.shufoo.net/c/2024/08/09/96510937073938/index/img/0_100_1.jpg
Downloaded image_0.jpg
Downloaded image_1.jpg
Saved combined image as images/combined_image_20240809_233141.jpg

となって無事にダウンロードが実行される

次にこれをモジュールにする

# image_downloader.py

import os
import json
import time
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from datetime import datetime

def load_config(config_file):
    """設定ファイルからコンフィグを読み込む"""
    with open(config_file, 'r') as file:
        config = json.load(file)
    return config

def open_link_in_safari(url):
    """指定されたURLをSafariで開く"""
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

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')
            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

def download_images(image_urls):
    """画像URLから画像をダウンロードする"""
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    """複数の画像を結合して保存する"""
    if not images:
        print("No images to merge.")
        return

    widths, heights = zip(*(img.size for img in images))
    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def download_and_merge_images(config_file, url):
    """画像をダウンロードして結合するメイン関数"""
    config = load_config(config_file)
    driver = open_link_in_safari(url)

    base_xpath_images = config['base_xpath_images']
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            current_timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            output_dir = 'images'
            os.makedirs(output_dir, exist_ok=True)
            output_path = os.path.join(output_dir, f'combined_image_{current_timestamp}.jpg')
            merge_images(images, output_path)

として
image_downloader.py
を作成

ただし、これだと最新のファイルを探すなどの処理が必要になるため
作成したファイル名を返り値として渡すようにすれば
そのファイルに対して
Cloud vision api を実行できるはず

# image_downloader.py

import os
import json
import time
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from datetime import datetime

def load_config(config_file):
    """設定ファイルからコンフィグを読み込む"""
    with open(config_file, 'r') as file:
        config = json.load(file)
    return config

def open_link_in_safari(url):
    """指定されたURLをSafariで開く"""
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

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')
            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

def download_images(image_urls):
    """画像URLから画像をダウンロードする"""
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    """複数の画像を結合して保存する"""
    if not images:
        print("No images to merge.")
        return

    widths, heights = zip(*(img.size for img in images))
    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def download_and_merge_images(config_file, url):
    """画像をダウンロードして結合するメイン関数"""
    config = load_config(config_file)
    driver = open_link_in_safari(url)

    base_xpath_images = config['base_xpath_images']
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            current_timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            output_dir = 'images'
            os.makedirs(output_dir, exist_ok=True)
            output_path = os.path.join(output_dir, f'combined_image_{current_timestamp}.jpg')
            merge_images(images, output_path)
            return output_path  # 生成されたファイル名を返す
    return None  # 画像がなかった場合はNoneを返す

次にgmailの未読メールからURLを取得する部分もモジュール化する

touch gmail_url_extractor.py

でファイルを作成

import os.path
import base64
import re
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def authenticate_gmail():
    """Gmail APIに認証し、サービスを構築します。"""
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    service = build('gmail', 'v1', credentials=creds)
    return service

def extract_first_url(text):
    """テキストから最初のURLを抽出します。"""
    url_pattern = r'https?://[^\s]+'
    urls = re.findall(url_pattern, text)
    return urls[0] if urls else None

def get_message_body(payload):
    """メールの本文を取得します。"""
    body = ""
    
    if 'data' in payload['body']:
        body = base64.urlsafe_b64decode(payload['body']['data']).decode('utf-8')
    elif 'parts' in payload:
        for part in payload['parts']:
            if part['mimeType'] == 'text/plain' or part['mimeType'] == 'text/html':
                body_data = part['body'].get('data')
                if body_data:
                    body += base64.urlsafe_b64decode(body_data).decode('utf-8')
    
    return body

def get_first_unread_email_url(subject_query):
    """指定された件名を持つ未読メールから最初のURLを取得します。"""
    service = authenticate_gmail()

    query = f'is:unread subject:"{subject_query}"'
    results = service.users().messages().list(userId='me', q=query, maxResults=1).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No unread messages found.')
        return None
    
    msg_id = messages[0]['id']
    msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
    msg_payload = msg['payload']
    
    msg_body = get_message_body(msg_payload)
    first_url = extract_first_url(msg_body)
    
    return first_url

として保存

使う時には

from gmail_url_extractor import get_first_unread_email_url

def main():
    subject_query = "【Shufoo!】お気に入り店舗新着チラシお知らせメール"
    url = get_first_unread_email_url(subject_query)
    
    if url:
        print(f'First URL found: {url}')
    else:
        print('No URL found in the message.')

if __name__ == '__main__':
    main()

というように使う

ファイルのダウンロードと生成モジュールは

# example_usage.py

from image_downloader import download_and_merge_images

def main():
    config_file = 'config.json'
    url = 'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc'
    output_path = download_and_merge_images(config_file, url)
    
    if output_path:
        print(f"Generated file: {output_path}")
        # Cloud Vision APIを実行するコードをここに追加
        # example: run_cloud_vision_api(output_path)
    else:
        print("No image file was created.")

if __name__ == '__main__':
    main()

として使う

とりあえずほとんど準備できたので
あとは
ocr_list.py

gmail_url_extractor.py
でURLを取得し
image_downloader.py
で画像を取得

この画像に対してOCRすればOKとなる

Gmailで指定のメールの中から件名を指定し取得

Gmailで指定のメールの中から件名を指定し取得


が指定メール
これのうち
【Shufoo!】お気に入り店舗新着チラシお知らせメール
の件名のもののみ取得するようにする

この中の本文の中のURLへアクセスしチラシを取得する


https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc

杏林堂西田町

https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc
ユーコープ/袋井田町店

https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc
ぴあご袋井

とりあえず件名が
【Shufoo!】お気に入り店舗新着チラシお知らせメール
のものだけを取得する

vim get_mail_subject.py

中身は

import os.path
import base64
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)

    # メールを検索
    query = 'subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No messages found.')
    else:
        print(f'Found {len(messages)} messages:')
        for msg in messages:
            msg_id = msg['id']
            msg = service.users().messages().get(userId='me', id=msg_id).execute()
            msg_snippet = msg['snippet']
            print(f'Message snippet: {msg_snippet}')

if __name__ == '__main__':
    main()

そして認証ファイルをコピーする

cp ../mail_auto/credentials.json .
 cp ../mail_auto/token.json .  

実行すると

Found 24 messages:
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、1件の新着チラシが掲載開始されました。 ・ピアゴ袋井店https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc ※Shufoo!PCサイトまたは、シュフーチラシアプリ(スマートフォン・タブレット端末用) からログインしてお店の
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、4件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・ユーコープ/袋井田町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、4件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、1件の新着チラシが掲載開始されました。 ・ピアゴ袋井店https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc ※Shufoo!PCサイトまたは、シュフーチラシアプリ(スマートフォン・タブレット端末用) からログインしてお店の
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、4件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・ユーコープ/袋井田町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、1件の新着チラシが掲載開始されました。 ・ピアゴ袋井店https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc ※Shufoo!PCサイトまたは、シュフーチラシアプリ(スマートフォン・タブレット端末用) からログインしてお店の
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、1件の新着チラシが掲載開始されました。 ・ピアゴ袋井店https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc ※Shufoo!PCサイトまたは、シュフーチラシアプリ(スマートフォン・タブレット端末用) からログインしてお店の
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、4件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・ユーコープ/袋井田町店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、3件の新着チラシが掲載開始されました。 ・杏林堂薬局/袋井旭町店https://www.shufoo.net/pntweb/shopDetail/860335/?cid=nmail_pc ・杏林堂薬局/袋井西田店https://www.shufoo.net/pntweb/
Message snippet: こちらのメールは「Shufoo!」でお気に入り登録した店舗の新着チラシ掲載開始をお知らせするメールです。 以下、1件の新着チラシが掲載開始されました。 ・ピアゴ袋井店https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc ※Shufoo!PCサイトまたは、シュフーチラシアプリ(スマートフォン・タブレット端末用) からログインしてお店の

というように
Ctrl + c で止めるまで続く

次に
取得したメールの本文の中に
https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc
もしくは
https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc
または
https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc
を含んでいる場合 safari の selenium でリンクページを開くようにコード変更

vim mail_url,py

import os.path
import base64
import re
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# 検索するURLリスト
URL_LIST = [
    'https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc',
    'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc',
    'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
]

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)

    # メールを検索
    query = 'subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No messages found.')
    else:
        print(f'Found {len(messages)} messages:')
        for msg in messages:
            msg_id = msg['id']
            msg = service.users().messages().get(userId='me', id=msg_id).execute()
            msg_payload = msg.get('payload', {})
            msg_parts = msg_payload.get('parts', [])
            msg_body = ''

            for part in msg_parts:
                if part['mimeType'] == 'text/plain':
                    msg_body = base64.urlsafe_b64decode(part['body']['data']).decode('utf-8')
                    break

            # URLリスト内のURLを含むか確認
            for url in URL_LIST:
                if url in msg_body:
                    print(f'Opening URL: {url}')
                    open_link_in_safari(url)
                    break

if __name__ == '__main__':
    main()

で実行

しかし取得できないので
メールの最新の1件を取得し その中に指定 のURLがあれば seleniumで開くようにする
が何も表示されないのでログを出力するようにコード変更

import os.path
import base64
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from selenium import webdriver
from selenium.webdriver.safari.service import Service as SafariService

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# 検索するURLリスト
URL_LIST = [
    'https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc',
    'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc',
    'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
]

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)

def get_email_body(parts):
    """メールパーツを再帰的に探索して本文を取得"""
    for part in parts:
        if part['mimeType'] == 'text/plain' or part['mimeType'] == 'text/html':
            try:
                body = base64.urlsafe_b64decode(part['body']['data']).decode('utf-8')
                return body
            except KeyError:
                continue
            except Exception as e:
                print(f'Error decoding part: {e}')
                continue
        elif 'parts' in part:
            body = get_email_body(part['parts'])
            if body:
                return body
    return None

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
        print("Loaded credentials from token file.")
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
            print("Credentials refreshed.")
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
            print("New credentials obtained.")
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())
            print("Credentials saved to token file.")

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)
    print("Gmail API client built.")

    # メールを検索
    query = 'subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query, maxResults=1).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No messages found.')
    else:
        print(f'Found {len(messages)} message(s).')
        msg_id = messages[0]['id']
        msg = service.users().messages().get(userId='me', id=msg_id).execute()
        print(f'Fetched message with ID: {msg_id}')
        msg_payload = msg.get('payload', {})
        msg_body = get_email_body(msg_payload.get('parts', []))

        if not msg_body:
            print(f'No body found for message ID: {msg_id}')
            return

        print(f'Message ID: {msg_id}')
        print(f'Message Body: {msg_body[:200]}...')  # メール本文の一部を表示

        # URLリスト内のURLを含むか確認
        for url in URL_LIST:
            if url in msg_body:
                print(f'Opening URL: {url}')
                open_link_in_safari(url)
                break

if __name__ == '__main__':
    main()

結果

Gmail API client built.
Found 1 message(s).
Fetched message with ID: 
No body found for message ID: 

となった

とりあえずラベルを shopにして
Shufooメールを自動振り分けする

とりあえずこのメールで最新のものを取得するようにするが
その前にブラウザで開くようにした

メールの本文が取得できない問題を解決するために、メールのペイロード構造をより詳細に確認し、可能なすべてのパートを再帰的にチェックして本文を取得する方法を強化します。また、HTML形式の本文も考慮しコード変更

import os.path
import base64
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from selenium import webdriver
from selenium.webdriver.safari.service import Service as SafariService

# 認証情報ファイルのパス
CREDENTIALS_FILE = 'path/to/credentials.json'
TOKEN_FILE = 'token.json'

# Gmail APIのスコープ
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# 検索するURLリスト
URL_LIST = [
    'https://www.shufoo.net/pntweb/shopDetail/15782/?cid=nmail_pc',
    'https://www.shufoo.net/pntweb/shopDetail/197728/?cid=nmail_pc',
    'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
]

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)

def get_email_body(parts):
    """メールパーツを再帰的に探索して本文を取得"""
    for part in parts:
        if part['mimeType'] == 'text/plain' or part['mimeType'] == 'text/html':
            try:
                body_data = part['body'].get('data')
                if body_data:
                    body = base64.urlsafe_b64decode(body_data).decode('utf-8')
                    return body
            except Exception as e:
                print(f'Error decoding part: {e}')
        elif 'parts' in part:
            body = get_email_body(part['parts'])
            if body:
                return body
    return None

def main():
    # トークンファイルが存在する場合は読み込む
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
        print("Loaded credentials from token file.")
    
    # 認証が有効でない場合は新しく認証を行う
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
            print("Credentials refreshed.")
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
            print("New credentials obtained.")
        # トークンを保存する
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())
            print("Credentials saved to token file.")

    # Gmail APIクライアントを構築
    service = build('gmail', 'v1', credentials=creds)
    print("Gmail API client built.")

    # メールを検索
    query = 'subject:"【Shufoo!】お気に入り店舗新着チラシお知らせメール"'
    results = service.users().messages().list(userId='me', q=query, maxResults=1).execute()
    messages = results.get('messages', [])

    if not messages:
        print('No messages found.')
    else:
        print(f'Found {len(messages)} message(s).')
        msg_id = messages[0]['id']
        msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
        print(f'Fetched message with ID: {msg_id}')
        msg_payload = msg.get('payload', {})
        msg_body = get_email_body([msg_payload])

        if not msg_body:
            print(f'No body found for message ID: {msg_id}')
            return

        print(f'Message ID: {msg_id}')
        print(f'Message Body: {msg_body[:200]}...')  # メール本文の一部を表示

        # URLリスト内のURLを含むか確認
        for url in URL_LIST:
            if url in msg_body:
                print(f'Opening URL: {url}')
                open_link_in_safari(url)
                break

if __name__ == '__main__':
    main()

これでURLを開くことができた

改良点
1. メールペイロードの完全な再帰的探索: メールペイロード全体を再帰的に探索し、本文データを見つけ出す。
2. デコードエラーハンドリング: デコードエラーが発生した場合にエラーメッセージを出力して継続する。
3. デバッグ情報の追加: 追加のデバッグ出力により、メールの取得プロセスの各ステップが明確になる。

次に
7/30 日替
というような日付のリンクをクリックするようにする

import datetime
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver):
    # 今日の日付を取得
    today_str = datetime.datetime.now().strftime("%m/%d")
    # 日付フォーマットの調整
    today_str = today_str.lstrip("0").replace("/", "月") + "日替"

    try:
        # 日付要素を探してクリック
        element = driver.find_element(By.XPATH, f"//*[contains(text(), '{today_str}')]")
        element.click()
        print(f'Clicked on element with text: {today_str}')
        time.sleep(3)  # クリックした後に3秒間待機
    except Exception as e:
        print(f'Error clicking on element: {e}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    click_date_element(driver)
    driver.quit()

if __name__ == '__main__':
    main()

としたが

Error clicking on element: Message: ; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception

となってしまう

このエラーは、指定された要素がページ上に見つからなかったことを示しています。この問題を解決するためには、ページが完全に読み込まれるまで待機する必要があります。また、指定されたXPathが正しいことを確認する必要があります
とのこと

追加の変更点
1. WebDriverWaitを使用して要素が見つかるまで待機:
pythonコードをコピーする

WebDriverWait(driver, 10).until(
2. EC.presence_of_element_located((By.XPATH, f”//*[contains(text(), ‘{today_str}’)]”))
3. )
4. 


5. デバッグ用に要素のリストを表示: 要素が見つからない場合に、ページ上の要素をリストして表示します。
pythonコードをコピーする

elements = driver.find_elements(By.XPATH, “//*”)
6. for elem in elements:
7. print(elem.text)


ということで

import datetime
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver):
    # 今日の日付を取得
    today_str = datetime.datetime.now().strftime("%m/%d")
    # 日付フォーマットの調整
    today_str = today_str.lstrip("0").replace("/", "月") + "日替"

    try:
        # 日付要素を探してクリック
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, f"//*[contains(text(), '{today_str}')]"))
        )
        element = driver.find_element(By.XPATH, f"//*[contains(text(), '{today_str}')]")
        element.click()
        print(f'Clicked on element with text: {today_str}')
        time.sleep(3)  # クリックした後に3秒間待機
    except Exception as e:
        print(f'Error clicking on element: {e}')
        # デバッグ用にページ上の要素をリストする
        elements = driver.find_elements(By.XPATH, "//*")
        for elem in elements:
            print(elem.text)

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    click_date_element(driver)
    driver.quit()

if __name__ == '__main__':
    main()

これで取得した結果を

python click_url.py > shop.txt

でみたけど量が多すぎるので
Chatgptでエラーになる

一度 developer tools でサイトの構成を見る

xpathだと

/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul

の中にリンクがある

/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul/li[2]/a

がそのリンク
しかし表示されない

import datetime
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver, xpath):
    try:
        # 日付要素を探してクリック
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, xpath))
        )
        container = driver.find_element(By.XPATH, xpath)
        # コンテナ内のすべてのリンクを取得
        links = container.find_elements(By.TAG_NAME, 'a')
        today_str = datetime.datetime.now().strftime("%m/%d").lstrip("0").replace("/", "月") + "日替"

        for link in links:
            if today_str in link.text:
                link.click()
                print(f'Clicked on link with text: {link.text}')
                time.sleep(3)  # クリックした後に3秒間待機
                return

        print(f'No link found with text: {today_str}')
    except Exception as e:
        print(f'Error clicking on element: {e}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # ここで指定されたXPathを使用
    xpath = '/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul'
    click_date_element(driver, xpath)
    driver.quit()

if __name__ == '__main__':
    main()

直接りんくのHTMLを調べる

<a href="//www.shufoo.net/pntweb/shopDetail/860323/86383836863914/" class="sc_custom_link" rel="sd_shop_chirashi_list" title="7/30 日替">
                          <span class="shop_chirashi_list_thumb"><img src="//ipqcache2.shufoo.net/c/2024/07/26/c/3927665654283/index/img/thumb/thumb_m.jpg" alt=""></span>
                          <span class="shop_chirashi_list_title">7/30 日替</span>
                        </a>

日付を指定せず
日替
と書かれたリンクをクリックするようにした

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver, base_xpath):
    try:
        # コンテナ内の日付要素を探してクリック
        container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, base_xpath))
        )
        links = container.find_elements(By.XPATH, ".//a[contains(@title, '日替')]")

        for link in links:
            if '日替' in link.get_attribute('title'):
                link.click()
                print(f'Clicked on link with title: {link.get_attribute("title")}')
                time.sleep(3)  # クリックした後に3秒間待機
                return

        print('No link found with title containing: 日替')
    except Exception as e:
        print(f'Error clicking on element: {e}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # ここで指定されたXPathを使用
    base_xpath = '/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul'
    click_date_element(driver, base_xpath)
    driver.quit()

if __name__ == '__main__':
    main()

これでクリックはできたので
次に画像を取得

Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_turn_over.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_turn_over.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_basic.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_basic.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_basic.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_basic.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_basic.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/button_cover_basic.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://ipqcache2.shufoo.net/c/2024/07/24/29355636822862/index/img/0_100_0.jpg
Found image: https://ipqcache2.shufoo.net/c/2024/07/24/29355636822862/index/img/0_100_1.jpg
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png
Found image: https://www.shufoo.net/site/chirashi_viewer_js/images/transparent.png

となるので

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            if 'index/img' in src:
                print(f'Found image: {src}')
    except Exception as e:
        print(f'Error finding images: {e}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # ここで指定されたXPathを使用
    base_xpath = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]'
    get_images_from_container(driver, base_xpath)
    driver.quit()

if __name__ == '__main__':
    main()

とする

取得された画像の中には多くの透明画像やボタン画像が含まれているようです。特定の条件に基づいて必要な画像をフィルタリングする必要があります。以下のように、特定の条件を追加して必要な画像のみを取得するようにコードを修正

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            if 'index/img' in src:
                print(f'Found image: {src}')
    except Exception as e:
        print(f'Error finding images: {e}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # ここで指定されたXPathを使用
    base_xpath = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]'
    get_images_from_container(driver, base_xpath)
    driver.quit()

if __name__ == '__main__':
    main()

コードの説明
1. リンクを開く:
pythonコードをコピーする

url = ‘https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc’
2. driver = open_link_in_safari(url)
3. 


4. XPathに基づいてコンテナを探し、画像要素を取得する:
pythonコードをコピーする

base_xpath = ‘/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]’
5. get_images_from_container(driver, base_xpath)
6. 


7. 画像URLを取得してフィルタリング:
pythonコードをコピーする

for img in images:
8. src = img.get_attribute(‘src’)
9. # 特定の条件に基づいて画像をフィルタリング
10. if ‘index/img’ in src:
11. print(f’Found image: {src}’)
12. 


このスクリプトは、指定されたXPath内の画像要素を取得し、その画像URLに特定の文字列(index/img)が含まれている場合にのみ出力します。これにより、不要な画像を除外し、必要な画像のみを取得することができます。

結果として

Found image: https://ipqcache2.shufoo.net/c/2024/07/24/29355636822862/index/img/0_100_0.jpg Found image: https://ipqcache2.shufoo.net/c/2024/07/24/29355636822862/index/img/0_100_1.jpg

となった

パスの取得はできたので
指定されたURLを開き、指定されたXPathに基づいて画像を取得し、それらの画像をリストとして返します。さらに、必要に応じて画像をダウンロードする

import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            with open(f'image_{i}.jpg', 'wb') as file:
                file.write(response.content)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # ここで指定されたXPathを使用
    base_xpath = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]'
    image_urls = get_images_from_container(driver, base_xpath)
    driver.quit()
    
    # 画像をダウンロード
    if image_urls:
        download_images(image_urls)

if __name__ == '__main__':
    main()

コードの説明
1. リンクを開く:
pythonコードをコピーする

url = ‘https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc’
2. driver = open_link_in_safari(url)
3. 


4. XPathに基づいてコンテナを探し、画像要素を取得する:
pythonコードをコピーする

base_xpath = ‘/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]’
5. image_urls = get_images_from_container(driver, base_xpath)
6. 


7. 画像URLを取得してリストに追加:
pythonコードをコピーする

for img in images:
8. src = img.get_attribute(‘src’)
9. # 特定の条件に基づいて画像をフィルタリング
10. if ‘index/img’ in src:
11. image_urls.append(src)
12. print(f’Found image: {src}’)

13. 画像をダウンロード:
pythonコードをコピーする

def download_images(image_urls):
14. for i, url in enumerate(image_urls):
15. response = requests.get(url)
16. if response.status_code == 200:
17. with open(f’image_{i}.jpg’, ‘wb’) as file:
18. file.write(response.content)
19. print(f’Downloaded image_{i}.jpg’)
20. else:
21. print(f’Failed to download {url}’)

このスクリプトは、指定されたXPath内の画像要素を取得し、それらの画像URLをリストとして返します。その後、リスト内の画像URLを使用して画像をダウンロードします。ダウンロードされた画像はimage_0.jpg, image_1.jpgのように保存されます。

画像は保存できたので

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver, base_xpath):
    try:
        # コンテナ内の日付要素を探してクリック
        container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, base_xpath))
        )
        links = container.find_elements(By.XPATH, ".//a[contains(@title, '日替')]")

        for link in links:
            if '日替' in link.get_attribute('title'):
                link.click()
                print(f'Clicked on link with title: {link.get_attribute("title")}')
                time.sleep(3)  # クリックした後に3秒間待機
                return

        print('No link found with title containing: 日替')
    except Exception as e:
        print(f'Error clicking on element: {e}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # ここで指定されたXPathを使用
    base_xpath = '/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul'
    click_date_element(driver, base_xpath)
    driver.quit()

if __name__ == '__main__':
    main()

の中にこの処理を追加して画像をダウンロードできるようにする

import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver, base_xpath):
    try:
        # コンテナ内の日付要素を探してクリック
        container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, base_xpath))
        )
        links = container.find_elements(By.XPATH, ".//a[contains(@title, '日替')]")

        for link in links:
            if '日替' in link.get_attribute('title'):
                link.click()
                print(f'Clicked on link with title: {link.get_attribute("title")}')
                time.sleep(3)  # クリックした後に3秒間待機
                return

        print('No link found with title containing: 日替')
    except Exception as e:
        print(f'Error clicking on element: {e}')

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            with open(f'image_{i}.jpg', 'wb') as file:
                file.write(response.content)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # 特定のリンクをクリックする
    base_xpath_click = '/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul'
    click_date_element(driver, base_xpath_click)
    
    # 画像を取得してダウンロードする
    base_xpath_images = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]'
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        download_images(image_urls)

if __name__ == '__main__':
    main()

コードの説明
1. リンクを開く:
pythonコードをコピーする

driver = open_link_in_safari(url)
2. 


3. 特定のリンクをクリックする:
pythonコードをコピーする

base_xpath_click = ‘/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul’
4. click_date_element(driver, base_xpath_click)
5. 


6. 画像を取得してダウンロードする:
pythonコードをコピーする

base_xpath_images = ‘/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]’
7. image_urls = get_images_from_container(driver, base_xpath_images)
8. driver.quit()
9.
10. if image_urls:
11. download_images(image_urls)
12. 


このスクリプトは、指定されたURLを開き、「日替」という文字列が含まれるリンクをクリックし、その後、画像を取得してダウンロードします。これにより、リンククリックと画像ダウンロードの処理を一貫して行うことができます。

しかし画像が3分割されて見えにくいので統合する

pip install selenium pillow requests

で必要なライブラリをインストール

import time
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver, base_xpath):
    try:
        # コンテナ内の日付要素を探してクリック
        container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, base_xpath))
        )
        links = container.find_elements(By.XPATH, ".//a[contains(@title, '日替')]")

        for link in links:
            if '日替' in link.get_attribute('title'):
                link.click()
                print(f'Clicked on link with title: {link.get_attribute("title")}')
                time.sleep(3)  # クリックした後に3秒間待機
                return

        print('No link found with title containing: 日替')
    except Exception as e:
        print(f'Error clicking on element: {e}')

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    widths, heights = zip(*(img.size for img in images))

    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # 特定のリンクをクリックする
    base_xpath_click = '/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul'
    click_date_element(driver, base_xpath_click)
    
    # 画像を取得してダウンロードする
    base_xpath_images = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]'
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            merge_images(images, 'combined_image.jpg')

if __name__ == '__main__':
    main()

1. リンクを開く:
pythonコードをコピーする

driver = open_link_in_safari(url)
2. 


3. 特定のリンクをクリックする:
pythonコードをコピーする

base_xpath_click = ‘/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul’
4. click_date_element(driver, base_xpath_click)
5. 


6. 画像を取得してダウンロードする:
pythonコードをコピーする

base_xpath_images = ‘/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]’
7. image_urls = get_images_from_container(driver, base_xpath_images)
8. 


9. 画像をダウンロード:
pythonコードをコピーする

images = download_images(image_urls)
10. 


11. 画像を結合して保存:
pythonコードをコピーする

if images:
12. merge_images(images, ‘combined_image.jpg’)
13. 


このスクリプトは、指定されたURLを開き、「日替」という文字列が含まれるリンクをクリックし、その後、画像を取得してダウンロードし、それらを1つの画像ファイルに統合します。統合された画像はcombined_image.jpgとして保存

しかし画像がバラバラ

import time
import requests
from PIL import Image
from io import BytesIO
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.safari.service import Service as SafariService
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_link_in_safari(url):
    # Safariドライバーを使用してブラウザを起動
    service = SafariService()
    driver = webdriver.Safari(service=service)
    driver.get(url)
    time.sleep(3)  # リンクを開いた後に3秒間待機
    return driver

def click_date_element(driver, base_xpath):
    try:
        # コンテナ内の日付要素を探してクリック
        container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, base_xpath))
        )
        links = container.find_elements(By.XPATH, ".//a[contains(@title, '日替')]")

        for link in links:
            if '日替' in link.get_attribute('title'):
                link.click()
                print(f'Clicked on link with title: {link.get_attribute("title")}')
                time.sleep(3)  # クリックした後に3秒間待機
                return

        print('No link found with title containing: 日替')
    except Exception as e:
        print(f'Error clicking on element: {e}')

def get_images_from_container(driver, base_xpath):
    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')
            # 特定の条件に基づいて画像をフィルタリング
            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

def download_images(image_urls):
    images = []
    for i, url in enumerate(image_urls):
        response = requests.get(url)
        if response.status_code == 200:
            image = Image.open(BytesIO(response.content))
            images.append(image)
            print(f'Downloaded image_{i}.jpg')
        else:
            print(f'Failed to download {url}')
    return images

def merge_images(images, output_path):
    widths, heights = zip(*(img.size for img in images))

    total_height = sum(heights)
    max_width = max(widths)

    combined_image = Image.new('RGB', (max_width, total_height))

    y_offset = 0
    for img in images:
        combined_image.paste(img, (0, y_offset))
        y_offset += img.height

    combined_image.save(output_path)
    print(f'Saved combined image as {output_path}')

def main():
    url = 'https://www.shufoo.net/pntweb/shopDetail/860323/?cid=nmail_pc'
    driver = open_link_in_safari(url)
    # 特定のリンクをクリックする
    base_xpath_click = '/html/body/div[1]/div[3]/div[1]/div/div[4]/div/div/div/div/div/div/ul'
    click_date_element(driver, base_xpath_click)
    
    # 画像を取得してダウンロードする
    base_xpath_images = '/html/body/div[1]/div[3]/div[1]/div/div[2]/div[2]'
    image_urls = get_images_from_container(driver, base_xpath_images)
    driver.quit()
    
    if image_urls:
        images = download_images(image_urls)
        if images:
            merge_images(images, '/mnt/data/combined_image_corrected.jpg')

if __name__ == '__main__':
    main()

で位置が合うか実験

結局位置は合わなかったが画像は一枚になった

直接杏林堂のHPで画像を取得しようとしたが
習慣のチラシになるためこの方法はやめた

とりあえずチラシの取得はできたので
次に
Cloud vision APIで文字列として商品情報が取得できるか試す

メール読み上げを設定ファイルからに変更

メール読み上げを設定ファイルからに変更

config.ini

を作成し

[Settings]
server_ip = 192.168.1.69:50021
label_id = GmailのラベルID
detection_interval = 1200
notification_sound = notice.wav
pdf_notification_sound = notice_pdf.wav

とする

configparser モジュールを使用して、INIファイルから設定を読みこむ

kao.py

import configparser

を追加

# configparserを使用してINIファイルを読み込む
config = configparser.ConfigParser()
config.read('config.ini')

# 設定を変数に格納
server_ip = config['Settings']['server_ip']
label_id = config['Settings']['label_id']
detection_interval = int(config['Settings']['detection_interval'])

        if lastTime is None or time.perf_counter() - lastTime > 1200:
を
        if lastTime is None or time.perf_counter() - lastTime > detection_interval:

というように設定値の定数へ変更

次にサーバーIPも設定ファイルから読み込むように変更する

email_processor,py

のコードを変更

import configparser

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')
server_ip = config['Settings']['server_ip']
label_id = config['Settings']['label_id']
notification_sound = config['Settings']['notification_sound']
pdf_notification_sound = config['Settings']['pdf_notification_sound']

を追記

音声を生成する部部の

    command_json = [
        "curl", "-s", "-X", "POST",
        "192.168.1.69:50021/audio_query?speaker=1",
        "--get", "--data-urlencode", f"text={text}"
    ]
    command_audio = [
        "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
        "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
    ]
でサーバーIPの部分を
    command_json = [
        "curl", "-s", "-X", "POST",
        f"http://{server_ip}/audio_query?speaker=1",
        "--get", "--data-urlencode", f"text={text}"
    ]
    command_audio = [
        "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
        "-d", "@query.json", f"http://{server_ip}/synthesis?speaker=1"
    ]

へ変更

また再生する音声を

        playsound('notice.wav')

から

playsound(notification_sound)

へ変更

                        playsound('notice_pdf.wav')

                        playsound(pdf_notification_sound)

へ変更

これで
Voicevox のdocker サーバーIP
お知らせの音声
顔認識してメールを読み上げる時間の指定
Gmailで読み上げるラベルの指定
を設定ファイルで行えるように変更完了

あとは
GithubへSSHでアップできるようにする

顔を認識したらメールを読み上げる

顔を認識したらメールを読み上げる

import fitz
from playsound import playsound
import subprocess
import pygame
import time

# テキストから音声を生成して再生する関数
def generate_and_play_audio_from_text(text):
    command_json = [
        "curl", "-s", "-X", "POST",
        "192.168.1.69:50021/audio_query?speaker=1",
        "--get", "--data-urlencode", f"text={text}"
    ]
    command_audio = [
        "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
        "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
    ]
    with open('query.json', 'w') as file:
        subprocess.run(command_json, stdout=file)
    with open('audio_output.wav', 'wb') as file:
        subprocess.run(command_audio, stdout=file)
    pygame.init()
    pygame.mixer.init()
    sound = pygame.mixer.Sound("audio_output.wav")
    sound.play()
    while pygame.mixer.get_busy():
        time.sleep(0.1)

# PDFファイルから文字数をカウントする関数
def count_pdf_characters(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return len(text)

# メールの処理を行う関数
def process_email(service, label_id):
    from gmail_utils import gmail_get_latest_unread_message_body
    from pdf_downloader import find_preview_link, download_pdf

    body, urls = gmail_get_latest_unread_message_body(service, label_id)
    if body:
        playsound('notice.wav')
        generate_and_play_audio_from_text(body)
        if urls:
            for url in urls:
                preview_url = find_preview_link(url)
                if preview_url:
                    download_pdf(preview_url, "downloaded_file.pdf")
                    char_count = count_pdf_characters("downloaded_file.pdf")
                    if char_count >= 100:
                        playsound('notice_pdf.wav')
                else:
                    print("プレビューリンクが見つかりませんでした。")
        else:
            print("メールにURLが見つかりませんでした。")
    else:
        print("未読メールはありません。")

というようにモジュールにして保存
ファイル名は

email_processor.py

とした

次に
Kao,pyで顔認識したらメールを読み上げるようにする

git clone https://github.com/Snowpooll/face_weather.git

で以前作成した顔認識したら天気を知らせるの中にあるものを

cp ../../face_weather/haarcascade_* .

でコピーして使う

import cv2
import time
from email_processor import process_email
from gmail_utils import gmail_init

# Haar Cascade分類器の読み込み
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

# Webカメラの設定
cap = cv2.VideoCapture(0)  # 0番目のカメラを使用する場合

# Gmailサービスの初期化
service = gmail_init()
label_id = "ラベルID"  # 例として特定のラベルIDを設定

# 最後の顔検出時刻
lastTime = None

# メインループ
while True:
    # カメラからのフレームの取得
    ret, frame = cap.read()
    
    # フレームのグレースケール化
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 顔の検出
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
    
    # 検出された顔に対する処理
    for (x, y, w, h) in faces:
        # 検出自の処理(検出から1分たったら再度イベント動かす)
        if lastTime is None or time.perf_counter() - lastTime > 60:
            # 検出時刻更新
            lastTime = time.perf_counter()
            # メール処理関数を呼び出し
            process_email(service, label_id)

# 後処理
cap.release()
cv2.destroyAllWindows()

で実行したら成功したが
間隔を一分にしたため
メールばかり読み上げになる

なので感覚を20分ぐらいに変更する

        # 検出自の処理(検出から20分たったら再度イベント動かす)
        if lastTime is None or time.perf_counter() - lastTime > 1200:

へ変更した

とりあえず動作確認は取れたので
次に設定ファイルを作成し
コードのメンテをしやすくする

とりあえず
docker サーバーIP
gmailのラベル
次の検出までの待ち時間
は設定ファイルにまとめておき
これを編集するだけでできるようにする

ダウンロード機能の修正とモジュール化

ダウンロード機能の修正とモジュール化

import fitz  # PyMuPDF
from playsound import playsound
from gmail_utils import gmail_init, gmail_get_latest_unread_message_body
from pdf_downloader import find_preview_link, download_pdf

def save_email_body_to_text(body, filename="email_body.txt"):
    with open(filename, "w", encoding="utf-8") as file:
        file.write(body)

def count_pdf_characters(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return len(text)

def main():
    # Gmail API サービスを初期化
    service = gmail_init()
    
    # ラベル ID を指定して最新の未読メール本文とURLを取得
    body, urls = gmail_get_latest_unread_message_body(service, "ラベルID")
    
    # 未読メールがある場合は音声ファイルを再生
    if body:
        playsound('notice.wav')

        if urls:
            for url in urls:
                print(f"プレビューリンクを検索するURL: {url}")
                # プレビューリンクを取得
                preview_url = find_preview_link(url)
                if preview_url:
                    print(f"プレビューリンク: {preview_url}")
                    # プレビューリンクからPDFファイルのダウンロードを試みる
                    download_pdf(preview_url, file_path="downloaded_file.pdf")
                    
                    # PDFファイルから文字数をカウント
                    char_count = count_pdf_characters("downloaded_file.pdf")
                    print(f"PDF内の文字数: {char_count}")
                    
                    # 文字数が100文字以上の場合は別の音声ファイルを再生
                    if char_count >= 100:
                        playsound('notice_pdf.wav')

                else:
                    print("プレビューリンクが見つかりませんでした。")
        else:
            print("メールにURLが見つかりませんでした。")

        # メール本文をテキストファイルに保存
        save_email_body_to_text(body)
    else:
        print("未読メールはありません。")

if __name__ == "__main__":
    main()

としたらリンクがエラーになる

from gmail_utils import gmail_init, gmail_get_latest_unread_message_body
from pdf_downloader import find_preview_link, download_pdf

def save_email_body_to_text(body, filename="email_body.txt"):
    with open(filename, "w", encoding="utf-8") as file:
        file.write(body)

def main():
    # Gmail API サービスを初期化
    service = gmail_init()
    
    # ラベル ID を指定して最新の未読メール本文とURLを取得
    body, urls = gmail_get_latest_unread_message_body(service, "ラベルID")
    if urls:
        for url in urls:
            print(f"プレビューリンクを検索するURL: {url}")
            # プレビューリンクを取得
            preview_url = find_preview_link(url)
            if preview_url:
                print(f"プレビューリンク: {preview_url}")
                # プレビューリンクからPDFファイルのダウンロードを試みる
                download_pdf(preview_url, file_path="downloaded_file.pdf")
            else:
                print("プレビューリンクが見つかりませんでした。")
    else:
        print("メールにURLが見つかりませんでした。")

    # メール本文をテキストファイルに保存
    save_email_body_to_text(body)

if __name__ == "__main__":
    main()

だと問題ない

とりあえず

import fitz  # PyMuPDF
from playsound import playsound
from gmail_utils import gmail_init, gmail_get_latest_unread_message_body
from pdf_downloader import find_preview_link, download_pdf
import subprocess
import pygame
import time

def generate_and_play_audio_from_text(text):
    # JSONファイルを作成するためのcurlコマンド
    command_json = [
        "curl", "-s", "-X", "POST",
        "192.168.1.69:50021/audio_query?speaker=1",
        "--get", "--data-urlencode", f"text={text}"
    ]

    # 音声ファイルを作成するためのcurlコマンド
    command_audio = [
        "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
        "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
    ]

    # JSONファイルと音声ファイルを作成
    with open('query.json', 'w') as file:
        subprocess.run(command_json, stdout=file)
    with open('audio_output.wav', 'wb') as file:
        subprocess.run(command_audio, stdout=file)

    # Pygameで音声ファイルを再生
    pygame.init()
    pygame.mixer.init()
    sound = pygame.mixer.Sound("audio_output.wav")
    sound.play()
    while pygame.mixer.get_busy():
        time.sleep(0.1)

def count_pdf_characters(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return len(text)

def main():
    # Gmail API サービスを初期化
    service = gmail_init()
    
    # ラベル ID を指定して最新の未読メール本文とURLを取得
    body, urls = gmail_get_latest_unread_message_body(service, "ラベルID")
    
    # 未読メールがある場合は音声ファイルを再生
    if body:
        playsound('notice.wav')
        generate_and_play_audio_from_text(body)
        

        if urls:
            for url in urls:
                print(f"プレビューリンクを検索するURL: {url}")
                # プレビューリンクを取得
                preview_url = find_preview_link(url)
                if preview_url:
                    print(f"プレビューリンク: {preview_url}")
                    # プレビューリンクからPDFファイルのダウンロードを試みる
                    download_pdf(preview_url, file_path="downloaded_file.pdf")
                    
                    # PDFファイルから文字数をカウント
                    char_count = count_pdf_characters("downloaded_file.pdf")
                    print(f"PDF内の文字数: {char_count}")
                    
                    # 文字数が100文字以上の場合は別の音声ファイルを再生
                    if char_count >= 100:
                        playsound('notice_pdf.wav')

                else:
                    print("プレビューリンクが見つかりませんでした。")
        else:
            print("メールにURLが見つかりませんでした。")
    else:
        print("未読メールはありません。")

if __name__ == "__main__":
    main()

とすることで
メールの読み上げと
PDFのダウンロードができた
また長文のPDFに関してはPDFを見るように促すようにした

あとは顔を認識したら起動するようにする

まずはこれをモジュールにする

import fitz
from playsound import playsound
import subprocess
import pygame
import time

# テキストから音声を生成して再生する関数
def generate_and_play_audio_from_text(text):
    command_json = [
        "curl", "-s", "-X", "POST",
        "192.168.1.69:50021/audio_query?speaker=1",
        "--get", "--data-urlencode", f"text={text}"
    ]
    command_audio = [
        "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
        "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
    ]
    with open('query.json', 'w') as file:
        subprocess.run(command_json, stdout=file)
    with open('audio_output.wav', 'wb') as file:
        subprocess.run(command_audio, stdout=file)
    pygame.init()
    pygame.mixer.init()
    sound = pygame.mixer.Sound("audio_output.wav")
    sound.play()
    while pygame.mixer.get_busy():
        time.sleep(0.1)

# PDFファイルから文字数をカウントする関数
def count_pdf_characters(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return len(text)

# メールの処理を行う関数
def process_email(service, label_id):
    from gmail_utils import gmail_get_latest_unread_message_body
    from pdf_downloader import find_preview_link, download_pdf

    body, urls = gmail_get_latest_unread_message_body(service, label_id)
    if body:
        playsound('notice.wav')
        generate_and_play_audio_from_text(body)
        if urls:
            for url in urls:
                preview_url = find_preview_link(url)
                if preview_url:
                    download_pdf(preview_url, "downloaded_file.pdf")
                    char_count = count_pdf_characters("downloaded_file.pdf")
                    if char_count >= 100:
                        playsound('notice_pdf.wav')
                else:
                    print("プレビューリンクが見つかりませんでした。")
        else:
            print("メールにURLが見つかりませんでした。")
    else:
        print("未読メールはありません。")

として

email_processor.py

として保存

次に顔を検出したらこれを呼び出すようにする

Gmail読み上げとメールのお知らせを作る

Gmail読み上げとメールのお知らせを作る

from gmail_utils import gmail_init, gmail_get_latest_unread_message_body
from pdf_downloader import find_preview_link, download_pdf

def save_email_body_to_text(body, filename="email_body.txt"):
    with open(filename, "w", encoding="utf-8") as file:
        file.write(body)

def main():
    # Gmail API サービスを初期化
    service = gmail_init()
    
    # ラベル ID を指定して最新の未読メール本文とURLを取得
    body, urls = gmail_get_latest_unread_message_body(service, "ラベルID")
    if urls:
        for url in urls:
            print(f"プレビューリンクを検索するURL: {url}")
            # プレビューリンクを取得
            preview_url = find_preview_link(url)
            if preview_url:
                print(f"プレビューリンク: {preview_url}")
                # プレビューリンクからPDFファイルのダウンロードを試みる
                download_pdf(preview_url, file_path="downloaded_file.pdf")
            else:
                print("プレビューリンクが見つかりませんでした。")
    else:
        print("メールにURLが見つかりませんでした。")

    # メール本文をテキストファイルに保存
    save_email_body_to_text(body)

if __name__ == "__main__":
    main()

でテキストファイルの作成とPDF取得ができているので
お知らせメッセージの
notice.wavを再生する

GPTによれば

pip install playsound

でインストールし
コードを変更する

from playsound import playsound
from gmail_utils import gmail_init, gmail_get_latest_unread_message_body
from pdf_downloader import find_preview_link, download_pdf

def save_email_body_to_text(body, filename="email_body.txt"):
    with open(filename, "w", encoding="utf-8") as file:
        file.write(body)

def main():
    # Gmail API サービスを初期化
    service = gmail_init()
    
    # ラベル ID を指定して最新の未読メール本文とURLを取得
    body, urls = gmail_get_latest_unread_message_body(service, "ラベルID")
    
    # 未読メールがある場合は音声ファイルを再生
    if body:
        playsound('notice.wav')

        if urls:
            for url in urls:
                print(f"プレビューリンクを検索するURL: {url}")
                # プレビューリンクを取得
                preview_url = find_preview_link(url)
                if preview_url:
                    print(f"プレビューリンク: {preview_url}")
                    # プレビューリンクからPDFファイルのダウンロードを試みる
                    download_pdf(preview_url, file_path="downloaded_file.pdf")
                else:
                    print("プレビューリンクが見つかりませんでした。")
        else:
            print("メールにURLが見つかりませんでした。")

        # メール本文をテキストファイルに保存
        save_email_body_to_text(body)
    else:
        print("未読メールはありません。")

if __name__ == "__main__":
    main()

変更点は

def save_email_body_to_text(body, filename="email_body.txt"):
    with open(filename, "w", encoding="utf-8") as file:
        file.write(body)

で音声ファイルを読み込んでいること

Main.pyの中で

    # 未読メールがある場合は音声ファイルを再生
    if body:
        playsound('notice.wav')

を追加して音声を出している

次にPDFの文字数を調べて100文字以上なら
音声でPDFの内容を確認するように促す

あとはメール本文の読み上げ処理だけ

PyMuPDFを使い
文字数をカウント、文字数が100文字以上ならPDFを確認する音声を再生するようにコード変更

import fitz  # PyMuPDF
from playsound import playsound
from gmail_utils import gmail_init, gmail_get_latest_unread_message_body
from pdf_downloader import find_preview_link, download_pdf

def save_email_body_to_text(body, filename="email_body.txt"):
    with open(filename, "w", encoding="utf-8") as file:
        file.write(body)

def count_pdf_characters(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return len(text)

def main():
    # Gmail API サービスを初期化
    service = gmail_init()
    
    # ラベル ID を指定して最新の未読メール本文とURLを取得
    body, urls = gmail_get_latest_unread_message_body(service, "ラベルID")
    
    # 未読メールがある場合は音声ファイルを再生
    if body:
        playsound('notice.wav')

        if urls:
            for url in urls:
                print(f"プレビューリンクを検索するURL: {url}")
                # プレビューリンクを取得
                preview_url = find_preview_link(url)
                if preview_url:
                    print(f"プレビューリンク: {preview_url}")
                    # プレビューリンクからPDFファイルのダウンロードを試みる
                    download_pdf(preview_url, file_path="downloaded_file.pdf")
                    
                    # PDFファイルから文字数をカウント
                    char_count = count_pdf_characters("downloaded_file.pdf")
                    print(f"PDF内の文字数: {char_count}")
                    
                    # 文字数が100文字以上の場合は別の音声ファイルを再生
                    if char_count >= 100:
                        playsound('notice_pdf.wav')

                else:
                    print("プレビューリンクが見つかりませんでした。")
        else:
            print("メールにURLが見つかりませんでした。")

        # メール本文をテキストファイルに保存
        save_email_body_to_text(body)
    else:
        print("未読メールはありません。")

if __name__ == "__main__":
    main()

あとはvoicevoxの処理のみ

とりあえずdockerを起動させる

docker run -d -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest

でやったら普通に音声が再生できたので
単純に負荷でdockerが落ちてたみたい

文章を音声で読み上げしたが
どうやら最初の文章に日付と宛先の名前がくっついている
毎回main3.pyを実行するたびに他のメールでも同じなので
最初の文章を削除する

Gmail未読メールの読み上げ

Gmail未読メールの読み上げ

cp mail_message.py unread_mail_message.py


ファイルをコピーしてから改造
未読の最新の1件のみ取得するようにする

ChatGPTでの出力
最新の未読メール1件のみを取得するようにコードを修正します。
既にmessages.list API呼び出しでmaxResults=1を指定しているため、最新の1件だけを取得しています。
しかし、一番新しいメールが未読でない可能性があるため、
念のためにメッセージのリストをループする部分を削除し、最初のメッセージのみを処理するようする

from __future__ import print_function
import os.path
import re
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# URLを削除する関数
def remove_urls(text):
    # URLにマッチする正規表現パターン
    url_pattern = r'https?://\S+|www\.\S+'
    return re.sub(url_pattern, '', text)

# 最新の未読メール本文を取得する関数
def gmail_get_latest_unread_message_body(service, labelIdsValue):
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, q="is:unread", maxResults=1).execute()

    if 'messages' not in msg_list:
        return "No unread messages found."

    # 最新のメッセージ(最新の未読メール)を選択
    msg = msg_list['messages'][0]
    date = gmail_get_messages_body_date(messages, msg)
    topid = msg['id']
    msg = messages.get(userId='me', id=topid).execute()

    # メールの本文を取得
    body = ""
    if msg["payload"]["body"]["size"] != 0:
        body = decode(msg["payload"]["body"]["data"])
    elif 'parts' in msg["payload"]:
        # メール本文が parts 属性にある場合
        for part in msg["payload"]["parts"]:
            if part["body"]["size"] != 0:
                body = decode(part["body"]["data"])
                break

    if not body:
        return date + "<br> No body content"

    # URLを削除
    body_no_urls = remove_urls(body)
    return date + "<br>" + body_no_urls

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()

# ラベル ID を指定して最新の未読メール本文を取得
latest_unread_message_body = gmail_get_latest_unread_message_body(service, "Label_4")
print(latest_unread_message_body)

これで最新の1件のみ取得できるようになった
さらに差し出し元などもなく本文のみ取得している

URLの削除もできているが
メルマガによっては
——
などで区切っているところがある
これは読み上げるとどうなるのか?

もしダメなら、削除項目を増やすこと

とりあえず本文を読み上げる

まず支援学校のメールのIDになるように

latest_unread_message_body = gmail_get_latest_unread_message_body(service, "Label_4")

Label_4の部分を変える

次に
本文が

特別支援学校よりメッセージをお預かりしています。
下記URLをクリックして内容を確認してください。



※学校からのお知らせの保存期間は1年間です。
重要なお知らせは、スクリーンショットなどでお手元に保存してください。
※お手元で保存された画像等データのお取り扱いにつきましては、個人情報保護に十分ご配慮ください。

となって取得できているので
記号がどうなるか試すため
Docker を起動して実験

docker run -d  -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest

あとは
取得したメールの本文が
mail.txt
にあるので中のテキストをもとにして音声を作成する

curl -s -X POST "192.168.1.69:50021/audio_query?speaker=1" --get --data-urlencode text@mail.txt > query.json

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > mail_audio.wav

普通に問題なく記号や句読点などは読み上げることなく成功した

支援学校は問題なさそうなので
メルマガで実験する

サーチマン佐藤さんのメルマガを使い実験

mail_searchman.txt

として保存し

curl -s -X POST "192.168.1.69:50021/audio_query?speaker=1" --get --data-urlencode text@mail_searchman.txt > query.json

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > mail_searchman_audio.wav

で作成し音声を作成

しかし途中でdocker が過負荷で落ちる
このためおそらくこの方法ではメルマガなどの長文を再生することは出来なさそう

pythonでGmailの最新、または未読の本文を取得する

最新のものだけ取得するようにする

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# 最新のメール本文を取得する関数
def gmail_get_latest_message_body(service, labelIdsValue):
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, maxResults=1).execute()

    if not msg_list['messages']:
        return "No messages found."

    msg = msg_list['messages'][0]
    date = gmail_get_messages_body_date(messages, msg)
    topid = msg['id']
    msg = messages.get(userId='me', id=topid).execute()

    # メールの本文を取得
    if msg["payload"]["body"]["size"] != 0:
        return date + "<br>" + decode(msg["payload"]["body"]["data"])
    elif 'parts' in msg["payload"]:
        # メール本文が parts 属性にある場合
        for part in msg["payload"]["parts"]:
            if part["body"]["size"] != 0:
                return date + "<br>" + decode(part["body"]["data"])
    return date + "<br> No body content"

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定して最新のメール本文を取得
latest_message_body = gmail_get_latest_message_body(service, "Label_4")
print(latest_message_body)

これで最新の1件のメールのみ取得できるようになる

次に未読のものだけ取得するようにする

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# 未読メールの本文を取得する関数
def gmail_get_unread_messages_body(service, labelIdsValue):
    mailBody = []
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, q="is:unread").execute()

    if 'messages' not in msg_list:
        return ["No unread messages found."]

    for msg in msg_list['messages']:
        date = gmail_get_messages_body_date(messages, msg)
        topid = msg['id']
        msg = messages.get(userId='me', id=topid).execute()

        # メールの本文を取得
        if msg["payload"]["body"]["size"] != 0:
            mailBody.append(date + "<br>" + decode(msg["payload"]["body"]["data"]))
        elif 'parts' in msg["payload"]:
            # メール本文が parts 属性にある場合
            for part in msg["payload"]["parts"]:
                if part["body"]["size"] != 0:
                    mailBody.append(date + "<br>" + decode(part["body"]["data"]))
                    break
        else:
            mailBody.append(date + "<br> No body content")

    return mailBody

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定して未読メールの本文を取得
unread_messages_body = gmail_get_unread_messages_body(service, "Label_4")
for body in unread_messages_body:
    print(body)

未読のメールのみを表示するようにコードを修正するには
Gmail APIのクエリパラメータに q=”is:unread” を追加する

q=”is:unread” パラメータを messages.list
API呼び出しに追加することで、未読のメールのみがフィルタリングされる

取得したメールの中で
decode 関数を使用してメールの本文をデコードし
必要に応じて parts 属性を確認

未読メールがない場合
“No unread messages found.”
というメッセージが返され
未読メールがある場合は
それぞれのメールについて受信日時と本文が表示される

Label_4
に属する未読メールのみを処理しているので
他のラベルや追加のフィルタリング条件を使用したい場合は
labelIdsValue 引数や q パラメータを適宜変更する

この場合
過去全てからになってしまうので期間を設定する

未読で最新のものを取得するようにする
そしてURLを本文から削除

from __future__ import print_function
import os.path
import re
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# URLを削除する関数
def remove_urls(text):
    # URLにマッチする正規表現パターン
    url_pattern = r'https?://\S+|www\.\S+'
    return re.sub(url_pattern, '', text)

# 未読で最新のメール本文を取得する関数
def gmail_get_latest_unread_message_body(service, labelIdsValue):
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, q="is:unread").execute()

    if 'messages' not in msg_list:
        return "No unread messages found."

    # 未読メッセージのリストを取得し、最初のメッセージ(最新)を選択
    msg = msg_list['messages'][0]
    date = gmail_get_messages_body_date(messages, msg)
    topid = msg['id']
    msg = messages.get(userId='me', id=topid).execute()


    # メールの本文を取得
    body = ""
    if msg["payload"]["body"]["size"] != 0:
        body = decode(msg["payload"]["body"]["data"])
    elif 'parts' in msg["payload"]:
        # メール本文が parts 属性にある場合
        for part in msg["payload"]["parts"]:
            if part["body"]["size"] != 0:
                body = decode(part["body"]["data"])
                break

    if not body:
        return date + "<br> No body content"

    # URLを削除
    body_no_urls = remove_urls(body)
    return date + "<br" + body_no_urls

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定して未読メールの本文を取得
latest_unread_message_body  = gmail_get_latest_unread_message_body(service, "Label_4")
print(latest_unread_message_body)


しかしこれだと本文の中のURLのみ削除しているため
発信元とかについては削除されていないので
これも対処する必要がある

Gmail 本文取得

pythonでGmail 本文取得

まずラベルを指定して取得するには
ラベルIDが必要になる

vim base.py

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

import base64, email #デコード用
import dateutil.parser

#token.jsonを設定
tokenPath = "token.json"

#credentials.jsonを設定
credentialsPath = "credentials.json"


#メール本文のデコード
def decode(encoded):
   decoded = base64.urlsafe_b64decode(encoded).decode()
   return decoded


#メール本文の内容を配列で取得する関数
def gmail_get_messages_body(service, labelIdsValue):
   mailBody = []
   
   # メッセージの一覧を取得
   messages = service.users().messages()
   msg_list = messages.list(userId='me', labelIds=labelIdsValue).execute() 
   # msg_list = messages.list(userId='me', labelIds=labelIdsValue ,maxResults=456).execute() #最大値指定
   
   # 取得したメッセージの一覧を配列に格納
   for msg in msg_list['messages']:
       
       #メールの受信日時を取得
       date = gmail_get_messages_body_date(messages,msg)
       
       topid = msg['id']     
       msg = messages.get(userId='me', id=topid).execute()
       
       if(msg["payload"]["body"]["size"]!=0):
           mailBody.append(date+"<br>"+decode(msg["payload"]["body"]["data"])) 
       else:
           #メールによっては"parts"属性の中に本文がある場合もある
           mailBody.append(date+"<br>"+decode(msg["payload"]["parts"][0]["body"]["data"])) 

   return mailBody
   

#gmail_get_messages_body関数内で受信日時を取得する関数
def gmail_get_messages_body_date(messages,msg):
   msg_id = msg['id']
   m = messages.get(userId='me', id=msg_id, format='raw').execute()
   raw = base64.urlsafe_b64decode(m['raw'])
   
   # Emailを解析する
   eml = email.message_from_bytes(raw)
   
   date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
   return date

#ラベルのIDやnameを表示する関数
def gmail_display_label(service):
   results = service.users().labels().list(userId='me').execute()
   labels = results.get('labels', [])

   if not labels:
       print('No labels found.')
   else:
       print('Labels:')
       for label in labels:
           print(label)


#初期化(from quickstart.py)
def gmail_init():
   creds = None
   # The file token.json stores the user's access and refresh tokens, and is
   # created automatically when the authorization flow completes for the first
   # time.
   if os.path.exists(tokenPath):
       creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
   # If there are no (valid) credentials available, let the user log in.
   if not creds or not creds.valid:
       if creds and creds.expired and creds.refresh_token:
           creds.refresh(Request())
       else:
           flow = InstalledAppFlow.from_client_secrets_file(
               credentialsPath, SCOPES)
           creds = flow.run_local_server(port=0)
       # Save the credentials for the next run
       with open(tokenPath, 'w') as token:
           token.write(creds.to_json())
           
   service = build('gmail', 'v1', credentials=creds)
   return service


#ここからやりたい処理を書く
service = gmail_init()

#quickstart.pyと同じ処理にしてみた
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])

if not labels:
   print('No labels found.')
else:
   print('Labels:')
   for label in labels:
       print(label['name'])
       
gmail_display_label(service)

として

python base.py

を実行すると
ラベルとID一覧が表示される

これでラベルIDがわかったので本文を取得するようにコード変更

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# メール本文を取得する関数
def gmail_get_messages_body(service, labelIdsValue):
    mailBody = []
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue).execute()

    for msg in msg_list['messages']:
        date = gmail_get_messages_body_date(messages, msg)
        topid = msg['id']
        msg = messages.get(userId='me', id=topid).execute()

        # メールの本文を取得
        if msg["payload"]["body"]["size"] != 0:
            mailBody.append(date + "<br>" + decode(msg["payload"]["body"]["data"]))
        elif 'parts' in msg["payload"]:
            # メール本文が parts 属性にある場合
            for part in msg["payload"]["parts"]:
                if part["body"]["size"] != 0:
                    mailBody.append(date + "<br>" + decode(part["body"]["data"]))
                    break
        else:
            mailBody.append(date + "<br> No body content")

    return mailBody

# メールの受信日時を取得する関数
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定してメール本文を取得
mail_bodies = gmail_get_messages_body(service, "Label_4")
for body in mail_bodies:
    print(body)

これで

python base.py

とすると
Lavel_4のIDのメールの本文を表示することができる

gmail読み上げ (タイトル取得まで)

gmail読み上げ (タイトル取得まで)

ダウンロードしたファイルをコピーしてリネームする
詳細は
https://developers.google.com/gmail/api/quickstart/python?hl=ja
にあるが
とりあえず
credentials.json
にしておく

cd Downloads
cp client_secret_336287491272-faqvqnb4hjrg4ragurjh8nfhn2s3ujkg.apps.googleusercontent.com.json credentials.json

のあと作業ディレクトリ作成

mkdir -p mail_auto

ここへ認証ファイルの credentials.json をコピーする

あとはライブラリのインストール

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

次に
quickstart.py
を作成し
コピペして動作確認

from __future__ import print_function

import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']


def main():
    """Shows basic usage of the Gmail API.
    Lists the user's Gmail labels.
    """
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:
        # Call the Gmail API
        service = build('gmail', 'v1', credentials=creds)
        results = service.users().labels().list(userId='me').execute()
        labels = results.get('labels', [])

        if not labels:
            print('No labels found.')
            return
        print('Labels:')
        for label in labels:
            print(label['name'])

    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')


if __name__ == '__main__':
    main()

保存したら

python3 quickstart.py

で実行

この時に認証画面が出るが
セキュリティの危険があるような警告が出る
これはサーバーのオレオレ証明書の時のようなものなので
気にせず続ける

認証が終わると
メールのラベル一覧が表示される