『幽黶』はリンク切れチェックツールです。自サイトのアップロード前に確認することで記述ミス等によるリンク切れを防ぐことができます。
>>GUI外観 >>使い方以下は『幽黶』のソースコード全文です:
import os
import re
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from datetime import datetime
class LinkCheckerApp:
def __init__(self, root):
self.root = root
self.root.title("リンク切れチェックツール - 幽黶")
self.root.geometry("800x800")
self.selected_folder = ""
self.ext_counts = {}
self.ext_vars = {}
self.results = []
# オプション変数
self.exclude_mailto = tk.BooleanVar(value=False)
self.exclude_zip = tk.BooleanVar(value=False)
self.create_widgets()
def create_widgets(self):
# フォルダ選択
frame_top = ttk.Frame(self.root)
frame_top.pack(padx=10, pady=5, fill="x")
ttk.Button(frame_top, text="フォルダ選択", command=self.select_folder).pack(side="left")
self.label_folder = ttk.Label(frame_top, text="(未選択)")
self.label_folder.pack(side="left", padx=10)
# 拡張子チェック欄(スクロール付き)
self.ext_frame_outer = ttk.LabelFrame(self.root, text="拡張子別ファイル数(チェックして対象選択)")
self.ext_frame_outer.pack(padx=10, pady=5, fill="x")
canvas = tk.Canvas(self.ext_frame_outer, height=100)
scrollbar = ttk.Scrollbar(self.ext_frame_outer, orient="vertical", command=canvas.yview)
self.ext_frame = ttk.Frame(canvas)
self.ext_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=self.ext_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 探索オプション
self.option_frame = ttk.LabelFrame(self.root, text="探索オプション")
self.option_frame.pack(padx=10, pady=5, fill="x")
ttk.Checkbutton(self.option_frame, text="mailto: を除く", variable=self.exclude_mailto).pack(side="left", padx=10)
ttk.Checkbutton(self.option_frame, text="zipファイルへのリンクを除く", variable=self.exclude_zip).pack(side="left", padx=10)
# 探索開始
ttk.Button(self.root, text="探索開始", command=self.start_check).pack(pady=5)
# 結果表示
self.text_result = tk.Text(self.root, wrap="none")
self.text_result.pack(padx=10, pady=5, expand=True, fill="both")
# エクスポート
ttk.Button(self.root, text="結果をエクスポート", command=self.export_result).pack(pady=5)
def select_folder(self):
folder = filedialog.askdirectory()
if folder:
self.selected_folder = folder
self.label_folder.config(text=folder)
self.scan_extensions()
def scan_extensions(self):
self.ext_counts.clear()
for root, _, files in os.walk(self.selected_folder):
for file in files:
ext = os.path.splitext(file)[1].lower()
if ext:
self.ext_counts[ext] = self.ext_counts.get(ext, 0) + 1
# チェックボックス初期化
for widget in self.ext_frame.winfo_children():
widget.destroy()
self.ext_vars.clear()
# 全選択・全解除ボタン
control_frame = ttk.Frame(self.ext_frame)
control_frame.grid(row=0, column=0, columnspan=5, sticky="w", pady=(0, 5))
ttk.Button(control_frame, text="全てにチェック", command=self.check_all).pack(side="left", padx=5)
ttk.Button(control_frame, text="全てチェックを外す", command=self.uncheck_all).pack(side="left", padx=5)
# 拡張子チェックボックス(5列折り返し)
columns = 5
for idx, (ext, count) in enumerate(sorted(self.ext_counts.items())):
var = tk.BooleanVar(value=True)
chk = ttk.Checkbutton(self.ext_frame, text=f"{ext} ({count}件)", variable=var)
row = (idx // columns) + 1
col = idx % columns
chk.grid(row=row, column=col, sticky="w", padx=5, pady=2)
self.ext_vars[ext] = var
def check_all(self):
for var in self.ext_vars.values():
var.set(True)
def uncheck_all(self):
for var in self.ext_vars.values():
var.set(False)
def start_check(self):
self.results.clear()
self.text_result.delete("1.0", "end")
target_exts = [ext for ext, var in self.ext_vars.items() if var.get()]
if not target_exts:
messagebox.showwarning("警告", "最低1つの拡張子を選択してください。")
return
for root, _, files in os.walk(self.selected_folder):
for file in files:
ext = os.path.splitext(file)[1].lower()
if ext not in target_exts:
continue
filepath = os.path.join(root, file)
self.check_file(filepath)
if self.results:
for line in self.results:
self.text_result.insert("end", line + "\n")
else:
self.text_result.insert("end", "リンク切れは見つかりませんでした。")
def check_file(self, filepath):
try:
with open(filepath, encoding="utf-8", errors="ignore") as f:
for i, line in enumerate(f, 1):
for link in self.extract_links(line):
if self.exclude_mailto.get() and link.startswith("mailto:"):
continue
if self.exclude_zip.get() and link.lower().endswith(".zip"):
continue
target_path = self.resolve_path(filepath, link)
if target_path and not os.path.exists(target_path):
rel_path = os.path.relpath(filepath, self.selected_folder)
self.results.append(f"{rel_path} : {i}行目 : {link}")
except Exception as e:
self.results.append(f"{os.path.basename(filepath)} の読み込み中にエラー: {e}")
def extract_links(self, line):
pattern = r'(?:href|src)\s*=\s*["\'](.*?)["\']'
return [m for m in re.findall(pattern, line)
if not m.startswith(("http:", "https:", "javascript:", "#"))]
def resolve_path(self, base_file, link):
if os.path.isabs(link):
return os.path.join(self.selected_folder, link.lstrip("/\\"))
else:
return os.path.normpath(os.path.join(os.path.dirname(base_file), link))
def export_result(self):
if not self.results:
messagebox.showinfo("情報", "エクスポートする結果がありません。")
return
folder_name = os.path.basename(self.selected_folder.rstrip(os.sep))
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{folder_name}-{timestamp}.txt"
desktop = os.path.join(os.path.expanduser("~"), "Desktop")
filepath = os.path.join(desktop, filename)
with open(filepath, "w", encoding="utf-8") as f:
for line in self.results:
f.write(line + "\n")
messagebox.showinfo("保存完了", f"結果をデスクトップに保存しました:\n{filename}")
if __name__ == "__main__":
root = tk.Tk()
##タイトルバーアイコン表示用
data = '''R0lGODdhEAAQAIcAAEzM6EDB5VS91z624lmtzE+uzEyp1kCl1kemwjqh4Eacuz6S0zWSzjZ7sC59vzJ5lSpnkCZiiiFgoRtemh1UgR5UeClQjSBTeCVNfRVTjBJLhglNjRxIfyJEgRhDchI9aiA4Yx44ZB43ZBQ4YRI5ZRwyWhAvTw0qSiEhOSAhOiAgOCAfOB8fOBUjPRUiOxQjPBIiOxQgNxIgNxIfNhApSg0mRQ4lRRAiQREhORAfNxEfNB4eNR0eNhweNRseNBMeMhIeMg8eNA8eMw0eMRscMRocMBcdMRQcMRIdMhEcLw4cMA0cLwwcLxkbMRgbMBgaLhgaLRYbLRcaLhcaLRQbLxUaLBQaLRMbLxMbLhUYKhUYKBQYKhQXKRQXJxIaLBAbLhAaKxEZKREYKREYJw8XJw8XJhIWJxIWJhIWJREWJhAWJQ8WJwgaPwgaMAsaLwkbLw0aLg4bLQsbLQoZKw0YKQoYKgkYKgkYKQsXKAoXKAkWKQgXKA0YJwsXJwsXJg4WJw0WJg4WJAwWJAsWJgsWJAgXJwkWJwkWJggWJhEVJREVJBAVJA8VJQ8VJBAUIQ8UIxATIRATIA4VJA0VJA4UIQwVJQ4TIQ4THw0THw8SHw8SHg4SHw4SHQ0SHQ0SHA0RGwwRHwwRHAwRGwsVJAsVIwsUIAkVJQkUIwsTIAsTHwoTIQoTHgkTIgkTHggVJggVJQgUJAgUIwgUIggTIQcTIQgRJAgRHgkSHQsRGwkRGwwQIwwQGgsQGQoQGQkQGgkQGQgQGggQGQsPGAoPGAkQGAgPGAoPFwkPFwgPFwgPFgIMNAQKHwUNHQoOFgkOFggOFgoOFQkOFQkNFQkNFAgOFQgNFAgNEwcOFQcNFAgMEwcMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAEAAQAEAI/wCtYXtWLNgtQXToEGqVC9k1atUWURlF50YBAhMCAKhQy48qL2OclUFCyxQiO21oPLig55CSJL+aiZrSwwykTJskMbKUqEmWUM2qHUsVowWcOnXuyGESRAafYtQwHTllas6JDQcUKOMgwZasL4F4RbFiqUQHEQsg2BjyZ5KaKrimRRP2ScsOFCpWsCDSxZOxZtO0USO26ouLFzBw5BAShxSwZNSo+foxAxYsV4X2LGNmaImOUsl6YRkU69UbEx8EIHDgwVYeIKkaXTmFxw0JAwwyREgwgtUsMGEuGSEDCkMDNhQGaKjRpxIqK5SGofGRRgwICyF0AVrT6JETRcagCSPrVCRFDylbznB5wgMKp7/ZsklzZmwXp0iONO0S5iyatGkBAQA7'''
root.tk.call('wm', 'iconphoto', root._w, tk.PhotoImage(data=data))
####
app = LinkCheckerApp(root)
root.mainloop()