简介:全国旅游景点SQL数据包,面向旅游数据分析、GIS可视化、地图产品开发以及相关学术研究场景,收录了约32万条全国景点记录,可直接导入MySQL查询使用。资源以7z压缩后仅11.01MB,包内包含1个SQL文件;表结构涉及id、title、tel、add、type、areaid、poiid、gcjx、gcjy、gpsx、gpsy等字段,完整覆盖景点名称、联系电话、地址、类型归属、行政区划标识及经纬度坐标等关键信息。数据总量达324498项,并附带北京动物园等样例记录,便于理解字段的实际取值与存储方式。读者拿到后即可快速建表导入,用于构建景点检索系统、旅游APP后端数据、区域热力分析或各类数据可视化项目。已有4653人学习下载,适合需要结构化地理数据的开发者和数据分析师直接取用。
1. 32万条全国旅游景点数据.7z:一份还没打开就让人头疼的压缩包
第一次拿到「32万条全国旅游景点数据.7z」这个文件时,大多数人的第一反应是先解压再说,结果要么卡在 7z 命令不存在,要么解压出来一堆乱码,甚至打开 CSV 才发现经纬度整列都是反的。这个压缩包的本质,是一份覆盖全国的大规模 POI 数据,解压后通常是一张带景点名称、省份、城市、经纬度、景区等级和简介的结构化表,适合做旅游客流分析、景区分布可视化、门票定价研究或地理信息系统的基础数据源。但真正值钱的不是文件本身,而是你拿到手之后能否把它清洗成一张能直接进 pandas 的表。这篇笔记就是围绕这个目标写的:从 7z 解压命令、数据生成格式到清洗和可视化,一条链路走完。
2. 这份景点数据里有什么:字段结构、压缩比与先别急着解压的理由
2.1 字段结构的合理预期
旅游景点数据看起来是「一张表」,但真实落地时字段千差万别。常见做法是,核心字段至少包含景点名称、所在省份、城市和区县,这是地理聚合分析的基本维度。其次是坐标字段,一般是「经度,纬度」两个独立列,或者兑成一个字符串如「116.397,39.909」;稿子里写「纬度,经度」顺序的坑在数据源里非常常见,清洗时一定要留个心眼。
再往下是描述性字段,包括景区等级(5A、4A 这类评定信息)、门票参考价、开放时间、景点简介以及分类标签。有些数据源会把「A 级景区」单独拆一个表,用景区编号关联。拿到手先不要假设它一定是 CSV 或 JSON,很多数据包为了压缩体积会直接用 SQLite 数据库文件,配合 7z 压缩能达到非常夸张的压缩比,解压后你面对的甚至不是一个文件,而是一个目录树。
2.2 数据量与压缩比:7z 压缩包的玄学
先给新手打个预防针:32 万条听起来很多,但纯文本 CSV 里的每条记录如果带上简介和详细地址,行均长度很容易超过 1KB。也就是说,解压后的文件体积可能在几百 MB 到 1GB 之间浮动,7z 压缩包则通常在几十 MB 到一百多 MB 的水平。7z 格式在 LZMA2 算法下对这类重复度高的文本数据压缩率极高,这也是为什么「32万条全国旅游景点数据.7z」会用 7z 而不是 zip 发布。
这就带来一个很实际的问题:解压后的临时文件直接把磁盘剩余空间吃剩一个零头。个人经验里最典型的翻车现场,是先把压缩包解压到 C 盘默认目录,结果 C 盘爆红,整个系统卡到鼠标都挪不动。处理大压缩包的玄学是:先看压缩包内部文件列表,再预估解压后体积,最后决定解压到哪块盘。
2.3 三种常见的数据内容形态
解压后有三种主流形态,处理方式完全不同:
CSV 最通用,但编码不确定。都是 CSV,有的用 UTF-8 带 BOM,有的用 GBK,还有的是 UTF-8 无 BOM。直接用 pandas 读大概率会有一场乱码血泪战。
JSON 文件适合嵌套结构,比如每个景点带多张图片链接、多条交通信息。这类数据更「现代」,但一行 JSON 可能就几 KB,解析时内存压力更大。
SQLite 数据库则是一步到位的玩法,直接用 sqlite3 查询,不需要一次性 load 进内存,非常适合「32 万条全量分析」这种场景。
你可以用一条命令先探明压缩包内部结构,不需要解压任何东西:
# 列出压缩包内所有文件,带大小信息 7z l 32万条全国旅游景点数据.7z这命令的输出会显示文件数、原始大小和压缩后大小。重点看 Original Size 那一列,它就是解压后需要的磁盘空间。如果这个数字是几个 GB,我建议直接改解压路径,别犹豫。
2.4 拿到数据后先做的一件事
很多人的习惯是拿到压缩包直接右键解压,然后打开文件看数据。经验丰富一点的工程师会反过来:先补一条数据体检命令,把编码格式和字段首行抓出来,因为解压本身不费事,费事的是解压后发现格式不对再重新处理。
# -so 把内部文件内容输出到标准输出,结合 head 只取前 1KB 做探测 7z e -so 32万条全国旅游景点数据.7z 景点数据.csv | head -c 1000注意这里的「景点数据.csv」需要替换成 2.1 节那条 7z l 命令看到的内部文件名。如果文件路径带中文目录,在部分 Linux 终端下会出现转义问题,保险做法是给文件名加引号。看到的前几行会直接告诉你三件事:这是不是 CSV,表头长什么样,以及用的是 UTF-8 还是 GBK。这一步做完,后面清洗才有方向。
3. 7z 解压实操:Linux、Windows 与 PyCharm 三环境跑通
3.1 Linux 下解压 7z 文件:安装 p7zip 与命令参数说明
「linux解压7z文件」是这块最常见的搜索需求,因为 Linux 默认不带 7z 支持,报错信息往往是「7z: command not found」。你需要先装 p7zip 工具集:
# Debian/Ubuntu 系 sudo apt update && sudo apt install p7zip-full # CentOS/RHEL 系,需要先启用 EPEL 仓库 sudo yum install epel-release && sudo yum install p7zip装完以后最核心的一条命令是:
# 解压到当前目录,保留压缩包内目录结构 7z x 32万条全国旅游景点数据.7z # 指定解压目录,例如 /data/tourism 7z x 32万条全国旅游景点数据.7z -o/data/tourism参数说明:x是「解压并保留目录结构」,这是处理多文件压缩包的推荐参数;-o指定输出目录,注意-o后面不能有空格,写-o/data/tourism而不是-o /data/tourism。如果压缩包有密码,追加-p你的密码,比如7z x file.7z -p123456,但这样会在 shell 历史里留下密码,建议解压后再清理。
还有一个容易被坑的点:7z e这个命令会把所有文件解压到同一层目录,不保留压缩包内的子目录。如果内部正好有两个不同目录下有同名文件,7z e会直接覆盖或报错。判断自己该用x还是e的唯一标准,就是看7z l列出来的路径是否带多层目录。
3.2 Windows 上的图形界面与命令行解压
Windows 用户通常直接装 7-Zip,右键就能看到「解压到当前文件夹」和「解压到 32万条全国旅游景点数据\」两个选项。但这套图形界面操作在批处理或定时任务里没法用,所以我一般会用命令行方式,假设 7-Zip 装在默认路径:
# 在 cmd 或 PowerShell 中调用 7z.exe,注意路径带引号 "C:\Program Files\7-Zip\7z.exe" x "D:\data\32万条全国旅游景点数据.7z" -o"D:\data\tourism"这里有个 Windows 特有的坑:压缩包里如果是中文文件名,在 cmd 默认的 GBK 代码页下看起来可能是乱码。解压前先执行chcp 65001切换到 UTF-8 代码页,再跑 7z 命令,文件名就能正常显示。另外 PowerShell 里调用 7z 时,-o参数的路径如果带空格,必须用双引号把整个-o参数包起来,否则会解析为两个参数。
3.3 PyCharm 里处理 7z:用 py7zr 库把解压纳入项目流程
「pycharm添加7z」这个搜索词其实是个概念混淆:PyCharm 本身不解压压缩包,真正要做的是给项目解释器安装一个能处理 7z 的 Python 库,让解压、读取、清洗在同一条脚本链路里完成,不需要来回切命令行。我常用的是 py7zr,它把 7z 的解压能力直接以 API 形式暴露给 Python。
# 在 PyCharm 的 Terminal 面板执行一次即可 # pip install py7zr然后解压到项目目录:
import py7zr # mode='r' 表示只读打开,extractall 解压到指定目录 with py7zr.SevenZipFile('32万条全国旅游景点数据.7z', mode='r') as z: z.extractall(path='./tmp_tourism') # getnames() 返回压缩包内所有文件名 print(z.getnames()[:10])参数说明:SevenZipFile的第一个参数是压缩包路径;mode='r'是读取模式,如果你想创建压缩包则用mode='w';extractall(path=...)控制解压目标目录。py7zr 的历史版本中extract方法需要额外传path参数,新版本里统一使用extractall,如果你在旧代码里看到z.extract(path=...)也能跑,但建议按当前 API 写。
解压完立刻交给 pandas 是这条链路最畅快的部分:
import pandas as pd import glob # 解压后再用 glob 找 CSV 文件,避免硬编码文件名 csv_files = glob.glob('./tmp_tourism/**/*.csv', recursive=True) df = pd.read_csv(csv_files[0], encoding='utf-8', nrows=1000) print(df.shape, df.columns.tolist())参数说明:glob.glob的recursive=True允许**匹配子目录,这样不管解压出来是平铺文件还是多层目录都能找到。pd.read_csv里nrows=1000是快速预览手段,拿到文件先只看前 1000 行,别上来就读全量,这是防内存崩溃的常识。
3.4 解压后的文件校验:行数与哈希
解压完成先别急着进分析,花一分钟做三个验证。第一步是数行数:
# wc -l 统计行数,CSV 带表头时要减 1 才是数据条数 wc -l tmp_tourism/*.csv第二步是核对文件大小,确认和7z l输出的 Original Size 大致一致。第三步是比对哈希值,如果数据源官方给了 MD5 或 SHA256,用下面对比:
# 计算解压文件哈希 sha256sum tmp_tourism/*.csv这一步的作用不是形式主义,而是确认压缩包在下载或传输过程中没有损坏。哈希不匹配基本意味着解压出来的文件有某处数据损坏,这种隐患在 32 万条数据里极难定位,返工成本极高。保留原始 7z 压缩包就是给自己留一颗后悔药,重置数据时不需要重新下载。
4. 从原始数据到分析表:编码修复、去重与坐标规整
4.1 编码问题:UTF-8、GBK 与 BOM 的一次性排查
解压只是热身,真正的第一道坎是编码。读取时常见三种结果:正常显示中文、中文变成「锟斤拷」、首列多一个看不见的\ufeff字符。前一种是编码用对了,后两种分别对应 UTF-8 解码 GBK 数据、以及未处理 BOM 头。
一个简单的排查办法,是先用二进制模式读文件头,判断编码再选择读取参数:
with open(csv_path, 'rb') as f: head_bytes = f.read(100) # UTF-8 的 BOM 是 \xef\xbb\xbf,GBK 没有 BOM 头 if head_bytes.startswith(b'\xef\xbb\xbf'): encoding = 'utf-8-sig' else: # 尝试用 GBK 解读,如果抛异常就回到 UTF-8 try: head_bytes.decode('utf-8') encoding = 'utf-8' except UnicodeDecodeError: encoding = 'gbk'这里的逻辑是:有 BOM 就直接用utf-8-sig,它能自动剥离 BOM;没有 BOM 时先尝试 UTF-8 严格解码,失败说明大概率是 GBK。实际项目中还有一种情况:文件整体是 UTF-8,但个别景区简介里混入了 GBK 编码的错误字节。这种情况更隐蔽,读取时要用errors='replace'把坏字节替换成占位符,避免整个文件读取中断。
# 用 errors='replace' 兜底,坏字节变成 � 而不是把整个 DataFrame 搞炸 df = pd.read_csv(csv_path, encoding=encoding, errors='replace')参数说明:errors='replace'是在解码遇到非法字节时用 U+FFFD 替换,而不是抛出异常。宁可个别字符坏掉,也不能让 32 万条数据卡在读取这关。
4.2 去重:32 万条里混了多少重复数据
旅游景点数据的重复率通常高得吓人。同一个景区可能被以「西湖景区」「杭州西湖」「西湖风景名胜区」三种名称收录,还可能有同一名称在不同区县重复出现。直接按「景点名称」去重会误删,按「名称+城市+坐标」去重才靠谱。
# 先按文本去重,再按坐标容差去重 df_dedup = df.drop_duplicates(subset=['景点名称', '城市'], keep='first') # 坐标去重:保留每个景区第一条记录 df_dedup = df_dedup.drop_duplicates(subset=['经度', '纬度'], keep='first')注意这样写的前提是经纬度已经是数值类型。如果原始数据里经度是字符串「116.397,39.909」这种合并写法,要先拆列再排序,否则两个字段永远不会重复。
还有一个容易误杀的场景:同一名称在不同省份存在,比如「人民公园」几乎每座城市都有。因此去重时subset必须把「省份」或「城市」放进去,不然会把不同城市的同名景区删掉。
4.3 坐标解析:从字符串到可用于地图的数值
数据里常见的坐标格式有三种:经度,纬度逗号分隔、116.397 39.909空格分隔、以及+116.397+39.909这种带符号格式。更极端的是度分秒格式,比如116°23'49"。写一个统一解析函数是最省心的做法:
import re def parse_lng_lat(coord_str): if not isinstance(coord_str, str): return None, None # 匹配数字和可选的小数点 nums = re.findall(r'[-+]?\d+\.?\d*', coord_str) if len(nums) < 2: return None, None # 度分秒格式: 116°23'49" -> 116 + 23/60 + 49/3600 deg_match = re.search(r'(\d+)°(\d+)\'(\d+)"', coord_str) if deg_match: d, m, s = map(int, deg_match.groups()) val = d + m / 60 + s / 3600 # 原字符串带 - 号则取负数 return (-val if '-' in coord_str else val), None # 普通格式取前两个数值,按「经度,纬度」的常规顺序返回 return float(nums[0]), float(nums[1])这段代码的关键在于它先分出度分秒和普通格式两条路径,再对度分秒做换算。注意它默认返回顺序是「经度,纬度」,如果你的数据源顺序相反,可以在调用时把返回值对调。
接着用中国地理范围做一次合法性过滤,这是把坐标漂移问题挡在门口最粗暴也最有效的手段:
# 中国大致经纬度范围,超出即判定为脏数据 df_valid = df[(df['经度'].between(73, 135)) & (df['纬度'].between(18, 54))]4.4 输出干净的表:CSV 给人看,Parquet 给程序用
清洗完的数据最终要落盘。常见做法是同时输出两份,一份给同事用 SQL 或 Excel 打开,一份给你自己的后续分析流程用:
# utf-8-sig 保证 Excel 打开不乱码 df_clean.to_csv('tourism_clean.csv', index=False, encoding='utf-8-sig') # parquet 是二进制列式格式,读取快、体积小 df_clean.to_parquet('tourism_clean.parquet', index=False)这里有个细节:to_csv用utf-8-sig而不是utf-8,因为 Excel 在读取带 BOM 的 UTF-8 文件时才能正确识别中文。而 Parquet 自带 schema 信息,列类型会原样保留,下次pd.read_parquet直接得到一个干净的 DataFrame,连dtype都不用重新指定。
如果你用的是 pandas 2.x,to_parquet需要先确认环境里有 pyarrow 或 fastparquet 库。缺库时运行会直接报ImportError,这时执行:
pip install pyarrow5. 常见翻车点排查:从 CRC 错误到经纬度漂移
5.1 解压到一半报 CRC 错误,进度条卡住不动
现象:7z x解压到 80% 左右弹出CRC Failed,文件停留在目录里但无法确认是否完整。
原因:压缩包在下载过程中被截断,或者存储介质的某个扇区损坏。CRC(循环冗余校验)是压缩包自带的完整性校验机制,文件稍有损坏就会在解压时暴露。
解决:先别急着删除,用7z t测试压缩包完整性,确认损坏范围。如果哈希对不上,重新下载源头文件;如果是网络传输导致,换下载方式并对比官方提供的哈希值。对于已经解压出来的半成品文件,建议直接清掉,因为损坏位置不确定,后续读出来的数据可能在某一行悄悄出错。
5.2 pandas 读取时内存爆炸,8G 机器直接卡死
现象:pd.read_csv跑完一行代码,内存占用从 1G 飙到 8G,系统开始疯狂卡顿。翻车现场通常发生在读取全量文件时,尤其是字段多、单行内容长的 CSV。
原因:pandas 默认按 Python 对象读入所有列,字符串列的内存开销远大于文件本身。32 万条数据、每条含几百字简介时,内存放大系数达到 5~10 倍很正常。
解决:读取时指定dtype压缩存储粒度,用usecols只留你真正需要的字段,必要时用chunksize分块读取。
# 只读必要的列,经度纬度降为 float32 cols = ['景点名称', '省份', '城市', '经度', '纬度', '景区等级', '简介'] df = pd.read_csv( 'tourism_clean.csv', usecols=cols, dtype={'经度': 'float32', '纬度': 'float32'}, chunksize=50000 )参数说明:chunksize=50000让read_csv变成一个可遍历的对象,每次只处理 5 万行,而不是一把梭全部读进内存。配合usecols去掉不需要的大字段,8G 内存可以安稳跑完 32 万条。
5.3 经纬度列顺序反了,地图上的点全部漂移
现象:把数据画到地图上,四川省的景区全部落在云南,北京的景点漂到河北。看起来是地理分布错了,实际是数据源的坐标顺序根本不是「经度,纬度」。
原因:不少采集工具从 GPS 模块拿到的是「纬度,经度」顺序,写入 CSV 时却也直接把字段命名为「经度,纬度」,字段名和实际内容对不上。
解决:抽样标定。取几个你确定的坐标:北京故宫约 (116.397, 39.909),杭州西湖约 (120.130, 30.259)。如果数据里的「经度」列读数在 30 附近而不是 116 附近,说明两列顺序反了,用下面的命令对调:
# 发现经度列实际存了纬度,直接交换两列 df[['经度', '纬度']] = df[['纬度', '经度']].values注意这里用.values取数组再赋值,避免 pandas 在列名替换时的索引对齐问题。
5.4 打开 CSV 中文乱码,出现「锟斤拷」和「口口口」
现象:用 pandas 或 Excel 打开解压后的 CSV,景点名称变成一坨「锟斤拷」或者方块字符。
原因:文件本身是 GBK/GB2312 编码,读入时用了 UTF-8。解码错了字节序列就映射成汉字「锟斤拷」,这是 UTF-8 解码 GBK 字节流最典型的产物。
解决:按第 4.1 节的流程做编码探测。如果已经知道是 GBK,在 Windows 下要把 CSV 转成 UTF-8 再给 Excel 用:
# Linux 下用 iconv 把 GBK 转成 UTF-8 iconv -f GBK -t UTF-8 原始文件.csv > 转换后文件.csv历史经验是:数据发布方如果是从旧版 Windows 系统导出,优先怀疑 GBK;如果发布方是后端程序生成的文件,优先怀疑 UTF-8。两种都可以用 4.1 节的二进制探测代码区分。
5.5 统计各省 5A 景区数量,结果多出一个数量级
现象:按省份统计景区数量,发现某些省份的 5A 景区数量比官方公布值高出一倍,同一个「故宫」出现在好几个城市的记录里。
原因:数据源把同一景区的多平台收录条目都算进来了,名称完全相同但来源不同、或者同一个景区被重复抓取多次。
解决:做多级去重,第一级按名称、省份、城市、经纬度四个字段联合去重,第二级对经纬度做空间容差合并。
# 核心去重:四字段联合确认唯一性 df_uniq = df.drop_duplicates( subset=['景点名称', '省份', '城市', '经度', '纬度'], keep='first' ) # 再按坐标格网去重:放大 100 倍后取整,坐标差 0.01 度以内的视为同一景点 df_uniq['grid'] = (df_uniq['经度'] * 100).round() * 100000 + (df_uniq['纬度'] * 100).round() df_uniq = df_uniq.drop_duplicates(subset=['grid'], keep='first')参数说明:(经度 * 100).round()的意思是保留小数点后两位的粗粒度,两个坐标相差在 0.01 度以内时会被归到同一个格网。这里的 100 就是容差系数,数值越大容差越小、去重越严格,需要根据你实际数据的精度调整。
6. 进阶玩法:把 32 万条数据渲染成全国分布图
数据清洗到这个阶段,可以做一个非常有说服力的验证:把 32 万条景点全部画到一张全国地图上。这一步既检验坐标清洗成果,也直接把你之前所有处理的结果可视化。建议先用sample抽样,比如抽 5 万条渲染,因为浏览器和前端图表库一次性处理 32 万个散点会明显掉帧。
import plotly.express as px # 随机抽样并指定随机种子,保证结果可复现 df_sample = df_clean.sample(n=50000, random_state=42) # scatter_geo 不需要地图服务 key,开箱可用 fig = px.scatter_geo( df_sample, lat='纬度', lon='经度', color='省份', scope='china', title='全国旅游景点分布抽样' ) fig.show()参数说明:random_state=42让后续每次运行抽到相同样本,方便对比验证;scope='china'把地图视野限制在中国区域内,同时会隐藏经纬度越界的数据点。渲染完成后,把图放大到四川、广东、黑龙江几个省份,凭直觉核对一下局部密度,再抽查几个已知景点坐标,验证就基本结束了。
除了散点图,这 32 万条数据还可以做语义搜索。用str.contains搜索简介里的关键词,比如「避暑」「古镇」「国家级自然保护区」,把符合条件的子集提出来看分布,这就是一份免费的旅游主题数据集。更进一步的做法是把每行简介用 TF-IDF 向量化,然后用余弦相似度做推荐系统;想打满效果的上限可以再跑一个本地 Embedding 模型做语义检索,但这已经超出一次笔记的范畴了。
我现在的习惯是:所有地理数据压缩包在解压前,先写一个十行的验证脚本,把编码、行数、坐标范围三个指标一次性打出来,确认后再进行主流程。这套流程始于「32万条全国旅游景点数据.7z」这个文件,但它真正有用的部分是那个可复用的检查脚本和数据清洗框架。下次你拿到任何别的 ZIP、7z 或 TGZ 数据包,这套流程还能原样用上,希望帮到你。
本文还有配套的精品资源,点击获取