上个月我在处理一项稍微有点繁琐的任务:需要从几个高校的招生信息门户网上,把历年的历史录取数据和页面存档扒下来。
一开始我还是用老套路,掏出最基础的 urllib 发送请求,然后配合一堆复杂的正则表达式去硬抠网页源码里的关键信息。但当你需要把整个网页“原汁原味”地保存到本地时,这种做法简直就是一场噩梦。页面里的相对路径、满天飞的 CSS 样式表、散落的图片和 JS 脚本,只要少匹配了一个,下载到本地的网页打开就是排版错乱的半残废状态。
被正则折磨了两个晚上后,我在开源社区挖到了一个绝对能拯救发际线的 Python 库——pywebcopy。
它的功能极其纯粹:只要给它一个 URL,它就能顺藤摸瓜,把这个网页(甚至整个网站)的所有静态资源全部下载到本地,并且自动帮你把代码里的超链接全部替换成本地路径。也就是说,哪怕你断了网,打开下载好的本地 HTML 文件,排版和交互也和原网站一模一样。
核心工作流:它是怎么做到的?
以前我们自己写爬虫下载网页,最头疼的就是路径重定向。pywebcopy 在底层做的事情,其实就是一条非常清晰的自动化流水线:
[输入目标网页 URL] │ ▼ (发送请求并解析 DOM 树) │ ├──────────────────────┬──────────────────────┐ │ │ │ ▼ ▼ ▼ 抓取所有 CSS 样式表 抓取所有 JS 脚本 抓取所有媒体图片 │ │ │ └──────────────────────┼──────────────────────┘ │ ▼ (核心操作:将源码中的网络链接重写为本地相对路径) │ ▼ [生成完全离线可用的本地静态文件]
废话不多说,咱们直接看代码。用它写爬虫,代码短得简直让人心生愉悦。
实战演示一:极速克隆单个网页
假设我们现在看到一篇排版非常精美的技术文档或者通告页面,想把它原样下载到本地的 archives 文件夹里。
from pywebcopy import save_webpageimport os# 目标网页的链接target_url = 'https://docs.python.org/3/tutorial/index.html'# 设置保存在本地的哪个目录下download_folder = './archives'if not os.path.exists(download_folder): os.makedirs(download_folder)# 自定义一些下载配置kwargs = { 'project_name': 'python_tutorial_page', # 最终会生成这个名字的文件夹 'bypass_robots': True, # 是否忽略 robots.txt 的限制 'debug': False, # 关掉没完没了的控制台日志 'open_in_browser': False # 下载完是否自动用浏览器打开}print("开始抓取网页,请稍候...")# 就这一行核心代码,搞定所有事情save_webpage( url=target_url, project_folder=download_folder, **kwargs)print(f"抓取完成!文件已静静地躺在 {download_folder}/python_tutorial_page 目录下了。")
运行完这短短几行代码,你去文件夹里看,它不仅帮你建好了 css、js、images 等子目录,还把主文件规规矩矩地保存成了 index.html。双击打开,排版分毫不差。
实战演示二:丧心病狂的“全站克隆”
有时候我们需要的不仅是当前这一个页面,而是整个站点的静态备份。比如你想把某个开源工具的整套在线文档全部拉到本地,方便在高铁上没网的时候查阅。
这时候我们就不用 save_webpage 了,直接上大杀器 save_website。
from pywebcopy import save_website# 目标网站的根目录site_url = 'https://some-open-source-docs.com/'save_dir = './offline_sites'# 配置参数和单页抓取基本一致config = { 'project_name': 'full_docs_backup', 'bypass_robots': True, 'debug': False, 'delay': 1 # 友好爬虫,每次请求稍微停顿一下,别把人家服务器打崩了}print("正在执行全站克隆操作,这可能会需要一杯咖啡的时间...")# 启动全站抓取save_website( url=site_url, project_folder=save_dir, **config)print("全站克隆结束!现在你可以拔掉网线,尽情浏览了。")
友情提示:使用全站克隆时一定要克制。如果目标网站是一个包含几十万个页面的大中型站点,这个函数会顺着链接一直爬下去,不仅你的硬盘会抗议,对方的服务器也会顺着网线过来找你拼命。
实战演示三:更精细的底层控制
如果你觉得上面两个一键封装好的函数太“黑盒”,你想要自己控制到底该抓什么不该抓什么,pywebcopy 也暴露了底层的 WebPage 类供我们玩耍。
from pywebcopy import WebPage# 初始化一个网页对象url = "https://news.ycombinator.com/"page = WebPage()# 拉取网页内容print("正在解析网页DOM树...")page.get(url)# 你可以轻松拿到清洗后的本地化 HTML 源码local_html = page.get_html()# 或者,你其实只想要这个页面里所有的静态文件链接,不想立刻下载print("\n--- 页面包含的所有资源链接 ---")for file_url in page.files: print(file_url)# 等你做完一些自定义的过滤逻辑后,再决定统一保存# page.save_complete()
总结一下
在这个数据就是资产的时代,我们经常会有存档网页的需求。相比于用截图软件滚屏截出一张几百兆的糊图,或者自己苦哈哈地写几十行 urllib 请求外加正则匹配,使用 pywebcopy 这样纯粹的工具来生成原生的离线静态站,无疑是一种优雅得多的解决方案。
下次当你再遇到需要完整备份某个信息门户或者文档库的时候,记得把这个库翻出来试试。把枯燥的体力活交给代码,咱们省下的时间拿去喝茶不好吗?
编辑:余文彬
审校:余雨馨