告别404!用Python+Selenium把整个网站打包成单个MHTML文件(附完整代码)
·
用Python+Selenium实现整站MHTML归档:从技术原理到完整解决方案
每次遇到心仪的博客突然关闭,或是重要的技术文档突然消失,那种"404 Not Found"的挫败感总是让人抓狂。作为经常需要查阅资料的技术从业者,我逐渐意识到网页归档的重要性——直到我发现MHTML这个神奇的格式。
1. 为什么MHTML是网页归档的最佳选择?
在尝试过各种网页保存方式后,我发现MHTML(MIME HTML)格式完美解决了传统方法的三大痛点:
- 完整性保留 :将HTML、CSS、JavaScript和图片等所有资源打包成单一文件
- 样式还原 :无需网络连接也能完美呈现原始页面布局
- 管理便捷 :一个网页对应一个.mhtml文件,避免杂乱的外部依赖
与普通HTML保存方式相比,MHTML的优势显而易见:
| 特性 | HTML保存 | MHTML保存 |
|---|---|---|
| 外部资源 | 需要额外文件夹 | 全部内嵌 |
| 样式还原 | 可能错乱 | 完美保留 |
| 文件数量 | 多个文件 | 单一文件 |
| 移动分享 | 需要打包 | 直接发送 |
提示:MHTML是W3C标准格式,被主流浏览器广泛支持,包括Chrome、Edge和Firefox。
2. 环境准备与核心工具链
2.1 基础环境配置
开始之前,确保你的开发环境已安装以下组件:
# 推荐使用Python 3.8+
python --version
# 安装必要库
pip install selenium beautifulsoup4 tqdm requests
2.2 ChromeDriver的正确配置
由于我们要利用Chrome的DevTools Protocol生成MHTML,需要特别注意浏览器驱动的匹配:
- 查看已安装的Chrome版本
- 从 官方仓库 下载对应版本的ChromeDriver
- 将可执行文件放在系统PATH或项目目录下
from selenium import webdriver
# 检查驱动是否可用
try:
driver = webdriver.Chrome()
print("ChromeDriver配置成功!")
driver.quit()
except Exception as e:
print(f"驱动配置错误:{str(e)}")
3. 整站爬取的核心算法设计
3.1 递归链接发现机制
我们的爬虫需要智能地发现站内链接,同时避免陷入无限循环或爬取外部资源。以下是优化后的链接处理逻辑:
def is_valid_link(url, base_domain):
"""判断是否为有效的站内链接"""
if url.startswith('mailto:') or url.startswith('javascript:'):
return False
if not url.startswith(base_domain) and not url.startswith('/'):
return False
return True
def normalize_url(current_url, link, base_domain):
"""规范化URL格式"""
if link.startswith('/'):
return f"{base_domain}{link}"
if not link.startswith('http'):
return f"{current_url}/{link}"
return link
3.2 高效去重与黑名单管理
使用Python的set()实现O(1)复杂度的去重检查,并添加常见静态资源扩展名到黑名单:
BLACKLIST_EXT = {'.pdf', '.jpg', '.png', '.zip', '.exe', '.mp4'}
def should_skip(url):
return any(url.endswith(ext) for ext in BLACKLIST_EXT) or '#' in url
4. 完整MHTML归档解决方案实现
4.1 主程序架构设计
我们的解决方案分为三个核心模块:
- 链接发现引擎 :递归爬取全站有效URL
- MHTML生成器 :通过CDP协议保存完美副本
- 目录管理器 :按原始URL结构组织文件
class MHTMLArchiver:
def __init__(self, base_url):
self.base_url = base_url
self.visited = set()
self.todo = {base_url}
self.driver = self._init_driver()
def _init_driver(self):
options = webdriver.ChromeOptions()
options.add_argument('--headless')
return webdriver.Chrome(options=options)
def crawl(self):
while self.todo:
url = self.todo.pop()
self._process_page(url)
def _process_page(self, url):
# 实现页面处理和链接发现
pass
def save_as_mhtml(self, url):
# 实现MHTML保存逻辑
pass
4.2 处理动态内容的进阶技巧
现代网站大量使用AJAX和懒加载,我们需要确保内容完全加载后再保存:
def wait_for_lazy_load(driver, timeout=30):
WebDriverWait(driver, timeout).until(
lambda d: d.execute_script(
"return document.readyState === 'complete' && "
"typeof jQuery !== 'undefined' ? jQuery.active === 0 : true"
)
)
def save_with_retry(driver, url, max_retries=3):
for attempt in range(max_retries):
try:
driver.get(url)
wait_for_lazy_load(driver)
return driver.execute_cdp_cmd('Page.captureSnapshot', {})
except TimeoutException:
if attempt == max_retries - 1:
raise
5. 实战优化与异常处理
5.1 常见问题解决方案
在实际使用中,我总结了几个典型问题的应对策略:
- 登录限制 :使用Selenium模拟登录后再爬取
- 反爬机制 :合理设置请求间隔,添加User-Agent
- 内存泄漏 :定期重启浏览器实例
def safe_crawl(archiver, max_pages=100, restart_interval=20):
for i in range(max_pages):
try:
archiver.crawl()
if i % restart_interval == 0:
archiver.driver.quit()
archiver.driver = archiver._init_driver()
except Exception as e:
print(f"处理失败: {str(e)}")
archiver.driver = archiver._init_driver()
5.2 性能优化技巧
对于大型网站,这些优化可以显著提升效率:
- 并行处理 :使用多线程处理不同页面
- 增量归档 :记录已处理URL,支持断点续传
- 智能节流 :根据响应时间动态调整请求频率
from concurrent.futures import ThreadPoolExecutor
def parallel_save(archiver, workers=4):
with ThreadPoolExecutor(max_workers=workers) as executor:
while archiver.todo:
batch = [archiver.todo.pop() for _ in range(min(10, len(archiver.todo)))]
executor.map(archiver.save_as_mhtml, batch)
6. 完整代码实现与使用指南
6.1 开箱即用的归档脚本
以下是整合所有优化后的完整实现:
# mhtml_archiver.py
import os
import time
from urllib.parse import urlparse
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from concurrent.futures import ThreadPoolExecutor
import tqdm
class MHTMLArchiver:
# 包含之前讨论的所有方法...
def run(self, output_dir='archive', max_workers=4):
os.makedirs(output_dir, exist_ok=True)
self.output_dir = output_dir
self.crawl()
self.parallel_save(max_workers)
if __name__ == '__main__':
archiver = MHTMLArchiver('https://example.com')
archiver.run()
6.2 使用示例与参数说明
典型使用场景配置:
# 基本用法
python mhtml_archiver.py https://blog.example.com
# 高级选项
python mhtml_archiver.py \
--output my_archive \
--workers 8 \
--timeout 60 \
https://docs.example.com
7. 应用场景扩展与进阶思路
7.1 定期自动归档方案
结合crontab实现定时归档:
# 每天凌晨2点执行归档
0 2 * * * /usr/bin/python3 /path/to/mhtml_archiver.py https://important.site >> /var/log/archiver.log 2>&1
7.2 与企业知识管理系统集成
将归档结果自动同步到Notion或Confluence:
def upload_to_notion(mhtml_file, notion_api_key, database_id):
# 实现Notion API上传逻辑
pass
在实际项目中,我发现这套方案特别适合保存技术文档、产品手册等关键资源。曾经有个重要API文档突然改版,幸好我有之前的MHTML归档,才避免了团队的知识断层。
更多推荐


所有评论(0)