import os
import sys
import time
import requests
import threading
import tkinter as tk
from tkinter import filedialog, messagebox
from datetime import datetime, timezone, timedelta
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pystray
from PIL import Image, ImageDraw
class GPVManagerApp:
def __init__(self, root):
self.root = root
self.root.title("GPVデータ 自動取得&管理ツール (Rjp対応版)")
self.root.geometry("600x600")
self.root.protocol('WM_DELETE_WINDOW', self.hide_window)
self.target_date_var = tk.StringVar()
self.save_dir_var = tk.StringVar()
self.start_date_var = tk.StringVar()
self.end_date_var = tk.StringVar()
self.download_event = threading.Event()
self.period_download_event = threading.Event()
self.delete_event = threading.Event()
self.create_widgets()
self.set_utc_today()
def create_widgets(self):
setting_frame = tk.LabelFrame(self.root, text="基本設定", padx=10, pady=10)
setting_frame.pack(pady=5, padx=10, fill="x")
tk.Label(setting_frame, text="対象日 (UTC自動更新):").grid(row=0, column=0, sticky="w", pady=5)
tk.Entry(setting_frame, textvariable=self.target_date_var, width=15, state="readonly").grid(row=0, column=1, sticky="w", padx=5)
tk.Label(setting_frame, text="※起動中は毎日自動で最新日(世界標準時)に切り替わります", fg="gray", font=("", 9)).grid(row=0, column=2, sticky="w")
tk.Label(setting_frame, text="保存先:").grid(row=1, column=0, sticky="w", pady=5)
tk.Entry(setting_frame, textvariable=self.save_dir_var, width=35).grid(row=1, column=1, sticky="w", padx=5)
tk.Button(setting_frame, text="参照...", command=self.browse_folder).grid(row=1, column=2, sticky="w")
dl_frame = tk.LabelFrame(self.root, text="当日自動取得 (10分間隔でチェック)", padx=10, pady=10)
dl_frame.pack(pady=5, padx=10, fill="x")
self.btn_dl_start = tk.Button(dl_frame, text="▶ 取得スタート", command=self.start_download, bg="#e0f7fa", width=15)
self.btn_dl_start.pack(side="left", padx=10)
self.btn_dl_stop = tk.Button(dl_frame, text="■ 取得ストップ", command=self.stop_download, bg="#ffebee", width=15, state="disabled")
self.btn_dl_stop.pack(side="left", padx=10)
period_frame = tk.LabelFrame(self.root, text="期間指定取得 (ファイル名に「Rjp」を含むもの限定)", padx=10, pady=10)
period_frame.pack(pady=5, padx=10, fill="x")
tk.Label(period_frame, text="開始日 (YYYY/MM/DD):").grid(row=0, column=0, sticky="w", pady=5)
tk.Entry(period_frame, textvariable=self.start_date_var, width=12).grid(row=0, column=1, sticky="w", padx=5)
tk.Label(period_frame, text="終了日 (YYYY/MM/DD):").grid(row=0, column=2, sticky="w", pady=5)
tk.Entry(period_frame, textvariable=self.end_date_var, width=12).grid(row=0, column=3, sticky="w", padx=5)
self.btn_period_start = tk.Button(period_frame, text="▶ 期間取得スタート", command=self.start_period_download, bg="#e8f5e9", width=18)
self.btn_period_start.grid(row=1, column=0, columnspan=2, pady=5, padx=10, sticky="w")
self.btn_period_stop = tk.Button(period_frame, text="■ 期間取得ストップ", command=self.stop_period_download, bg="#ffebee", width=18, state="disabled")
self.btn_period_stop.grid(row=1, column=2, columnspan=2, pady=5, padx=10, sticky="w")
del_frame = tk.LabelFrame(self.root, text="24時間経過データの自動削除", padx=10, pady=10)
del_frame.pack(pady=5, padx=10, fill="x")
self.btn_del_start = tk.Button(del_frame, text="▶ 削除監視スタート", command=self.start_delete, bg="#fff3e0", width=15)
self.btn_del_start.pack(side="left", padx=10)
self.btn_del_stop = tk.Button(del_frame, text="■ 削除監視ストップ", command=self.stop_delete, bg="#ffebee", width=15, state="disabled")
self.btn_del_stop.pack(side="left", padx=10)
tk.Label(self.root, text="進行状況ログ (時刻はローカルタイム):").pack(anchor="w", padx=10)
self.log_text = tk.Text(self.root, height=10, width=75, state="disabled", bg="#f5f5f5")
self.log_text.pack(pady=5, padx=10, fill="both", expand=True)
def set_utc_today(self):
utc_today_str = datetime.now(timezone.utc).strftime("%Y/%m/%d")
self.target_date_var.set(utc_today_str)
self.start_date_var.set(utc_today_str)
self.end_date_var.set(utc_today_str)
def browse_folder(self):
folder = filedialog.askdirectory(title="保存先フォルダを選択")
if folder:
self.save_dir_var.set(folder)
def log(self, message):
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.config(state="normal")
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END)
self.log_text.config(state="disabled")
self.root.update_idletasks()
def create_icon_image(self):
image = Image.new('RGBA', (64, 64), (255, 255, 255, 0))
dc = ImageDraw.Draw(image)
dc.ellipse((10, 20, 40, 50), fill=(135, 206, 250))
dc.ellipse((25, 10, 55, 40), fill=(135, 206, 250))
dc.ellipse((20, 30, 60, 55), fill=(135, 206, 250))
dc.rectangle((15, 32, 42, 38), fill=(0, 0, 139))
dc.polygon([(42, 25), (42, 45), (57, 35)], fill=(0, 0, 139))
return image
def hide_window(self):
self.root.withdraw()
image = self.create_icon_image()
menu = pystray.Menu(
pystray.MenuItem('画面を表示', self.show_window),
pystray.MenuItem('完全に終了', self.quit_app)
)
self.icon = pystray.Icon("gpv_app", image, "GPV自動取得ツール", menu)
threading.Thread(target=self.icon.run, daemon=True).start()
def show_window(self, icon=None, item=None):
self.icon.stop()
self.root.after(0, self.root.deiconify)
def quit_app(self, icon=None, item=None):
if hasattr(self, 'icon') and self.icon is not None:
self.icon.stop()
self.download_event.set()
self.period_download_event.set()
self.delete_event.set()
os._exit(0)
def start_download(self):
if not self.save_dir_var.get():
messagebox.showwarning("確認", "出力先のフォルダを選択してください。")
return
self.btn_dl_start.config(state="disabled")
self.btn_dl_stop.config(state="normal")
self.download_event.clear()
threading.Thread(target=self.download_loop, daemon=True).start()
def stop_download(self):
self.log("取得処理の停止信号を送信しました...")
self.download_event.set()
self.btn_dl_start.config(state="normal")
self.btn_dl_stop.config(state="disabled")
def download_loop(self):
self.log("自動取得を開始しました。")
while not self.download_event.is_set():
utc_today_str = datetime.now(timezone.utc).strftime("%Y/%m/%d")
self.root.after(0, lambda: self.target_date_var.set(utc_today_str))
self._fetch_data(utc_today_str)
if self.download_event.wait(600):
break
self.log("自動取得を完全に停止しました。")
def _fetch_data(self, target_date_str):
save_dir = self.save_dir_var.get()
base_url = f"https://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/{target_date_str}/"
try:
response = requests.get(base_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a')
downloaded_count = 0
for link in links:
if self.download_event.is_set():
return
href = link.get('href')
if not href or href.startswith('?') or href == '../' or href.endswith('/'):
continue
file_url = urljoin(base_url, href)
file_name = os.path.join(save_dir, href)
if not os.path.exists(file_name):
self.log(f"新着発見・ダウンロード中: {href} ...")
try:
with requests.get(file_url, stream=True) as r:
r.raise_for_status()
with open(file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
downloaded_count += 1
time.sleep(1)
except Exception as e:
self.log(f"エラー ({href}): {e}")
if downloaded_count > 0:
self.log(f"更新完了 (UTC {target_date_str} の新規取得: {downloaded_count}件)")
except requests.exceptions.RequestException:
self.log(f"待機中: UTC {target_date_str} のデータはまだアップロードされていません。")
def start_period_download(self):
if not self.save_dir_var.get():
messagebox.showwarning("確認", "出力先のフォルダを選択してください。")
return
start_str = self.start_date_var.get().strip()
end_str = self.end_date_var.get().strip()
try:
start_date = datetime.strptime(start_str, "%Y/%m/%d")
end_date = datetime.strptime(end_str, "%Y/%m/%d")
except ValueError:
messagebox.showerror("エラー", "日付は YYYY/MM/DD の形式で入力してください。")
return
if start_date > end_date:
messagebox.showerror("エラー", "開始日は終了日より前の日付にしてください。")
return
self.btn_period_start.config(state="disabled")
self.btn_period_stop.config(state="normal")
self.period_download_event.clear()
threading.Thread(target=self.period_download_loop, args=(start_date, end_date), daemon=True).start()
def stop_period_download(self):
self.log("期間取得の停止信号を送信しました...")
self.period_download_event.set()
def period_download_loop(self, start_date, end_date):
self.log("期間指定ダウンロード(Rjp限定)を開始しました。")
current_date = start_date
save_dir = self.save_dir_var.get()
while current_date <= end_date:
if self.period_download_event.is_set():
self.log("期間指定ダウンロードが中断されました。")
break
date_str = current_date.strftime("%Y/%m/%d")
self.log(f"--- {date_str} のデータを確認中 ---")
base_url = f"https://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/{date_str}/"
try:
response = requests.get(base_url)
if response.status_code == 404:
self.log(f"データが存在しません (404): {date_str}")
else:
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a')
downloaded_count = 0
for link in links:
if self.period_download_event.is_set():
break
href = link.get('href')
if not href or href.startswith('?') or href == '../' or href.endswith('/'):
continue
if "Rjp" not in href:
continue
file_url = urljoin(base_url, href)
file_name = os.path.join(save_dir, href)
if not os.path.exists(file_name):
self.log(f"Rjpファイル発見・ダウンロード中: {href} ...")
try:
with requests.get(file_url, stream=True) as r:
r.raise_for_status()
with open(file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
downloaded_count += 1
time.sleep(1)
except Exception as e:
self.log(f"エラー ({href}): {e}")
if downloaded_count > 0:
self.log(f"{date_str} のRjpファイル取得完了: {downloaded_count}件")
else:
self.log(f"{date_str} に新たな対象Rjpファイルはありません。")
except requests.exceptions.RequestException as e:
self.log(f"通信エラー ({date_str}): {e}")
current_date += timedelta(days=1)
self.log("期間指定ダウンロード処理が終了しました。")
self.root.after(0, lambda: self.btn_period_start.config(state="normal"))
self.root.after(0, lambda: self.btn_period_stop.config(state="disabled"))
def start_delete(self):
if not self.save_dir_var.get():
messagebox.showwarning("確認", "対象のフォルダを選択してください。")
return
self.btn_del_start.config(state="disabled")
self.btn_del_stop.config(state="normal")
self.delete_event.clear()
threading.Thread(target=self.delete_loop, daemon=True).start()
def stop_delete(self):
self.log("削除監視の停止信号を送信しました...")
self.delete_event.set()
self.btn_del_start.config(state="normal")
self.btn_del_stop.config(state="disabled")
def delete_loop(self):
self.log("24時間経過データの自動削除監視をスタートしました。")
while not self.delete_event.is_set():
save_dir = self.save_dir_var.get()
now = time.time()
if os.path.exists(save_dir):
for filename in os.listdir(save_dir):
filepath = os.path.join(save_dir, filename)
if os.path.isfile(filepath):
file_mtime = os.path.getmtime(filepath)
if (now - file_mtime) > (24 * 60 * 60):
try:
os.remove(filepath)
self.log(f"自動削除: {filename}")
except Exception as e:
self.log(f"削除エラー ({filename}): {e}")
if self.delete_event.wait(60):
break
self.log("自動削除監視を完全に停止しました。")
if __name__ == "__main__":
root = tk.Tk()
app = GPVManagerApp(root)
root.mainloop()
コメント