一个完整的小项目案例,涉及到项目的规划,模块的设计功能的衔接等。
以下是一个基于分层架构和模块化设计的项目规划,使用Tkinter作为GUI框架,Playwright进行浏览器操作,SQLite作为数据库:
📂 项目结构
```
web_checker/
├── __main__.py # 程序入口
├── config.py # 配置管理
├── gui/ # 图形界面模块
│ ├── __init__.py
│ └── main_window.py
├── services/ # 业务逻辑
│ ├── __init__.py
│ ├── website_service.py
│ └── database.py
├── browser/ # 浏览器控制
│ ├── __init__.py
│ └── controller.py
└── models/ # 数据模型
├── __init__.py
└── website.py
```
1. 📝 config.py 配置文件
```python
import pathlib
BASE_DIR = pathlib.Path(__file__).parent.parent
class Config:
DB_PATH = BASE_DIR / "data" / "web_checker.db"
BROWSER_TYPE = "chromium" # chromium, firefox, webkit
HEADLESS = False
TIMEOUT = 30000 # 30秒超时
```
2. 📦 models/website.py 数据模型
```python
from datetime import datetime
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class WebsiteVisit(Base):
__tablename__ = "website_visits"
id = Column(Integer, primary_key=True)
url = Column(String(500), nullable=False)
success = Column(Boolean, nullable=False)
timestamp = Column(DateTime, default=datetime.now)
error_message = Column(String(500))
```
3. 💾 services/database.py 数据库管理
```python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from config import Config
from models.website import Base, WebsiteVisit
class DatabaseManager:
def __init__(self):
self.engine = create_engine(f"sqlite:///{Config.DB_PATH}")
Base.metadata.create_all(self.engine)
self.Session = sessionmaker(bind=self.engine)
def add_visit_record(self, url, success, error=None):
with self.Session() as session:
record = WebsiteVisit(
url=url,
success=success,
error_message=error
)
session.add(record)
session.commit()
```
4. 🌐 browser/controller.py 浏览器控制器
```python
from playwright.sync_api import sync_playwright
from config import Config
class BrowserController:
def __init__(self):
self.playwright = sync_playwright().start()
self.browser = getattr(self.playwright, Config.BROWSER_TYPE).launch(
headless=Config.HEADLESS
)
self.context = self.browser.new_context()
def visit_website(self, url):
page = self.context.new_page()
try:
response = page.goto(url, timeout=Config.TIMEOUT)
return response.ok
except Exception as e:
return False, str(e)
finally:
page.close()
def __del__(self):
self.context.close()
self.browser.close()
self.playwright.stop()
```
5. 🧠 services/website_service.py 业务逻辑
```python
from threading import Thread
from browser.controller import BrowserController
from services.database import DatabaseManager
class WebsiteService:
def __init__(self):
self.db = DatabaseManager()
self.browser_controller = BrowserController()
def open_website(self, url, callback=None):
def async_task():
success, error = True, None
try:
result = self.browser_controller.visit_website(url)
success = result if isinstance(result, bool) else result.ok
except Exception as e:
success = False
error = str(e)
self.db.add_visit_record(url, success, error)
if callback:
callback(success, error)
Thread(target=async_task).start()
```
6. 🖥️ gui/main_window.py GUI界面
```python
import tkinter as tk
from tkinter import ttk
from services.website_service import WebsiteService
class MainWindow(tk.Tk):
def __init__(self, website_list):
super().__init__()
self.title("Website Checker")
self.service = WebsiteService()
self._create_widgets(website_list)
def _create_widgets(self, website_list):
self.tree = ttk.Treeview(self, columns=("checkbox", "url"), show="headings")
self.tree.heading("checkbox", text="Select")
self.tree.heading("url", text="URL")
for url in website_list:
self._add_website_row(url)
self.tree.pack(fill=tk.BOTH, expand=True)
# 绑定复选框事件
self.tree.bind("<Button-1>", self._on_checkbox_click)
def _add_website_row(self, url):
item = self.tree.insert("", tk.END, values=("☐", url))
self.tree.set(item, "checkbox", "☐")
def _on_checkbox_click(self, event):
region = self.tree.identify("region", event.x, event.y)
if region != "cell":
return
column = self.tree.identify_column(event.x)
item = self.tree.identify_row(event.y)
if column == "#1": # 复选框列
current_val = self.tree.set(item, "checkbox")
new_val = "☑" if current_val == "☐" else "☐"
self.tree.set(item, "checkbox", new_val)
if new_val == "☑":
url = self.tree.set(item, "url")
self.service.open_website(
url,
lambda success, error: self._update_status(item, success, error)
)
def _update_status(self, item, success, error):
status = "✓" if success else f"✗ ({error[:20]})"
self.tree.set(item, "status", status)
```
7. 🚀 __main__.py 程序入口
```python
from gui.main_window import MainWindow
def load_websites():
# 这里可以替换为从文件读取网站列表
return [
"https://www.google.com",
"https://www.github.com",
"https://www.example.com"
]
if __name__ == "__main__":
websites = load_websites()
app = MainWindow(websites)
app.mainloop()
```
🔑 关键设计要点:
1. **多线程架构**:
- 使用独立线程处理浏览器操作,避免阻塞GUI主线程
- 通过回调机制更新界面状态
2. **数据流向**:
```
GUI事件 → 业务服务层 → 浏览器控制 → 数据库记录 → 界面反馈
```
3. **扩展性设计**:
- 可在config.py中添加浏览器参数配置
- 数据库层支持切换不同SQL方言
- 状态反馈列可扩展显示更多详细信息
4. **错误处理**:
- 捕获浏览器操作异常
- 记录详细的错误信息到数据库
- 界面显示简明的错误提示
5. **界面优化建议**:
- 添加加载状态指示器
- 增加右键菜单管理网站列表
- 支持双击打开详细访问记录
🛠️ 扩展建议:
1. **添加日志系统**:
```python
# utils/logger.py
import logging
from config import Config
def setup_logger():
logging.basicConfig(
filename=Config.LOG_PATH,
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
```
2. **支持异步Playwright**:
```python
# 修改browser/controller.py使用async API
import asyncio
from playwright.async_api import async_playwright
class AsyncBrowserController:
async def visit_website(self, url):
async with async_playwright() as p:
browser = await getattr(p, Config.BROWSER_TYPE).launch()
# ...
```
3. **添加数据看板**:
```python
# gui/dashboard.py
class Dashboard(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self._create_charts()
def _create_charts(self):
# 使用matplotlib嵌入图表
pass
```
这种架构可以实现:
- 修改浏览器类型只需调整config.py
- 更换数据库只需修改database.py
- 添加新功能模块不会影响核心逻辑
- 各组件可独立测试(浏览器操作、数据库访问等)