资讯动态

folium地图HTML打包与分发实战:从脚本编写到src.zip交付详解

发布时间:2026/9/8 7:06:01 来源:尧图企业网站定制
简介面向 Python 数据可视化与 WebGIS 开发者的 folium 离线加速资源包用于解决 folium 生成的交互式地图在弱网或内网环境下因依赖外部脚本与样式而加载缓慢、无法完整展示的问题。压缩包内置 80 个文件大小约 1.82MB包含地图渲染所需的 Leaflet 核心库、负责页面布局的 Bootstrap 框架、简化交互逻辑的 jQuery、提供标记图标的 Font Awesome 字体库以及支持大量标记聚合显示的 markercluster 插件文件类型涵盖样式表、脚本、源码映射、图片与字体等静态资源可满足 folium 页面本地化部署的基本需要。目前已有 4047 人浏览学习适用于需要离线展示地理数据的项目实践也可用于研究 folium 默认模板的前端依赖构成。通过将本地资源路径接入页面开发者能显著减少网络等待时间提升地图加载稳定性并在此过程中掌握地图插件的配置方法对后续优化 WebGIS 应用具有直接参考价值。 遇到folium html src.zip这类压缩包我估计不少做数据分析和地理可视化的同事都经历过——项目代码、放在 src 目录下的 Python 脚本、跑完 folium 之后生成的一个 HTML 地图文件为了交付方便直接把源码和成品打包成一个 zip 丢给对方。这个流程听上去很常规但实际踩坑率非常高HTML 在本地能打开换个机器就白屏源码明明能跑但地图的样式和依赖在对方环境里对不上打包时多包了一层目录对方解压之后反而找不到文件。我前段时间刚好梳理了一个从 folium 地图脚本编写、HTML 输出、到源码和页面打包分发的完整项目属于那种“看起来半小时搞定实际折腾了大半天”的典型。这篇文章就把整个过程拆开讲清楚重点解释 folium 生成的 HTML 到底是什么结构、为什么有时候双击能看有时候不能、以及怎么打包才能让拿到文件的人少走弯路。想用 folium 做地图交付、或者拿到 zip 包又无从下手的同学这篇可以直接当参考手册用。1. 先把“folium 生成了 HTML”这件事的本质说透1.1 folium 的本质是 Python 和 Leaflet 之间的桥梁很多人第一次接触 folium 都会困惑我用 Python 调用库创建地图为什么不弹出一个 GUI 窗口而是落盘成一个map.html这其实是 folium 的设计思路——它不做渲染它只负责“生成代码”。folium 在后台做的工作可以粗略拆成三件事接收你用 Python 传入的地理数据、坐标、GeoJSON 或 GeoDataFrame。把这些数据翻译成 Leaflet 能认识的 JavaScript 对象和调用代码。把这些 JS 和对应的 HTML 标签一起组装成一个完整的 HTML 文档通过save()写到一个文件里。这个 HTML 文件本身就是一个完整自洽的 Web 页面。你双击打开它浏览器加载了页面里的 Leaflet 库和地块数据渲染出一张能拖拽、能缩放、能弹窗的交互式地图。中间不需要 Flask不需要 Django也不需要任何 Python 后端。也正是因为这个特性folium 特别适合做“一次性交付”写个脚本跑一遍就能产出一个别人用浏览器直接看的地图文件。可交付的文件里没有任何 Python 环境依赖这在对外分享、给领导汇报、发给客户确认时非常方便。1.2 解构一个典型的 folium HTML 文件为了搞明白后面那些“打开是白屏”“地图灰底不显示”的坑有必要看一眼生成的 HTML 到底长什么样。我自己实际打开过一次生成的map.html核心骨架大概是下面这样的只是经过了很多删节。!DOCTYPE html html langzh-cn head meta charsetutf-8 titleFolium 地图/title meta nameviewport contentwidthdevice-width, initial-scale1.0 !-- 这里会引入 Leaflet 的样式文件 -- link relstylesheet hrefhttps://unpkg.com/leaflet1.9.4/dist/leaflet.css / /head body !-- 地图的容器 -- div idmap stylewidth: 100%; height: 100%; position: relative;/div !-- 引入 Leaflet 核心库 -- script srchttps://unpkg.com/leaflet1.9.4/dist/leaflet.js/script script // 创建地图实例 var map L.map(map).setView([39.9042, 116.4074], 10); // 添加底图瓦片 L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { attribution: OpenStreetMap contributors }).addTo(map); // 添加标记点 L.marker([39.9042, 116.4074]).addTo(map) .bindPopup(b这里是起点/b); /script /body /html注意看两个关键外部依赖Leaflet 库是从unpkg.com的 CDN 引入的底图瓦片是从tile.openstreetmap.org加载的。这意味着这个 HTML 文件想要正常显示浏览器就必须能访问这两个外网域名。这一点是后续所有“地图打不开”问题的核心根源。folium 生成的 HTML不是把所有依赖都内嵌到文件里的“完全自包含”形态它依赖 CDN 和在线瓦片服务。理解了这一点很多种玄学问题都能一眼定位。1.3 为什么最终要打成 zip 分发既然 folium 产出的核心产物是一个 HTML 文件那直接把这个 HTML 发给别人不就行了吗现实中往往会多打包一层把源代码也带上原因大概是这么几点。地图需求方经常需要改需求这里加个点、那里换个颜色、换一张底图。只发 HTML 没法让客户自己改把 Python 源码一起附上对方团队里有懂技术的人就能自己调。output/map.html是从src/map_generator.py生成的如果不带源码文件在交付后就是一个“黑盒”谁都不知道地图里的点是怎么来的。很多项目到了结项阶段需要归档需求方会要求源代码、文档、产物三位一体放进一个包zip 就是最通用的容器格式。所以“folium html src.zip”这个组合其实是一个很典型的小交付物形态用 Python 写 folium 脚本生成 HTML 地图再把源码、HTML 成品和依赖声明一起打进 zip。它不复杂但要做好细节才能让双方都省心。2. 从零搭好“src 目录 HTML 输出”的项目骨架2.1 目录结构不要随意按 src 布局来很多新手写数据脚本喜欢把所有文件平铺在根目录下比如map_generator.py旁边就是map.html再旁边是requirements.txt。临时跑一跑没问题但要作为 zip 交付我建议还是按 src 布局整理结构清晰会省掉非常多后续麻烦。一个我验证过很多次的结构是这样folium_html_src/ ├── src/ │ └── map_generator.py ├── output/ │ └── map.html ├── requirements.txt └── README.md这么做有几个实际好处。src/里只放 Python 源码output/里只放生成的产物职责分离后打 zip 时才知道哪些要打包、哪些不用。同时别人拿到压缩包后一眼就能看懂项目怎么组织不需要再额外解释。README.md按惯例写清楚依赖安装命令和运行命令即使隔了几个月回头看也能快速上手。2.2 写一个能真实运行的地图生成脚本下面的脚本不是玩具示例而是我自己项目里用过的简化版本。目标是生成一张包含业务点位、带图层控制、能直接保存为本地 HTML 的交互式地图。# src/map_generator.py import folium from folium.plugins import MarkerCluster # 1. 先创建地图对象以成都市中心作为初始视野 m folium.Map( location[30.5728, 104.0668], zoom_start12, tilesOpenStreetMap, width100%, height100%, control_scaleTrue, ) # 2. 准备一组业务点位数据 pois [ {name: 春熙路, lat: 30.6576, lng: 104.0812, desc: 购物商圈}, {name: 武侯祠, lat: 30.6455, lng: 104.0478, desc: 三国文化}, {name: 九眼桥, lat: 30.6390, lng: 104.0867, desc: 酒吧与夜景}, {name: 天府广场, lat: 30.6573, lng: 104.0637, desc: 市中心地标}, ] # 3. 用 MarkerCluster 做点聚合点多了不卡 cluster MarkerCluster(name景点聚合).add_to(m) for p in pois: folium.Marker( location[p[lat], p[lng]], popupf{p[name]}br{p[desc]}, tooltipp[name], ).add_to(cluster) # 4. 加一个图层控制开关方便切换到其他底图图层 folium.LayerControl().add_to(m) # 5. 保存文件 m.save(output/map.html) print(地图已生成: output/map.html)这个脚本的逻辑很简单但有几个细节新手容易忽略。tilesOpenStreetMap是 folium 内置的底图之一默认向https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png请求瓦片。这个域名在国内部分网络环境下访问不稳定后面会单独讲替代方案。MarkerCluster是 folium 的插件它对业务点位多、不需要逐个分析的地图非常有用。一个图层塞几十个 Marker 浏览器还能扛住但如果点位有几百上千个不加聚类会明显卡顿。LayerControl可以让你在页面上切换底图和图层但如果地图里只有一层数据不加也无妨。加了之后记得要在脚本最后调用m.save()否则图层控制不会出现在产物里。2.3 save() 不是只能输出到文件很多人把m.save(output/map.html)当成一句话背下来其实save()内部做的是把 HTML 字符串渲染出来再写盘。如果你不想落盘或者想把这个字符串塞进其他模板里可以用m.get_root().render()拿到完整的 HTML 文本。html_text m.get_root().render() print(len(html_text)) # 查看生成页面的大小这个技巧在两种场景下很实用一是你想把地图直接嵌入到已有的 Web 模板里二是你想在内存里对 HTML 做字符串替换——比如把默认的 CDN 域名替换成你自己内网部署的域名。直接改字符串比重新写一遍 folium 脚本要快得多。3. 把源码和成品打包成 src.zip 的正确姿势3.1 打包命令和“不要多包一层”的坑目录结构准备好了生成物也验证没问题接下来就是打包。这一步看起来无非是右键压缩但实际交付中因为路径问题翻车的例子不在少数。标准做法是在项目根目录的上一级执行压缩保证 zip 包内部第一层就是folium_html_src/不要把上一级目录也套进来。在 Linux 或 macOS 命令行下cd ~/projects/ zip -r folium_html_src.zip folium_html_src/在 Windows 的 PowerShell 下cd ~/projects/ Compress-Archive -Path .\folium_html_src -DestinationPath .\folium_html_src.zip这两种方式都能保证 zip 里第一层是folium_html_src/目录而不是散落一地的src/和output/。如果压缩后打开 zip 发现第一层就是src、output、requirements.txt平铺着说明你是在folium_html_src目录内部执行的压缩解压时极容易把文件混淆到别人当前目录下。多套一层folium_html_src对方解压后所有文件都在一个独立文件夹里体验会好很多。3.2 打包前的自查清单结合自己的交付经验我整理了一个简单的检查表每次打 zip 之前过一遍基本能避免 90% 的低级问题。检查项检查方法常见问题依赖版本已固化pip freeze后截取关键包版本写进 requirements别人装到新版 folium 导致 API 不兼容HTML 文件能正常显示双击打开确认瓦片和标记点都在只在你本地正常对方显示白屏路径不含有中文或空格检查项目文件夹命名某些工具解析路径出错README 写了运行命令pip install -r requirements.txt python src/map_generator.py对方拿到包无从下手zip 内没有多余文件双击 zip 检查内容把.venv、缓存、密钥文件一起打进去关于最后一条我特别提醒一句.venv虚拟环境目录千万不要打进 zip。虚拟环境里动辄几千个文件压缩包巨大而且里面很多路径是写死的别人解压后根本没法直接用。同样的如果脚本里有访问数据库或外部 API 的密钥打包前记得清理zip 一旦扩散出去基本等于密钥泄露。3.3 对方拿到 zip 后的复现路径为了让拿到包的人能在 5 分钟内跑通我在 README 里通常只写三行命令unzip folium_html_src.zip cd folium_html_src pip install -r requirements.txt python src/map_generator.pyrequirements.txt我一般这样写folium0.18.0 branca0.7.2固定版本号非常关键。folium 是更新很快的库不同小版本之间的插件 API 和默认瓦片行为都有过调整。如果requirements.txt只写folium对方哪天装了一个大版本翻新的 folium脚本未必能直接跑出一样的地图。锁版本虽然保守但在交付场景里稳定大于先进。4. 实战中最高频的地图 HTML 问题与排查记录4.1 双击打开 HTML地图区域是灰色或空白这是 folium 交付中排名第一的反馈“你的地图文件打开是白的。”问清楚现象后我第一反应永远是让反馈者按 F12 打开浏览器开发者工具切到 Network 面板刷新页面看是哪个请求红了。根据经验结果无外乎两种。第一类Leaflet 库本身没加载下来。在 Network 面板里能看到leaflet.js或leaflet.css的请求是红色失败的。原因是 folium 默认从unpkg.com拉取 Leaflet这个 CDN 在某些网络环境下不稳定甚至直接被防火墙屏蔽。验证方法很简单直接在浏览器地址栏访问那个https://unpkg.com/leaflet1.9.4/dist/leaflet.js看能否下载。第二类Leaflet 加载正常但底图瓦片加载失败。页面里能看到地图容器的框架也有控件按钮但地图主体区域一直灰着。Network 里能看到对tile.openstreetmap.org的请求大量失败。OSM 官方瓦片服务器在国内的网络环境下一直不稳定属于老问题。4.2 换底图从根源上解决瓦片加载失败解决瓦片加载失败最直接的办法是在脚本里换一个可用的瓦片源。folium 本身支持通过tiles参数指定多种内置地图也可以传自定义 URL 模板。常见的替换方式# 1. 使用高德地图瓦片 m folium.Map( location[30.5728, 104.0668], tileshttps://webrd0{s}.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x{x}y{y}z{z}, attr高德地图, zoom_start12, )换成高德瓦片后国内网络环境的加载速度会有质的改善。但要注意高德瓦片属于商业地图如果是企业项目需要确认授权边界个人学习研究没问题商用前一定要查清楚。4.3 完全离线/内网环境怎么显示地图有的项目在部署阶段要求完全内网离线这时候 CDN 和在线瓦片都没办法依赖。处理思路分两步。第一步把 Leaflet 库本身“内网本地化”。做法是先把leaflet.js和leaflet.css下载到本地再通过字符串替换把 HTML 里的 CDN 链接指向本地相对路径。m.save(output/map.html) # 用 text 方式替换 CDN 引用为本地相对路径 with open(output/map.html, r, encodingutf-8) as f: html_text f.read() html_text html_text.replace( https://unpkg.com/leaflet1.9.4/dist/leaflet.css, ./leaflet/leaflet.css, ).replace( https://unpkg.com/leaflet1.9.4/dist/leaflet.js, ./leaflet/leaflet.js, ) with open(output/map_local.html, w, encodingutf-8) as f: f.write(html_text)第二步是底图本地化这一步工作量和成本都比较大。简单方案是只保留 GeoJSON 叠加数据把底图换成空白底图地图上只显示你自己画的矢量边界、点位和热力图。想要有真实底图的话就必须用离线瓦片服务器比如 GeoServer或者先批量下载瓦片再用 nginx 做静态托管来提供瓦片服务这已经超出本文范围但方向是明确的。4.4 在 PyQt5 桌面应用里加载 folium 地图我在项目里也踩过“把 folium HTML 塞进 PyQt5 桌面程序”的坑。核心结论是不要用QWebView用QWebEngineView两者完全是两代技术栈。QWebView基于 QtWebKit已经停止维护遇到 folium 这种依赖现代 JavaScript 的页面经常加载失败、交互卡顿。QWebEngineView基于 Chromium功能完整而且对 HTML5 和 ES6 支持很好。一个可运行的简易加载示例import sys from PyQt5.QtWidgets import QApplication, QMainWindow from PyQt5.QtWebEngineWidgets import QWebEngineView app QApplication(sys.argv) window QMainWindow() browser QWebEngineView() # 把前面生成好的 map.html 加载进窗口 browser.load(file:///absolute/path/to/output/map.html) window.setCentralWidget(browser) window.resize(900, 600) window.show() sys.exit(app.exec_())注意file:///后面要跟绝对路径。如果地图页面里有异步请求或者插件加载进度需要监听loadFinished信号来做后续逻辑不能简单用time.sleep等否则会出现页面还没初始化完就获取 DOM 元素导致的空指针问题。4.5 HTML 转 Markdown 和邮件分发也要注意见了很多同事用 Typora 或语雀写文档时想把 folium 地图里的内容贴进 Markdown。这里有个认知层面需要澄清的地方folium 地图 Map图形本质上是 JavaScript 运行时的产物不是静态图片所以没办法直接通过“复制粘贴”转成 Markdown 里的插图。可行的做法是在生成的 HTML 页面里截屏保存为 PNG然后把图片贴进 Markdown。或者用 pyppeteer / playwright 自动化控制浏览器对地图区域截图。在支持 iframe 的文档平台里可以用iframe srcmap.html嵌入但绝大多数 Markdown 渲染器不支持这种用法。至于把 HTML 当邮件内容发出去我真心不建议。大部分邮件客户端出于安全考虑会屏蔽 HTML 里的 JavaScriptfolium 地图打开后要么空白要么只有静态框架。火光效果完全出不来客户体验很差。交付地图最稳妥的方式还是打包 zip 或提供链接。5. 让这个 src.zip 交付物再进阶一步5.1 批量生成多城市地图并打包如果你的项目不是一张地图而是十几个城市各一张脚本可以批量生成并把所有 HTML 集中放进output/。import folium from pathlib import Path cities [ (北京, 39.9042, 116.4074), (上海, 31.2304, 121.4737), (广州, 23.1291, 113.2644), ] output_dir Path(output) output_dir.mkdir(exist_okTrue) for name, lat, lng in cities: m folium.Map(location[lat, lng], zoom_start10) folium.Marker([lat, lng], tooltipname).add_to(m) m.save(output_dir / f{name}_map.html)这样批量产出的 HTML 文件名建议统一用拼音或英文 日期。中文文件名在 Windows 自带的压缩工具里偶尔会有编码问题解压后文件名乱码这个坑很隐蔽但遇到了实在恼火。5.2 把地图嵌进 Web 应用模板如果后续决定把静态 HTML 升级成动态页面folium 的 HTML 字符串可以直接嵌入 Flask 或 Django 的模板里。在 Flask 中可以把m.get_root().render()先算出来再作为变量传递给模板。from flask import Flask, render_template_string import folium app Flask(__name__) app.route(/map) def map_page(): m folium.Map(location[30.5728, 104.0668], zoom_start12) html_map m.get_root().render() return render_template_string( div stylewidth: 800px; height: 600px;{{ map_html|safe }}/div, map_htmlhtml_map, )这里用|safe过滤器必不可少。Flask 默认会对模板变量做 HTML 转义如果不加safe地图的script标签会被当成纯文本显示在页面上地图完全加载不出来。这个问题我在第一次实践时就中过招排查了大半天才想起自动转义这回事。5.3 zip 包内附带一个验证脚本最后一次踩坑经历让我养成了一个习惯在 zip 包里额外放一个verify.py专门检查生成环境是否正常。它的功能很简单就是先校验依赖是否能导入再尝试生成一张测试地图最后检查输出文件是否存在且大小不为 0。# verify.py import os import folium m folium.Map(location[30.5728, 104.0668], zoom_start12) m.save(output/_verify.html) size os.path.getsize(output/_verify.html) print(f验证通过HTML 大小 {size} 字节) print(请用浏览器打开 output/_verify.html 检查地图是否显示)这个脚本的价值在于它把“环境是否正常”和“我的业务数据是否正确”这两类问题隔离开。对方跑verify.py没问题说明环境没毛病问题大概率出在业务数据或网络受限如果verify.py都没跑通你就能直接指导对方反馈依赖安装环节的问题省掉来回猜的沟通成本。我个人在实际交付里最深的体会是folium 本身不难真正影响用户体验的永远是环境差异和网络差异。同一份 HTML在张三的电脑上就是秒开在李四的内网里就是白屏一个这不是代码逻辑的问题而是外部依赖在起作用。所以做交付的人一定要在自己的工程里把“依赖声明、网络说明、路径规范”这三件事做到位而不是只丢一个孤零零的 HTML 文件给用户。最后再分享一个小技巧如果你发现生成的 HTML 里 CDN 链接特别多想要知道整个页面都依赖哪些外网资源可以用一条命令把script和link标签里的 src 全部抽出来看看。grep -oE (src|href)[^] output/map.html把输出的 URL 逐个在浏览器里访问一遍本地到底能不能满足这些依赖就一目了然了。这个技巧在做内网交付的时候几乎是必用它能帮你提前预判很多莫名其妙的显示异常。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价