ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

US-Cities-Database数据处理全流程:解压、清洗与导入SQLite/MySQL

US-Cities-Database数据处理全流程:解压、清洗与导入SQLite/MySQL 简介这是一套面向地理信息系统GIS开发、数据分析与城市研究的美国城市数据库资源可帮助读者省去逐站爬取公开数据的麻烦直接获得结构化城市基础信息。数据主体采用SQL格式存储包含城市名、州名、邮政编码、经纬度等常用属性导入MySQL等数据库后即可按需查询与二次加工同时附有说明文档、辅助文本和许可文件便于快速理解表结构、字段含义及合规使用边界。资源共4个文件整体仅560KB轻量易部署适合在原型项目、教学演示或个人学习场景中快速接入。已有49人浏览学习尤其适合需要完成地图打点、城市距离计算、区域人口分析或市场选址研究的读者。拿到数据后可结合QGIS、Tableau或Python地理库做可视化和建模减少前期数据清洗的人力与时间成本直接聚焦业务结论和应用开发。1. US-Cities-Database-master.zip 是仓库快照不是整理好的数据文件拿到 US-Cities-Database-master.zip第一反应通常是解压然后当成“美国城市数据库”直接读进项目里。这个文件名看起来确实像一个数据交付包实际上它是某个 GitHub 仓库在 master 分支某次提交时的压缩快照源码、数据、脚本、文档都混在同一个目录树里。master 在这里不是版本号是分支名zip 是 GitHub 网页端 Download ZIP 按钮生成的归档产物。如果你的目标是拿美国城市做地理补全、周边城市查询或者地图标注真正需要的可能只是其中 data 目录下的几个 CSV。如果想把这份数据变成服务端可查的字典还得做一遍清洗、建索引、落库。下面这条路径从解压、观测到导入数据库一次走通中途会顺手处理掉几个不拆开包根本发现不了的坑。2. 解压前先侦察用 unzip -l 查看 zip 内部结构与校验完整性拿到 US-Cities-Database-master.zip我一般不会立刻解压。GitHub 打包仓库时会把整个目录树塞进 zip里面可能有很多文件也可能有你不需要的 .github/workflows、docs 长文档、LICENSE 等。先看包内清单能避免解压之后才发现放错了文件位置。2.1 不急着解压unzip -l 只读中央目录unzip -l US-Cities-Database-master.zip | head -40unzip -l 只读取 zip 的中央目录不把文件写到磁盘。输出里能看到文件路径、压缩前大小和日期。head -40 只显示前 40 行方便第一眼判断包内结构。典型输出长这样Archive: US-Cities-Database-master.zip length date time name --------- ---------- ----- ---- 184 2024-05-11 14:22 US-Cities-Database-master/README.md 295 2024-05-11 14:22 US-Cities-Database-master/LICENSE 0 2024-05-11 14:22 US-Cities-Database-master/data/ 9123456 2024-05-11 14:22 US-Cities-Database-master/data/cities.csv注意输出里所有路径都带US-Cities-Database-master/前缀这是 GitHub 归档的标准行为所有文件放在以仓库名和分支名命名的顶层目录下解压后会自动多一层目录。写脚本时如果直接import cities.csv而忽略了这层前缀路径就会找不到。2.2 解压到指定目录并用 unzip -t 排除坏包unzip -t US-Cities-Database-master.zip unzip US-Cities-Database-master.zip -d ./us-cities cd ./us-cities find . -maxdepth 2 -type f | sortunzip -t 做完整性测试逐文件解压到内存并比对 CRC。这一步能提前暴露常见的下载截断问题比如error read zip archive或者压缩包本身损坏时报的invalid zip archive: could not find EOCD。EOCD 是 zip 末尾的 End of Central Directory 记录压缩包下载到一半或传输被代理篡改时最容易丢这段数据。unzip -d 指定解压目标目录。-d ./us-cities会把所有文件释放到us-cities/US-Cities-Database-master/下面。find 命令紧接着把前两层文件列出来和 2.1 里 unzip -l 的清单互相验证确保解压没有丢文件。提示unzip -t 输出最后一行是No errors detected in compressed data of US-Cities-Database-master.zip看到这句话再继续。如果这里报错重新下载比手动修 zip 省时间。2.3 包内目录的角色分工data、scripts、docs解压后对照下面的典型结构判断每个文件属于哪个角色路径常见内容角色data/cities.csv城市名、州、经纬度、人口核心数据data/zips.csv 或 state_code.csv邮编、州名映射辅助数据scripts/ 或 src/导入脚本、数据生成器数据准备docs/字段说明、来源说明元信息README.md / LICENSE用法与授权必读scripts 目录里通常有把原始数据重新生成 CSV 的脚本这类脚本依赖 pandas 或 requests。如果只是想把城市数据用起来不需要跑这些脚本直接用 data 目录的成品 CSV。README 里如果没有写字段说明就先打开 CSV 看表头不要猜列名。GitHub 的 archive 由 git archive 生成不会包含 .git 目录但会包含 .github 工作流文件那些可以忽略。顺带回答一个常见疑问GitHub 下载的 zip 包“怎么安装”。这里不存在安装动作解压后要么读 CSV要么按 README 执行导入脚本仅此而已。3. 读懂 US-Cities-Database 的数据schema、坐标精度与空值清洗解压只是第一步。打开 CSV 之前先确认字段名、行数和每个列的数据类型。很多 US-Cities-Database 类仓库的 CSV 并不完全一致有的用lat、lng有的用latitude、longitude有的甚至直接给 WKT 字符串。探测 schema 的脚本应该循环处理这三种情况。3.1 用 head、wc 和 pandas 确认字段与行数head -3 data/cities.csv wc -l data/cities.csvhead 输出表头和前两行wc -l 统计总行数。如果 CSV 有 BOM表头第一个字段名会显示成city前面带不可见字符导入时字段名会变成city本身处理起来很别扭。遇到这种情况后续 pandas 读取时指定encodingutf-8-sig。import pandas as pd df pd.read_csv(data/cities.csv, encodingutf-8-sig) print(df.shape) print(df.dtypes) print(df.isna().sum())shape 是行列数dtypes 能看出 lat、lng 被读成了 float64 还是 object。isna().sum() 输出每个字段的缺失值统计。人口列 population 是缺失重灾区部分城镇人口为 0部分直接空着。这不是脏数据是美国行政区划本身的特点很多 census-designated place 没有独立的人口数据。3.2 纬度经度不是简单浮点数范围校验和 WKT 解析bad df[(df[lat] -90) | (df[lat] 90) | (df[lng] -180) | (df[lng] 180)] print(越界坐标:, len(bad))纬度范围 [-90, 90]经度范围 [-180, 180]越界记录直接打印数量再抽取前几条看具体值。这类异常通常来自手填数据比如把-74.0060写成了74.0060丢掉负号后坐标会落到欧洲或非洲。如果 CSV 里坐标列是POINT (-74.0060 40.7128)这种 WKT 格式需要拆列df[[lng, lat]] df[wkt].str.extract( rPOINT \(([-\d.]) ([-\d.])\) ).astype(float)正则里POINT \((...) (...)\)两个捕获组分别对应经度和纬度。注意 WKT 的 POINT 语法固定是POINT (经度 纬度)和常识里的“纬度在前”相反。拆完后再做范围校验。浮点精度也要看一眼。40.7128保留 6 位小数时误差约 0.1 米够做地址匹配如果只有 2 位小数误差到 1 公里级别做城市级展示没问题做门店级距离计算就偏差太大。数据里出现整数值的经纬度基本可以判定是占位数据。3.3 主键设计为什么不能只用 city 当主键美国城市重名非常普遍。Springfield有几十个Portland有东西两个海岸的大城市还有一堆同名的小镇分布在德州和俄亥俄。直接拿 city 做主键入库瞬间就会被重复键砸晕。判断重复的标准方式是 city 加 state 复合select city, state, count(*) from cities group by city, state having count(*) 1;同一个州内重复的情况也存在比如 CDP人口普查指定地区和同一个名字的城市行政区在数据源里被分别收录。更稳妥的主键是自增 id 或数据源自带的城市 ID 字段。如果原始 CSV 没有独立 ID就按(city, state, lat, lng)做唯一性约束再用自增主键替代。4. 把 US-Cities-Database 导入 SQLite 与 MySQL最小可复现步骤数据清洗通过后导入数据库。SQLite 和 MySQL 是两种最常见的落地方式前者适合本地单机分析后者适合 Web 服务共享读取。两条路都从同一份 cleaned CSV 出发。4.1 最快路径sqlite3 命令行 .import 导入 CSVsqlite3 cities.db EOF DROP TABLE IF EXISTS cities; CREATE TABLE cities ( city TEXT, state TEXT, lat REAL, lng REAL, population INTEGER ); .mode csv .import data/cities.csv cities EOF先建表再 .import能让 sqlite3 CLI 按 CREATE TABLE 的类型去做字段解析。如果跳过建表直接 .importSQLite 会把所有列存成 TEXTlat、lng 后续做范围查询时就不会走数值比较需要用 CAST 或者重新建表补救。.mode csv 让导入逻辑按 CSV 规则处理字段支持引号包裹的字段、带逗号的字符串。population 列要留心空字符串在 CSV 导入后不会自动变成 NULL而是查询结果里的数值类型也就成了 TEXT。更稳的写法是导入完成后补一条 UPDATEsqlite3 cities.db update cities set population NULL where population ;4.2 MySQL 用 LOAD DATA LOCAL INFILE 导入LOAD DATA LOCAL INFILE data/cities.csv INTO TABLE cities CHARACTER SET utf8mb4 FIELDS TERMINATED BY , ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (city, state, lat, lng, population);MySQL 这边多了三个控制参数。FIELDS TERMINATED BY , 定义列分隔符ENCLOSED BY 表示字段可以用双引号包裹城市名里带空格或逗号时不会把后续字段挤到下一列LINES TERMINATED BY \n 声明行结束符Windows 上编辑过的 CSV 可能是\r\n需要改成\r\n或者先转行符。IGNORE 1 LINES 跳掉表头。执行前确认 MySQL 客户端开启了 local-infile命令行连接时加--local-infile1否则报Loading local data is disabled。另外表结构里 lat、lng 建议用 DECIMAL(9,6)不要用 FLOAT。FLOAT 是 4 字节精度大约 7 位有效数字经度 6 位小数外加整数部分很容易在边界位置出现 0.000001 级的舍入偏差。DECIMAL 是精确存储地理坐标用它是安全的。4.3 常用查询与 haversine 距离排序导入后先做两个索引create index idx_cities_state_pop on cities(state, population desc); create index idx_cities_city_state on cities(city, state);第一个索引服务“按州查热门城市”第二个服务“按城市名查重名”。然后是常用查询按州聚合人口和城市数量以及 haversine 距离排序。select state, count(*) as city_count, sum(population) as total_pop from cities group by state order by city_count desc;两城市距离计算直接丢到 SQL 里做select c1.city, c2.city, round(6371 * 2 * asin(sqrt( power(sin(radians(c2.lat - c1.lat) / 2), 2) cos(radians(c1.lat)) * cos(radians(c2.lat)) * power(sin(radians(c2.lng - c1.lng) / 2), 2) )), 1) as distance_km from cities c1 join cities c2 on c1.city San Francisco and c2.city Los Angeles;6371 是地球半径单位公里。asin 段是球面余弦公式的数值稳定版本比直接 arccos 更抗舍入误差。运行前先确认 c1 和 c2 不是同一个主键否则同城两行距离返回 0。距离计算在百万行表上全表笛卡尔积会爆炸务必像上面这样先把 c1 过滤成单条再 join。5. 进阶不建库用 DuckDB 直查 CSV以及三个高频踩坑点如果只是临时查一下数据分布不值得建库、建表、导数据再删表。把这份 CSV 用 DuckDB 当外部表直接查是更快的方案DuckDB 支持面向列存的矢量化执行百万级城市 CSV 的 group by 聚合通常几十毫秒能完成。5.1 用 DuckDB 直接读 CSV 做聚合duckdb进入交互式命令行后直接执行select state, count(*) as city_count from read_csv(data/cities.csv, header true) group by 1 order by 2 desc limit 5;read_csv 是表函数调用传入文件路径即可不要求先建 schema。header true 表示首行是列名。DuckDB 会自动推断列类型lat、lng 会被识别为 DOUBLE。后面接普通 SQLgroup by 序号 1 指 state 列。这个查询适合快速验证 CSV 的状态分布和城市总量不用经过任何导入步骤。5.2 三个经常被踩的点全部避开再上线第一个坑是坐标列的负号丢失。很多 CSV 在 Excel 里被重新保存过lng 的负号可能被当作普通文本导出时把-74.0060变成-和74.0060两列或者直接丢掉。入库后查西海岸城市会全部落到中国或中亚。规避办法是入库前对 lat、lng 做一次数值范围校验任何lat 90或lng -180的记录直接挡在载入脚本外面。第二个坑是重名城市被业务层误判为同一条数据。城市名加州名做复合索引后业务查询仍要区分展示场景下拉框需要展示州名后缀API 返回至少携带city_state拼接字段不要在查询后才靠前端判断。第三个坑是 LICENSE 缺失。GitHub 仓库的 zip 里如果没有 LICENSE 文件数据来源不明商用前换用 GeoNames 或其他有明确授权的数据源。判断方法很简单解压后看根目录有没有 LICENSE 文件没有就走人。最后放一条入库前扫脏命令任何数据库都能用结束这一步就可以放心把表连进接口了select count(*) from cities where lat between -90 and 90 and lng between -180 and 180;正常结果应该等于总行数。出现差值把不满足条件的记录导出来逐条看脏数据不会自愈越早暴露成本越低。本文还有配套的精品资源点击获取
返回列表