大家好!今天我想分享一个我最近开发的小工具——Web Mirror Pro(网页模仿器) 。这是一个能让你输入网址,一键克隆整个网页的工具,生成可离线浏览的HTML文件,包含所有图片、CSS和JS资源。无论你是前端开发者想学习网站结构,还是需要备份网页内容,这个工具都能帮上大忙!
🌟 为什么我要开发这个工具?
在日常工作中,我经常遇到这样的需求:
- 想学习优秀网站的设计,但需要离线查看
- 需要备份某个网页,但担心原网站关闭
- 作为建站参考,快速获取网页结构
市面上虽然有一些网页保存工具,但要么功能单一(只保存HTML),要么操作复杂。所以我决定自己动手,用Python开发一个简单易用、功能完整的网页克隆工具。
💡 核心功能
- ✅ 输入网址,一键克隆网页(含图片/CSS/JS)
- ✅ 生成可完全离线浏览的HTML文件
- ✅ 桌面GUI界面,无需浏览器
- ✅ 可打包为独立.exe文件,无需安装Python
- ✅ 自动替换外链为本地资源路径
- ✅ 支持递归下载(CSS中的背景图等)
🧩 代码实现与关键函数解析
下面我将重点解析几个核心函数,解释为什么这样设计以及解决了什么问题。
1. clone_page_with_deep_assets - 深度克隆函数
def clone_page_with_deep_assets(url, save_path, max_depth=1, current_depth=0, log_callback=None, downloaded_urls=None):
if downloaded_urls is None:
downloaded_urls = set()
if current_depth > max_depth:
return True
# ...(中间代码省略)
# 下载图片
for img in soup.find_all("img", src=True):
src = img['src']
if src.lower().startswith(''):
continue # 跳过 base64
abs_url = urljoin(url, src)
if abs_url in downloaded_urls:
continue
downloaded_urls.add(abs_url)
local_file = download_resource(session, src, url, assets_dir)
if local_file:
img['src'] = f"assets/{local_file}"
log_callback(f"{' ' * current_depth}🖼️ 图片: {src} → {local_file}")
# 处理 CSS 及其背景图
for link in soup.find_all("link", rel="stylesheet", href=True):
href = link['href']
abs_url = urljoin(url, href)
if abs_url in downloaded_urls:
continue
downloaded_urls.add(abs_url)
local_file = download_resource(session, href, url, assets_dir)
if local_file:
link['href'] = f"assets/{local_file}"
log_callback(f"{' ' * current_depth}🎨 样式: {href} → {local_file}")
# 解析 CSS 中的 url(...)
css_path = os.path.join(assets_dir, local_file)
try:
with open(css_path, 'r', encoding='utf-8', errors='ignore') as f:
text = f.read()
urls = re.findall(r'url\([\'"]?(.*?)[\'"]?\)', text)
for bg_url in urls:
full_bg = urljoin(abs_url, bg_url.strip())
if full_bg in downloaded_urls:
continue
bg_file = download_resource(session, full_bg, url, assets_dir)
if bg_file:
log_callback(f"{' ' * (current_depth+1)}🖼️ CSS 图: {bg_url} → {bg_file}")
except:
pass
# ...(后续代码省略)
为什么需要这个函数?
大多数网页克隆工具只下载HTML和直接引用的资源,但CSS中可能包含背景图,而这些图片不会被普通爬虫捕获。这个函数通过递归方式:
- 深度优先遍历:设置
max_depth参数控制递归深度,避免无限爬取 - 自动识别资源:不仅下载HTML中的图片,还解析CSS文件中的
url(...)引用 - 避免重复下载:使用
downloaded_urls集合记录已下载URL,防止循环和重复
解决了什么问题?
- 游戏网站、设计类网站常有大量CSS背景图,普通工具无法完整保存
- 没有递归机制,生成的网页会缺少关键视觉元素
- 通过限制深度,平衡了完整性和效率
2. download_resource - 资源下载函数
def download_resource(session, url, base_url, save_dir):
try:
abs_url = urljoin(base_url, url)
parsed = urlparse(abs_url)
if not parsed.path or parsed.path == '/':
return None
# 解码 URL(处理 %20 等)
filename = os.path.basename(unquote(parsed.path))
if not filename or '.' not in filename:
try:
head = session.head(abs_url, timeout=5)
content_type = head.headers.get('content-type', '').lower()
if 'jpeg' in content_type or 'jpg' in content_type:
ext = '.jpg'
elif 'png' in content_type:
ext = '.png'
elif 'gif' in content_type:
ext = '.gif'
elif 'svg' in content_type:
ext = '.svg'
else:
ext = '.bin'
filename = f"res_{hash(abs_url) % 10000}{ext}"
except:
filename = f"res_{hash(abs_url) % 10000}.bin"
filename = re.sub(r'[^\w\.\-]', '_', filename)
filepath = os.path.join(save_dir, filename)
if os.path.exists(filepath):
return filename # 已存在则跳过
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
response = session.get(abs_url, headers=headers, timeout=10, stream=True)
response.raise_for_status()
# 检查文件大小(超过 10MB 跳过)
total_size = int(response.headers.get('content-length', 0))
if total_size > 10 * 1024 * 1024: # 10MB
return None
downloaded_size = 0
with open(filepath, 'wb') as f:
for chunk in response.iter_content(1024):
if chunk:
f.write(chunk)
downloaded_size += len(chunk)
if downloaded_size > 10 * 1024 * 1024: # 实时检查
os.remove(filepath)
return None
return filename
except Exception as e:
return None
为什么需要这个函数?
网页资源千奇百怪,直接下载可能会遇到:
- 文件名包含特殊字符(如空格、中文)
- 没有文件扩展名
- 超大文件导致程序卡死
- 重复下载同一资源
解决了什么问题?
-
智能文件名处理:
- 使用
unquote解码URL,处理%20等编码 - 通过
Content-Type推断文件扩展名 - 清理非法字符(
re.sub(r'[^\w.-]', '_', filename))
- 使用
-
大文件保护机制:
- 检查
Content-Length头部 - 流式下载并实时检查大小
- 超过10MB自动跳过,避免卡顿
- 检查
-
重复下载优化:
- 检查文件是否存在
- 避免多次下载同一资源
3. GUI界面设计 - 为什么选择ttkbootstrap
GUI主程序部分
class WebMirrorApp:
def __init__(self, root):
self.root = root
self.root.title("🚀 自研快速仿站神器 - 输入网址一键生成HTML")
self.root.geometry("800x600")
self.root.resizable(True, True)
#标题区
title_frame = ttk.Frame(root)
title_frame.pack(pady=15, fill=X, padx=20)
ttk.Label(
title_frame,
text="🌐 自研快速仿站神器",
font=("微软雅黑", 16, "bold"),
bootstyle="primary"
).pack()
# ...(后续代码省略)
为什么使用ttkbootstrap?
- 美观现代的界面:原生tkinter界面太简陋,而ttkbootstrap提供多种主题(cosmo, darkly, flatly等),让工具看起来更专业
- 跨平台一致性:确保在Windows/Mac/Linux上都有良好的显示效果
- 简化开发:提供圆角按钮、颜色主题等现代UI元素,无需手动绘制
解决了什么问题?
- 专业外观提升用户体验和信任度
- 让非技术用户也能轻松操作
- 日志区域使用ScrolledText,方便查看详细过程
🚀 如何使用这个工具?
见资源下载