Python 廣告阻擋腳本
作成日: 2024年12月7日
作成日: 2024年12月7日
寫一個用python 給window使用的block廣告腳本
要在 Windows 上使用 Python 編寫一個廣告阻擋腳本,並滿足以下需求:
使用指定的 DNS 設定:
94.140.14.14
2a10:50c0::1
提供一個圖形介面 (GUI),允許用戶啟用或禁用廣告阻擋。
記憶狀態,在軟件重啟後仍能知道廣告阻擋是否啟用。
以下是一個完整的 Python 腳本示例,使用 Tkinter
作為 GUI 框架,並使用 subprocess
模組來執行 netsh
命令以更改 DNS 設定。此外,狀態會保存在一個簡單的 JSON 文件中,以便在軟件重啟後保持狀態。
注意事項:
netsh
管理的網路介面,可能需要根據具體的網路配置進行調整。pythonimport os import subprocess import tkinter as tk from tkinter import messagebox import json # 設定檔案路徑 CONFIG_FILE = "ad_block_config.json" # 廣告阻擋 DNS 設定 AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] # 預設 DNS 設定(根據需要調整) DEFAULT_DNS_IPV4 = ["8.8.8.8", "8.8.4.4"] DEFAULT_DNS_IPV6 = ["2001:4860:4860::8888", "2001:4860:4860::8844"] def is_admin(): try: return os.getuid() == 0 except AttributeError: # Windows 不支援 os.getuid,使用 ctypes import ctypes try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def get_network_adapters(): """取得所有有線和無線網路介面的名稱""" cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) interfaces = [] for line in result.stdout.splitlines(): if "Connected" in line or "Disconne" in line: parts = line.split() name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4, ipv6): for adapter in interfaces: # 設定 IPv4 DNS cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" static {ipv4[0]} primary' subprocess.run(cmd_ipv4, shell=True) for dns in ipv4[1:]: cmd_add = f'netsh interface ip add dns name="{adapter}" {dns} index=2' subprocess.run(cmd_add, shell=True) # 設定 IPv6 DNS cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6[0]} primary' subprocess.run(cmd_ipv6, shell=True) for dns in ipv6[1:]: cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" {dns} index=2' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): for adapter in interfaces: # 設定 DNS 為自動獲取 cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" dhcp' subprocess.run(cmd_ipv4, shell=True) cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def load_config(): if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} with open(CONFIG_FILE, "r") as f: return json.load(f) def save_config(config): with open(CONFIG_FILE, "w") as f: json.dump(config, f) def toggle_ad_block(): config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到網路介面。") return if config.get("ad_block_enabled", False): # 禁用廣告阻擋,恢復預設 DNS reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: # 啟用廣告阻擋,設定廣告阻擋 DNS set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): if not is_admin(): messagebox.showerror("權限不足", "請以管理員身份運行此程序。") return root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()
管理員權限檢查:
is_admin()
函數檢查腳本是否以管理員身份運行。如果不是,顯示錯誤訊息並終止程序。取得網路介面:
netsh interface show interface
命令列出所有網路介面,並過濾出已連接的介面名稱。設定 DNS:
set_dns()
函數使用 netsh
命令將指定的 IPv4 和 IPv6 DNS 設定應用到所有網路介面。reset_dns()
函數將 DNS 設定恢復為自動獲取(DHCP)。配置管理:
ad_block_config.json
) 來儲存廣告阻擋的狀態 (ad_block_enabled
)。load_config()
和 save_config()
函數分別負責讀取和寫入配置文件。圖形介面 (GUI):
Tkinter
建立一個簡單的窗口,顯示當前的廣告阻擋狀態,並提供一個按鈕來切換狀態。toggle_ad_block()
函數會根據當前狀態啟用或禁用廣告阻擋,並更新配置文件和 GUI 顯示。狀態更新:
update_status()
函數會根據配置文件的內容設置狀態標籤,確保重啟後仍能反映當前狀態。安裝 Python: 確保已安裝 Python 3.x,並將其添加到系統的 PATH 環境變數中。
保存腳本:
將上述腳本保存為 ad_blocker.py
。
以管理員身份運行:
python ad_blocker.py
。操作 GUI:
自動啟動(可選): 若希望此腳本在每次 Windows 啟動時自動運行,可以將其添加到「啟動」文件夾,或使用任務排程器設置。
恢復原始 DNS: 如果您不確定原始的 DNS 設定,建議在第一次啟用廣告阻擋前手動記錄當前的 DNS 設定,或修改腳本以自動備份和恢復原始設定。
錯誤處理:
根據實際使用情況,可能需要添加更多的錯誤處理,例如處理 netsh
命令失敗的情況,或網路介面變更時的情況。
多語言支持: 根據用戶需求,可以擴展腳本以支持多種語言的界面。
希望這個腳本能滿足您的需求,並幫助您在 Windows 上有效地阻擋廣告!
要以管理員身份開 請開腳本時要求要管理員權限
請不要再有錯 思考多點 詳細一點
下面的範例程式碼是以 Windows 平台為目標,以 Python 撰寫的廣告阻擋腳本。該腳本在啟動時會自動判斷是否以管理員 (Administrator) 權限執行,如非管理員將嘗試以管理員身份重新啟動程式。程式利用 netsh
指令設定 DNS,以達到阻擋廣告的效果,同時提供簡易的圖形化介面 (GUI) 來切換廣告阻擋狀態。程式會將狀態資訊儲存在 JSON 檔案中,以便重啟後能保持先前的啟動或關閉狀態。
自動要求管理員權限:
若非以管理員身份執行,程式將嘗試透過 ShellExecute 提升權限並重新執行自己。
使用特定的 DNS 設定來阻擋廣告:
預設使用下列 DNS:
94.140.14.14
2a10:50c0::1
可在程式介面中開啟或關閉廣告阻擋:
按下按鈕即可切換狀態,無需手動修改系統設定。
狀態持久化:
使用 ad_block_config.json
檔案儲存當前狀態,以便下次程式重啟時能直接知道上次的狀態。
回復機制:
當您關閉廣告阻擋時,腳本會將 DNS 設定恢復為自動取得 (DHCP),以確保不影響正常網路使用。
netsh
指令來修改 DNS。請將下列程式碼儲存為 ad_blocker.py
,以便後續執行:
pythonimport os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json # 設定檔案名稱 CONFIG_FILE = "ad_block_config.json" # 廣告阻擋用的 DNS 設定 (可根據需要調整) AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] # 若未啟用廣告阻擋時,預設將 DNS 設定為自動取得 # (此處不預設特定 DNS,直接使用 DHCP) # 若您希望恢復特定的自訂 DNS,可在這裡增加預設的 DNS。 # 本程式將在關閉時以 DHCP 模式還原 DNS。 def run_as_admin(): """ 若目前非以管理員權限執行,嘗試以管理員身份重新執行本程式。 成功後程式會自行結束原進程並由新進程接管。 若使用者拒絕提升權限,將顯示錯誤訊息並終止程式。 """ try: # 在 Windows 上,IsUserAnAdmin() 可用來判斷是否為管理員 if ctypes.windll.shell32.IsUserAnAdmin(): return True else: # 若非管理員,嘗試以管理員模式重新啟動 params = " ".join(sys.argv[1:]) # ShellExecuteW執行提權 h_instance = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__ + " " + params, None, 1) if h_instance <= 32: # 使用者可能拒絕提權或發生錯誤 messagebox.showerror("錯誤", "需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: # 成功提權後,原程式結束,等待新程式執行 sys.exit(0) except Exception as e: messagebox.showerror("錯誤", f"嘗試提升權限時發生錯誤:{e}") sys.exit(1) def load_config(): """ 載入程式配置檔(JSON格式),用來記錄廣告阻擋的啟用狀態。 若檔案不存在,使用預設狀態。 """ if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except: # 若讀取失敗,回傳預設 return {"ad_block_enabled": False} def save_config(config): """ 將當前狀態寫回配置檔,以便下次程式重啟時仍保有狀態。 """ with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) def get_network_adapters(): """ 使用 netsh 指令取得目前系統的網路介面名稱清單。 僅處理「已連線」或「已斷線」的介面。 """ cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: messagebox.showerror("錯誤", "無法取得網路介面列表,請確認系統環境。") return [] interfaces = [] for line in result.stdout.splitlines(): if "Connected" in line or "Disconnected" in line or "連線" in line or "斷線" in line: parts = line.split() if len(parts) >= 4: name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): """ 為所有指定的網路介面設定靜態 DNS。 ipv4_list、ipv6_list為包含 DNS 伺服器的列表。 """ for adapter in interfaces: # 設定 IPv4 DNS (第一個為 Primary) if ipv4_list: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" static {ipv4_list[0]} primary' subprocess.run(cmd_ipv4, shell=True) for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ip add dns name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) # 設定 IPv6 DNS (第一個為 Primary) if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' subprocess.run(cmd_ipv6, shell=True) for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): """ 將所有指定的網路介面的 DNS 設定恢復為自動取得 (DHCP)。 """ for adapter in interfaces: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" dhcp' subprocess.run(cmd_ipv4, shell=True) cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def toggle_ad_block(): """ 切換廣告阻擋狀態。 若目前已啟用,則禁用並恢復為 DHCP。 若目前未啟用,則設定廣告阻擋 DNS。 """ config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return if config.get("ad_block_enabled", False): # 已啟用廣告阻擋,現在將其關閉並恢復為 DHCP reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: # 尚未啟用廣告阻擋,現在設定廣告阻擋 DNS set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): """ 根據配置檔更新介面上的狀態顯示。 """ config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): # 首先嘗試以管理員身份執行 if not run_as_admin(): # 若無法取得管理員權限,直接結束 return # 建立 Tkinter 視窗 root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()
安裝 Python:
確保系統已安裝 Python 3.x,並且已將 Python 加入系統路徑 (PATH)。
執行腳本:
在儲存 ad_blocker.py
的資料夾中,以滑鼠右鍵點選「在此處開啟命令提示字元」或 PowerShell(以系統管理員身份執行)。
執行指令:
python ad_blocker.py
若您直接執行而非以系統管理員身份啟動終端,程式將自動嘗試提升權限並重新啟動自己。如果使用者拒絕提權,程式將顯示錯誤訊息並結束。
切換狀態:
程式啟動後會顯示目前的廣告阻擋狀態 (啟用或禁用)。按下「切換廣告阻擋」按鈕即可在啟用和禁用之間切換。
重啟程式維持狀態:
當您關閉程式後,再次啟動時,程式會讀取 ad_block_config.json
狀態檔,並自動顯示先前狀態。
此程式碼已盡力考慮常見問題並增加詳細註解,希望能滿足您的需求並在使用時不再出現錯誤。
閃退
下面提供一個更為穩定的範例,解決可能的「閃退」問題。常見原因可能是:
未以管理員權限執行:
原本程式在檢測到非管理員後,會嘗試以管理員模式重新啟動。如果使用者拒絕或者出現路徑問題,可能導致程式立即結束(看似閃退)。
改善方法:在要求管理員權限時,不使用 messagebox
,直接使用印出錯誤並 sys.exit()
,確保程式不會在沒有 Tkinter 視窗的狀態下嘗試顯示對話框,造成意料外行為。
__file__
變數問題:
部分執行環境中 __file__
可能不存在,我們改用 os.path.abspath(sys.argv[0])
獲取程式自身路徑,以確保能正確提權。
在 run_as_admin 前就顯示對話框:
為避免在提升權限過程中使用 Tkinter 的對話框,run_as_admin 中若提升失敗,改以 print
顯示錯誤訊息並 sys.exit()
結束程式。
當提權成功後,再開始 Tkinter 界面相關動作。
下方程式碼經過調整,可降低「閃退」發生的機率。
pythonimport os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json CONFIG_FILE = "ad_block_config.json" AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] def is_admin(): # 使用 IsUserAnAdmin 檢查是否有管理員權限 try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def run_as_admin(): """ 若目前非管理員,嘗試以系統管理員身份重新啟動本程式。 如果使用者拒絕或發生錯誤,將直接結束程式。 """ if is_admin(): return True # 準備以管理員身份重新執行本檔案 script = os.path.abspath(sys.argv[0]) params = " ".join(sys.argv[1:]) h_instance = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{script}" {params}', None, 1) if h_instance <= 32: # 提權失敗或使用者拒絕 print("需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: # 成功提權後,原程式結束,等待新程式執行 sys.exit(0) def load_config(): if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except: return {"ad_block_enabled": False} def save_config(config): with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) def get_network_adapters(): cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: return [] interfaces = [] for line in result.stdout.splitlines(): # 尋找線上或離線的介面行 # "Connected" / "Disconnected" 或本地化語言表述 if ("Connected" in line or "Disconnected" in line or "連線" in line or "斷線" in line): parts = line.split() if len(parts) >= 4: name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): for adapter in interfaces: if ipv4_list: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" static {ipv4_list[0]} primary' subprocess.run(cmd_ipv4, shell=True) for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ip add dns name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' subprocess.run(cmd_ipv6, shell=True) for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): for adapter in interfaces: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" dhcp' subprocess.run(cmd_ipv4, shell=True) cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def toggle_ad_block(): config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return if config.get("ad_block_enabled", False): # 已啟用,現在停用 reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: # 未啟用,現在啟用 set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): # 確保以管理員身份執行 run_as_admin() # 到此程式若能繼續,表示已獲得管理員權限 root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()
ad_blocker.py
所在的資料夾中,開啟終端(建議一開始就以系統管理員身份開啟命令提示字元或 PowerShell)。python ad_blocker.py
。此版本程式碼在自動提權流程中避免使用 tkinter 的對話框,以減少在沒有 GUI 主視窗的情況下呼叫 messagebox 而導致的潛在問題。如果仍有「閃退」情況,請檢查:
若有需要,可在程式中增加 print()
調試資訊,以確保流程正常。
它的輸出
設定的 DNS 伺服器不正確或不存在。
設定的 DNS 伺服器不正確或不存在。
設定的 DNS 伺服器不正確或不存在。
設定的 DNS 伺服器不正確或不存在。
您看到「設定的 DNS 伺服器不正確或不存在。」的錯誤訊息,多半是由於 netsh
指令使用錯誤、介面名稱不符或環境不支援造成。以下是可能原因和改進方法:
指令使用方式:
舊版做法常用 netsh interface ip set dns
,但在較新環境中建議分別使用:
netsh interface ipv4 set dnsservers
netsh interface ipv6 set dnsservers
如果繼續使用 netsh interface ip set dns
可能會在部分環境出現錯誤。
網路介面名稱:
程式會從 netsh interface show interface
中取得介面名稱。如果介面名稱中有空格、特殊字元,或者該介面實際上不支援設定 DNS,可能會導致錯誤。
建議印出介面列表檢查,或手動指定您確定可用的介面名稱測試。
IPv6 問題:
如果系統未啟用 IPv6 或介面不支援 IPv6,設定 IPv6 DNS 可能失敗。若不需要 IPv6,可考慮直接略過設定 IPv6 DNS。
DNS Server 位址有誤:
雖然像 94.140.14.14
及 2a10:50c0::1
是知名的 AdGuard DNS,但請確定系統能辨識這些位址格式。
您可嘗試以手動在命令列中輸入指令來測試,例如:
cmdnetsh interface ipv4 set dnsservers name="Ethernet" static 94.140.14.14 primary
看看是否能正常執行。如果不能,表示該 DNS 位址或介面名稱有問題。
以下是改良後的程式碼範例,將 IPv4 和 IPv6 設定分別以較新命令處理,並在設定前列印介面名稱供檢查。若您只需要 IPv4,可註解掉 IPv6 部分。
pythonimport os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json CONFIG_FILE = "ad_block_config.json" # 廣告阻擋DNS AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] # 可加其他,如 ["94.140.14.14", "8.8.8.8"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def run_as_admin(): if is_admin(): return True script = os.path.abspath(sys.argv[0]) params = " ".join(sys.argv[1:]) h_instance = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{script}" {params}', None, 1) if h_instance <= 32: print("需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: sys.exit(0) def load_config(): if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except: return {"ad_block_enabled": False} def save_config(config): with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) def get_network_adapters(): cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: return [] interfaces = [] for line in result.stdout.splitlines(): # 篩出正常的介面行 # "Connected"/"Disconnected" 或本地語言 if ("Connected" in line or "Disconnected" in line or "連線" in line or "斷線" in line): parts = line.split() if len(parts) >= 4: name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): for adapter in interfaces: # 設定 IPv4 DNS if ipv4_list: # 清空 IPv4 DNS 設定,再用 static 設第一個為 primary cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" static {ipv4_list[0]} primary' subprocess.run(cmd_ipv4, shell=True) # 加入其他 IPv4 DNS for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ipv4 add dnsservers name="{adapter}" address={dns} index={i}' subprocess.run(cmd_add, shell=True) # 設定 IPv6 DNS if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' subprocess.run(cmd_ipv6, shell=True) for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" address={dns} index={i}' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): for adapter in interfaces: # 將 IPv4 DNS 設為自動 cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv4, shell=True) # 將 IPv6 DNS 設為自動 cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def toggle_ad_block(): config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return # 列印介面名稱,供偵錯使用 print("發現的網路介面:", interfaces) if config.get("ad_block_enabled", False): reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): run_as_admin() root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()
手動測試指令:
在 CMD(以系統管理員身份)輸入:
cmdnetsh interface ipv4 set dnsservers name="Ethernet" static 94.140.14.14 primary
如果此處就出錯,請檢查網路介面名稱是否正確 ("Ethernet"
是範例,請以程式輸出或您實際介面為準)。
確認介面名稱正確性:
在程式跑出「發現的網路介面: ...」後,檢查輸出清單內的名稱是否與系統在「控制台 -> 網路連線」中顯示的名稱相符。如不相符或有奇怪字元,請嘗試該名稱能否手動執行 netsh
指令。
如果不需要 IPv6:
如果您不需要設定 IPv6 DNS,可將 set_dns
函數中有關 IPv6 的設定程式碼註解掉或移除,避免不必要的錯誤。
透過以上修正與檢查,問題「設定的 DNS 伺服器不正確或不存在。」應該能獲得改善。
成功了 但我想你更改它檢測是否已啟用不用建立一個文件
很高興聽到腳本已經成功運行!接下來,我們將改進腳本,使其在啟動時不再依賴外部配置文件來檢測廣告阻擋的狀態,而是直接通過檢查當前網路介面的 DNS 設定來判斷是否已啟用廣告阻擋。這樣可以簡化流程,減少潛在的錯誤來源。
ad_block_config.json
來保存狀態。請將下列程式碼保存為 ad_blocker.py
,並以管理員身份運行:
pythonimport os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json # 廣告阻擋DNS設定 AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] def is_admin(): """ 檢查程式是否以管理員身份執行。 """ try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def run_as_admin(): """ 若非以管理員身份執行,則嘗試以管理員身份重新啟動程式。 """ if is_admin(): return True # 獲取程式絕對路徑 script = os.path.abspath(sys.argv[0]) params = " ".join([f'"{arg}"' for arg in sys.argv[1:]]) try: # 使用 ShellExecuteW 提權 h_instance = ctypes.windll.shell32.ShellExecuteW( None, "runas", sys.executable, f'"{script}" {params}', None, 1) if h_instance <= 32: # 提權失敗或使用者拒絕 messagebox.showerror("錯誤", "需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: # 成功提權後,原程式結束,等待新程式執行 sys.exit(0) except Exception as e: messagebox.showerror("錯誤", f"嘗試提升權限時發生錯誤:{e}") sys.exit(1) def get_network_adapters(): """ 使用 netsh 指令取得目前系統的網路介面名稱清單。 僅處理「已連線」或「已斷線」的介面。 """ cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: messagebox.showerror("錯誤", "無法取得網路介面列表,請確認系統環境。") return [] interfaces = [] for line in result.stdout.splitlines(): # 篩選出包含 "Connected" 或 "Disconnected" 的行,考慮中英文 if any(status in line for status in ["Connected", "Disconnected", "連線", "斷線"]): parts = line.split() if len(parts) >= 4: # 介面名稱可能包含空格,因此需合併後面的部分 name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): """ 為所有指定的網路介面設定靜態 DNS。 ipv4_list、ipv6_list 為包含 DNS 伺服器的列表。 """ for adapter in interfaces: # 設定 IPv4 DNS if ipv4_list: # 設定第一個 DNS 為 primary cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" static {ipv4_list[0]} primary' result_ipv4 = subprocess.run(cmd_ipv4, shell=True, capture_output=True, text=True) if result_ipv4.returncode != 0: print(f"設定 IPv4 DNS 失敗: {result_ipv4.stderr}") # 設定其他 DNS for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ipv4 add dnsservers name="{adapter}" address={dns} index={i}' result_add = subprocess.run(cmd_add, shell=True, capture_output=True, text=True) if result_add.returncode != 0: print(f"添加 IPv4 DNS 失敗: {result_add.stderr}") # 設定 IPv6 DNS if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' result_ipv6 = subprocess.run(cmd_ipv6, shell=True, capture_output=True, text=True) if result_ipv6.returncode != 0: print(f"設定 IPv6 DNS 失敗: {result_ipv6.stderr}") for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" address={dns} index={i}' result_add = subprocess.run(cmd_add, shell=True, capture_output=True, text=True) if result_add.returncode != 0: print(f"添加 IPv6 DNS 失敗: {result_add.stderr}") def reset_dns(interfaces): """ 將所有指定的網路介面的 DNS 設定恢復為自動取得 (DHCP)。 """ for adapter in interfaces: # 重置 IPv4 DNS cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" source=dhcp' result_ipv4 = subprocess.run(cmd_ipv4, shell=True, capture_output=True, text=True) if result_ipv4.returncode != 0: print(f"重置 IPv4 DNS 失敗: {result_ipv4.stderr}") # 重置 IPv6 DNS cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' result_ipv6 = subprocess.run(cmd_ipv6, shell=True, capture_output=True, text=True) if result_ipv6.returncode != 0: print(f"重置 IPv6 DNS 失敗: {result_ipv6.stderr}") def is_ad_block_enabled(interfaces): """ 檢查是否已啟用廣告阻擋。 透過檢查所有網路介面的 DNS 是否匹配廣告阻擋 DNS 來判斷。 如果所有介面均設置為廣告阻擋 DNS,則返回 True,否則返回 False。 """ for adapter in interfaces: # 檢查 IPv4 cmd_ipv4 = f'netsh interface ipv4 show dnsservers name="{adapter}"' result_ipv4 = subprocess.run(cmd_ipv4, shell=True, capture_output=True, text=True) if result_ipv4.returncode != 0: print(f"檢查 IPv4 DNS 失敗: {result_ipv4.stderr}") return False dns_ipv4 = [] for line in result_ipv4.stdout.splitlines(): line = line.strip() if line and not line.startswith("DNS servers configured"): dns_ipv4.append(line.split()[0]) # 檢查 IPv6 cmd_ipv6 = f'netsh interface ipv6 show dnsservers name="{adapter}"' result_ipv6 = subprocess.run(cmd_ipv6, shell=True, capture_output=True, text=True) if result_ipv6.returncode != 0: print(f"檢查 IPv6 DNS 失敗: {result_ipv6.stderr}") return False dns_ipv6 = [] for line in result_ipv6.stdout.splitlines(): line = line.strip() if line and not line.startswith("DNS servers configured"): dns_ipv6.append(line.split()[0]) # 比較 DNS 是否匹配廣告阻擋 DNS if not (dns_ipv4 and dns_ipv4[0] in AD_BLOCK_DNS_IPV4): return False if not (dns_ipv6 and dns_ipv6[0] in AD_BLOCK_DNS_IPV6): return False return True def toggle_ad_block(): """ 切換廣告阻擋狀態。 如果已啟用,則禁用;如果未啟用,則啟用。 """ interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return # 檢查當前狀態 enabled = is_ad_block_enabled(interfaces) if enabled: # 已啟用,現在禁用 reset_dns(interfaces) status_var.set("廣告阻擋已禁用") else: # 未啟用,現在啟用 set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) status_var.set("廣告阻擋已啟用") def update_status(): """ 根據當前 DNS 設定更新介面上的狀態顯示。 """ interfaces = get_network_adapters() if not interfaces: status_var.set("無法檢測狀態") return enabled = is_ad_block_enabled(interfaces) if enabled: status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): # 首先,確保以管理員身份運行 if not is_admin(): run_as_admin() # 如果已經是管理員,繼續執行 root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) # 增加一個刷新按鈕,以便手動刷新狀態 refresh_button = tk.Button(root, text="刷新狀態", command=update_status, width=20, font=("Arial", 10)) refresh_button.pack(pady=5) root.mainloop() if __name__ == "__main__": main()
管理員權限檢查與提權:
is_admin()
函數檢查程式是否以管理員身份運行。run_as_admin()
函數嘗試以管理員身份重新啟動程式。若使用者拒絕提權,則顯示錯誤訊息並退出。動態檢測廣告阻擋狀態:
is_ad_block_enabled()
函數通過檢查所有網路介面的 DNS 設定來判斷廣告阻擋是否啟用。只有當所有介面的主要 DNS 設定與廣告阻擋 DNS 匹配時,才認為已啟用。設定與重置 DNS:
set_dns()
函數為所有指定的網路介面設置靜態 DNS(包括 IPv4 和 IPv6)。reset_dns()
函數將所有指定的網路介面的 DNS 設定恢復為自動取得(DHCP)。GUI 介面:
Tkinter
建立一個簡單的窗口,顯示當前廣告阻擋的狀態,並提供一個按鈕來切換狀態。錯誤處理與日誌:
netsh
命令執行失敗時,會在控制台輸出錯誤訊息,便於調試。安裝 Python:
保存並執行腳本:
ad_blocker.py
。ad_blocker.py
,選擇「以管理員身份運行」。cmdpython ad_blocker.py
操作 GUI:
手動測試 DNS 設定:
cmdnetsh interface ipv4 set dnsservers name="Ethernet" static 94.140.14.14 primary netsh interface ipv6 set dnsservers name="Ethernet" static 2a10:50c0::1 primary
"Ethernet"
為您的實際網路介面名稱。檢查介面名稱:
get_network_adapters()
函數正確檢測到所有有效的網路介面。print("發現的網路介面:", interfaces)
來調試介面名稱。處理特殊網路介面:
自動啟動:
日誌記錄:
多語言支持:
更靈活的 DNS 設定:
希望這個改進版的腳本能夠更好地滿足您的需求,並在使用過程中更加穩定與可靠。如果有任何其他問題或需要進一步的功能擴展,請隨時告知!