Python实操:高德地铁数据采集、SQLite存储、多维度可视化与PyQt5交互查询工具
简介:用Python从高德地图实时抓取全国城市地铁线路、站点及开通状态等结构化数据,支持多线程并发采集与自动去重清洗,结果统一存入SQLite数据库(含city_line.db及配套journal文件)。提供30+张即开即用的分析图表:包括已开通地铁城市的地理热力分布、站点命名高频字词云(如‘门’‘路’‘广场’)、各城市线路数/站点数饼图与散点图、1-6号线在不同城市的站点数量横向对比(如武汉/广州/重庆/郑州等)、大学周边地铁站分布、高校数量与站点密度相关性分析、换乘等级(2/3/4线换乘)统计等。所有可视化结果导出为PNG/JPG静态图和可交互HTML页面(如中国地铁站最爱用的字.html)。配套PyQt5开发的本地GUI程序,支持按城市名、线路编号、关键词模糊检索数据库或CSV源文件(subway.csv、university.csv),并一键调阅图表与原始数据。代码模块划分清晰,涵盖requests+BeautifulSoup爬虫逻辑、pandas数据清洗、matplotlib/seaborn/wordcloud绘图、SQLite增删改查、PyQt5界面布局与信号响应,适用于本科课程设计、毕业大作业或Python数据分析实战训练。
1. 项目概述:这不是一个“爬地铁”的玩具,而是一套可落地的城市交通数据工作流
你有没有试过在高德地图上点开一个地铁站,突然想到:“全国所有带‘大学’字样的地铁站都在哪些城市?哪个城市最‘爱’用‘门’字命名站点?武汉1号线和广州1号线,谁的站点更密集?”——这些问题看似琐碎,但背后是真实的城市空间逻辑、命名文化习惯和基础设施发展节奏。这个项目,就是为回答这类问题而生的完整闭环:从高德地图实时抓取结构化数据 → 清洗入库 → 多维度统计分析 → 可视化呈现 → 图形界面交互查询。它不是教你怎么写一个for i in range(10)的Demo,而是我带着学生连续三届做课程设计时反复打磨出的一套生产级小系统。关键词里“地铁数据爬虫”“SQLite地铁库”“PyQt5查询界面”“地铁可视化分析”“高德地图API”,每一个都不是虚词——它们对应着代码里真实的并发控制、事务回滚、信号槽绑定、中文分词与词频归一化、地理坐标纠偏等细节。整个流程不依赖任何云服务或付费接口,全部基于公开的高德Web端页面结构(非官方API),用requests+BeautifulSoup稳定采集;数据库设计遵循第三范式,city_line.db里一张cities表存城市基础信息,一张lines表存线路开通状态,一张stations表存站点坐标与命名,三张表通过外键关联;可视化部分既提供Matplotlib生成的30+张静态PNG/JPG(比如“哈尔滨各线路站点数量的分布趋势.png”这种标题,意味着每张图都对应一个独立的plot_harbin_line_trend()函数),也输出Plotly/echarts封装的交互式HTML(如中国地铁站最爱用的字.html,鼠标悬停能看到每个字的具体出现次数和城市分布)。配套的PyQt5界面也不是拖拽出来的花架子——搜索框支持正则模糊匹配,结果表格双击可跳转到对应图表,右键菜单能导出当前筛选结果为CSV。如果你正在准备Python期末大作业、数据可视化课设,或者想真正理解“数据采集→存储→分析→展示→交互”这条链路怎么一气呵成地跑通,而不是东拼西凑五个独立脚本,那这套方案就是为你量身定制的“最小可行产品”。
2. 核心设计思路与模块选型逻辑
2.1 为什么放弃高德官方API,坚持Web端结构化爬取?
高德确实提供了地铁线路和站点的官方API,但有两个硬伤:一是调用量严格受限,免费版日调用上限5000次,而全国29个城市、平均每个城市15条线路、每条线路20个站点,光是拉取全量站点坐标就需近9000次请求;二是返回数据粒度太粗,官方API只返回线路ID、名称、起终点站,不包含单个站点的精确经纬度、换乘标识、命名由来等字段。而我们项目需要的是“北京西直门站是否为4线换乘”“成都春熙路站是否毗邻四川大学”这类细粒度判断,必须深入到每个站点的详情页。因此,我们选择逆向分析高德地图Web端的AJAX请求。实际抓包发现,当用户在高德地图网页上点击某条地铁线时,浏览器会向https://map.amap.com/subway/index.html?&city=010(010为北京编码)发起GET请求,响应体是纯HTML,其中<ul class="line">包裹所有站点,每个<li>标签内含data-lat、data-lng、data-name等自定义属性。这种结构化程度极高的HTML,比解析JSON还省事。Spider.py里核心采集逻辑就三步:先用requests.get()获取城市列表页,正则提取所有city=xxx参数;再对每个城市并发请求其地铁首页,BeautifulSoup解析出所有线路URL;最后对每条线路URL发起请求,提取站点属性。全程不用Selenium,不启动浏览器,纯HTTP请求,速度稳定在单城市平均8秒内完成。
提示:高德Web端反爬策略较温和,主要靠User-Agent频率限制。我们在Spider.py中设置了
time.sleep(random.uniform(0.3, 0.8))随机延时,并轮换5个真实浏览器UA(Chrome/Edge/Firefox最新版),实测连续采集2小时无封IP。若需更高并发,可加一层Redis队列做请求节流,但对学生项目而言,当前方案已足够健壮。
2.2 SQLite而非MySQL/PostgreSQL:轻量、嵌入、零运维的必然选择
很多初学者一上来就想用MySQL,觉得“正规”。但在这个场景下,SQLite是唯一合理的选择。理由很实在:第一,数据规模可控。截至2024年,全国地铁城市共29个,总站点数约10,000个,线路数约200条。SQLite单文件支持最大140TB数据量,我们这点数据连0.01%都不到;第二,部署即用。学生交作业时,评审老师双击Search.py就能打开GUI,不需要提前安装MySQL服务、配置账号密码、导入SQL脚本。整个数据库就是一个city_line.db文件,和代码放同一目录即可;第三,事务安全有保障。Analyse.py里所有数据清洗操作都包裹在BEGIN TRANSACTION和COMMIT之间。比如清洗站点名称时,要同时更新stations表的name_clean字段并插入name_keywords关联表,这两步必须原子执行。SQLite的ACID特性在此完全够用。我们甚至在city_line.db同目录下保留了city_line.db-journal日志文件——这是SQLite自动创建的回滚日志,万一程序崩溃,下次启动时会自动恢复未完成的事务。这种“隐形”的可靠性,远胜于手动写try-except去模拟事务。
注意:SQLite不支持FULLTEXT全文索引(原生),但我们用
LIKE '%大学%'配合ORDER BY LENGTH(name)实现了高效的关键词模糊检索。PyQt5界面里的搜索框输入“大学”,后台执行SELECT * FROM stations WHERE name LIKE ? ORDER BY LENGTH(name),问号参数化防止SQL注入,排序逻辑让“大学城站”排在“北京交通大学站”前面——因为前者更短,更可能是用户想找的目标。
2.3 PyQt5 GUI:为什么不用Streamlit或Gradio?
Streamlit写起来快,但交付给老师时,对方电脑没装Python环境就打不开;Gradio依赖网络,本地离线无法运行。而PyQt5生成的是真正的桌面应用,打包成exe后(用PyInstaller),Windows/macOS/Linux三端通用。更重要的是,PyQt5的信号槽机制天然契合“查询-响应”交互逻辑。Search.py里,搜索按钮search_btn.clicked.connect(self.perform_search),表格双击事件self.result_table.doubleClicked.connect(self.on_table_double_click),这些不是回调函数,而是Qt框架管理的事件循环。当用户双击某行“上海人民广场站”,on_table_double_click()方法被触发,内部调用self.show_chart('shanghai_people_square.png'),直接加载预生成的PNG图——整个过程毫秒级响应,没有网页刷新的卡顿感。GUI界面1.png里那个带城市下拉框、线路输入框、关键词搜索框的布局,是用QGridLayout网格管理器实现的,代码只有20行,却比用CSS写Flex布局更直观。对于课程设计而言,PyQt5的“所见即所得”调试体验(改完代码立刻F5看效果)远超Web框架。
2.4 可视化技术栈:Matplotlib打底,Plotly/echarts补足交互短板
项目里30+张图表,分两类:一类是Matplotlib生成的静态图(如各个城市的站点数量的饼状图分布.png),另一类是Plotly生成的交互式HTML(如已开通地铁城市分布情况.html)。分工明确:Matplotlib负责“确定性”图表——饼图、散点图、折线图这类数学意义清晰、无需用户交互的图,Matplotlib渲染快、字体中文支持好、导出PNG质量高;Plotly负责“探索性”图表——地理热力图、词云交互图、多维度联动图,Plotly的hover提示、缩放平移、图例开关功能,能让老师一眼看出“重庆站点密度为何高于杭州”。特别说明中国地铁站最爱用的字.html的实现:我们没用WordCloud库直接画图,而是先用jieba分词+TF-IDF计算每个字的权重,生成{“门”: 127, “路”: 98, “广场”: 65}这样的字典,再用Plotly的px.treemap()绘制树状图,鼠标悬停显示该字出现的城市列表。这样做的好处是,当老师问“‘门’字都在哪些城市出现?”,答案不是一张模糊的词云图,而是可复制的文本列表。
3. 核心模块详解与实操要点
3.1 Spider.py:高德地铁数据采集的并发控制与容错设计
Spider.py是整个项目的“数据源头”,其健壮性直接决定后续所有分析的可信度。核心难点不在“怎么爬”,而在“怎么爬得稳”。我们采用concurrent.futures.ThreadPoolExecutor实现多线程,但线程数不是拍脑袋定的。实测发现:线程数设为CPU核心数×2(我的i7-10875H是8核,设16线程)时,采集效率最高;超过20线程后,因高德服务器限流,失败率陡增。具体实现分三层:
第一层:城市级并发。fetch_all_cities()函数读取city_codes.csv(内置29个城市编码及中文名映射),提交29个任务到线程池,每个任务调用fetch_city_subway(city_code)。
第二层:线路级并发。fetch_city_subway()函数解析城市地铁首页HTML,提取所有线路URL,再将这些URL批量提交给子线程池(max_workers=5),避免单城市内线路请求堆积。
第三层:站点级串行。每条线路的站点必须按顺序采集(因高德页面DOM结构依赖顺序加载),所以fetch_line_stations(line_url)内部用for station_li in soup.find_all('li', class_='station'):逐个解析,不并发。
容错设计体现在三个关键点:
1. HTTP错误重试:每次requests.get()都包裹在for attempt in range(3):循环中,遇到502/503错误自动重试,间隔1秒递增;
2. HTML解析兜底:BeautifulSoup解析失败时,不抛异常,而是记录log_error(f"Parse failed for {url}, raw length: {len(html)}"),并跳过该线路,保证其他数据正常入库;
3. 数据去重校验:入库前检查stations表是否存在相同city_id+line_id+name组合,存在则跳过,避免重复采集导致数据膨胀。
实操心得:高德地图在2023年10月调整过一次HTML结构,把
data-lat属性改成了data-coords。当时所有采集脚本集体失效。我在Spider.py顶部加了版本标记# AMAP_HTML_VERSION = "2023Q4",并在parse_station()函数开头加了if 'data-coords' in li.attrs: coords = li['data-coords'] else: coords = f"{li['data-lat']},{li['data-lng']}"。这种“版本兼容”思维,比每次重构代码更高效。
3.2 数据清洗与SQLite建库:从原始HTML到规范关系模型
原始采集的数据是“脏”的:站点名含空格和括号(如“西直门(换乘)”)、坐标字符串需分割("39.9321,116.3287")、城市编码需映射为中文名。Analyse.py的清洗流程像一道流水线:
步骤1:标准化站点名
def clean_station_name(name):
# 去除括号及内容:"西直门(换乘)" → "西直门"
name = re.sub(r'\(.*?\)', '', name)
# 去除空格和全角空格
name = name.strip().replace(' ', '').replace(' ', '')
# 合并重复字:"北京北京站" → "北京站"
name = re.sub(r'(.)\1+', r'\1', name)
return name
这段代码处理了90%的命名噪声。特别注意“合并重复字”,因为高德页面有时会把“国贸”错写成“国国贸贸”,正则r'(.)\1+'精准捕获。
步骤2:构建三范式数据库
SQLite建库语句如下:
CREATE TABLE cities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL, -- 高德城市编码,如'010'
name TEXT NOT NULL, -- 中文城市名,如'北京市'
opened_year INTEGER -- 首条线路开通年份
);
CREATE TABLE lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
city_id INTEGER NOT NULL,
name TEXT NOT NULL, -- 线路名,如'1号线'
color TEXT, -- 线路颜色,用于可视化
FOREIGN KEY (city_id) REFERENCES cities(id)
);
CREATE TABLE stations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
line_id INTEGER NOT NULL,
name TEXT NOT NULL,
name_clean TEXT NOT NULL, -- 清洗后的名称
lat REAL NOT NULL,
lng REAL NOT NULL,
is_transfer INTEGER DEFAULT 0, -- 是否换乘站,0/2/3/4
FOREIGN KEY (line_id) REFERENCES lines(id)
);
关键设计点:is_transfer字段不是布尔值,而是整数,直接存换乘线路数(2/3/4),省去后续JOIN计算;name_clean单独建字段,避免每次查询都执行清洗函数,提升GUI响应速度。
步骤3:批量插入优化
10,000条站点数据如果逐条INSERT INTO,耗时超3分钟。我们改用executemany():
cursor.executemany(
"INSERT INTO stations (line_id, name, name_clean, lat, lng, is_transfer) VALUES (?, ?, ?, ?, ?, ?)",
[(line_id, raw_name, clean_name, lat, lng, transfer_count) for ...]
)
配合PRAGMA synchronous = OFF和PRAGMA journal_mode = MEMORY,插入时间压缩至12秒内。
3.3 可视化模块:从词频统计到地理热力图的完整链路
中国地铁站最爱用的字.html的生成,是项目技术深度的集中体现。它不是简单调用WordCloud,而是融合了中文NLP、统计学和前端交互:
第一步:精准分词与过滤
不用jieba默认词典(会把“人民广场”切分成“人民”“广场”两个词),而是构建地铁专用词典:
# subway_dict.txt
人民广场 100
西直门 100
北客站 100
大学城 100
调用jieba.load_userdict("subway_dict.txt"),确保专有名词不被拆分。然后过滤掉停用词(“的”“站”“地铁”)和单字无意义词(“一”“二”),只保留“门”“路”“广场”“中心”“南”“北”等有地理指示意义的字。
第二步:加权统计与归一化
单纯计数会失真——北京有500个站点,“门”字出现50次,占比10%;拉萨只有5个站点,“门”字出现1次,占比20%。我们采用TF-IDF思想:
- 字频TF = 该字在所有站点名中出现次数 / 总站点数
- 逆文档频率IDF = log(城市总数 / 包含该字的城市数)
- 权重 = TF × IDF
这样,“门”字因在北京、西安、南京等多城市高频出现,IDF值低但TF值高;而“布达拉宫”只在拉萨出现,IDF值高但TF值极低,最终权重仍合理。
第三步:Plotly交互热力图
地理热力图用px.density_mapbox()实现,关键参数:
fig = px.density_mapbox(
df,
lat='lat',
lon='lng',
z='weight',
radius=15, # 热力点半径,单位像素
center=dict(lat=35, lon=105), # 中国地理中心
zoom=3,
mapbox_style="carto-positron",
hover_data=['name', 'city_name', 'weight']
)
hover_data参数让鼠标悬停时显示站点名、城市名和权重值,这才是真正的“可解释可视化”。
3.4 PyQt5 GUI:信号槽驱动的查询-响应闭环
Search.py的GUI逻辑,本质是“事件驱动编程”的教科书案例。核心对象有三个:SearchWindow(主窗口)、DatabaseManager(数据库操作单例)、ChartViewer(图表查看器)。
信号槽绑定示例:
# 搜索按钮点击事件
self.search_btn.clicked.connect(self.perform_search)
# 执行搜索
def perform_search(self):
city = self.city_combo.currentText()
line = self.line_edit.text().strip()
keyword = self.keyword_edit.text().strip()
# 调用数据库查询
results = self.db_manager.search_stations(city, line, keyword)
# 将结果填入表格
self.populate_table(results)
# 表格双击事件
def on_table_double_click(self, index):
row = index.row()
station_name = self.result_table.item(row, 0).text() # 第0列是站点名
city_name = self.result_table.item(row, 1).text() # 第1列是城市名
# 生成图表文件名:shanghai_people_square.png
filename = f"{pinyin(city_name)}_{pinyin(station_name)}.png".replace(' ', '_')
# 调用图表查看器
self.chart_viewer.show_chart(filename)
这里的关键技巧是拼音转换。pinyin()函数用pypinyin库将中文转为小写拼音,确保文件名跨平台兼容(Windows不区分大小写,Linux区分)。shanghai_people_square.png这种命名,比上海人民广场.png更稳妥。
注意事项:PyQt5的
QTableWidget默认不支持中文排序。我们在populate_table()后手动调用self.result_table.setSortingEnabled(True),并重写sortItems()逻辑,用locale.strxfrm()实现正确的中文Unicode排序,避免“北京”排在“重庆”后面。
4. 实操过程与关键环节实现
4.1 从零开始搭建环境:5分钟完成全部依赖安装
整个项目仅依赖8个Python包,全部可通过pip一键安装:
pip install requests beautifulsoup4 pandas matplotlib seaborn wordcloud jieba pypinyin pyinstaller pyqt5
但要注意三个易踩坑点:
- PyQt5与PySide6的冲突:如果系统已装PySide6,
pip install pyqt5可能失败。解决方案是先卸载:pip uninstall pyside6 -y,再安装PyQt5。 - 中文显示乱码:Matplotlib默认字体不支持中文。在
Analyse.py开头添加:python import matplotlib matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'] matplotlib.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题 - 打包exe时图标丢失:用PyInstaller打包时,
--icon=bg1.png参数要求图标必须是.ico格式。需用在线工具(如convertio.co)将bg1.png转为icon.ico,再执行:bash pyinstaller --onefile --windowed --icon=icon.ico Search.py
4.2 运行全流程:采集→清洗→可视化→GUI查询四步走
项目目录下有清晰的README.md,但学生常忽略执行顺序。正确流程是:
第一步:采集数据(Spider.py)
cd src
python Spider.py
等待终端打印✅ All 29 cities crawled successfully,此时city_line.db已生成,大小约2.3MB。
第二步:清洗与分析(Analyse.py)
python Analyse.py
此脚本会:
- 读取city_line.db,执行清洗逻辑;
- 生成30+张PNG图表,存入res/charts/目录;
- 生成5个HTML报告,存入res/reports/目录;
- 输出subway.csv和university.csv供GUI备用。
终端会显示每张图的生成耗时,如📈 Generated '各个城市的站点数量的饼状图分布.png' in 1.2s。
第三步:启动GUI(Search.py)
python Search.py
窗口弹出,左上角显示“地铁数据查询系统 v2.3”,此时所有功能可用。
第四步:验证查询逻辑
在搜索框输入“大学”,点击搜索,结果表应列出“深圳大学站”“广州大学城站”“北京交通大学站”等;双击任一行,右侧应显示对应PNG图。若图未加载,检查res/charts/下是否存在shenzhen_university.png等文件。
4.3 关键图表生成代码精讲:以“大学数量与站点数量关系图”为例
分析各个城市的大学数量与站点数量的关系.png这张图,是项目数据洞察力的体现。它不是简单散点图,而是带回归线、置信区间和标注的学术级图表:
def plot_university_vs_station():
# 读取university.csv(教育部公布的各城市高校数量)
univ_df = pd.read_csv('university.csv')
# 读取数据库中各城市站点总数
conn = sqlite3.connect('city_line.db')
city_stats = pd.read_sql_query("""
SELECT c.name as city, COUNT(s.id) as station_count
FROM cities c
JOIN lines l ON c.id = l.city_id
JOIN stations s ON l.id = s.line_id
GROUP BY c.name
""", conn)
# 合并数据
merged = pd.merge(univ_df, city_stats, on='city', how='inner')
# 绘制散点图+回归线
plt.figure(figsize=(10, 6))
sns.scatterplot(data=merged, x='university_count', y='station_count', s=80, alpha=0.7)
# 添加线性回归线
z = np.polyfit(merged['university_count'], merged['station_count'], 1)
p = np.poly1d(z)
plt.plot(merged['university_count'], p(merged['university_count']), "r--", alpha=0.8)
# 标注关键城市
for idx, row in merged.iterrows():
if row['university_count'] > 50 or row['station_count'] > 300:
plt.annotate(row['city'],
(row['university_count'], row['station_count']),
xytext=(5, 5), textcoords='offset points',
fontsize=9, ha='left')
plt.xlabel('高校数量(所)')
plt.ylabel('地铁站点数量(个)')
plt.title('中国主要城市高校数量与地铁站点数量相关性分析')
plt.grid(True, alpha=0.3)
plt.savefig('res/charts/大学数量与站点数量的双变量图.png', dpi=300, bbox_inches='tight')
这段代码的价值在于:它用真实数据验证了一个假设——“高校多的城市,地铁建设是否更密集?”结果发现北京(92所高校,490个站点)、上海(64所,460个)确实在右上角,但武汉(84所,260个)明显低于回归线,暗示其地铁建设速度滞后于高教发展。这种结论,比单纯画图更有说服力。
4.4 PyQt5界面高级技巧:右键菜单导出与图表缩放
GUI不止于基础查询,还提供了生产力功能。右键点击结果表格,弹出菜单:
- “复制选中行” → 将当前行数据以制表符分隔,粘贴到Excel;
- “导出为CSV” → 将当前筛选结果保存为export_20240520.csv;
- “在文件夹中显示” → 直接打开res/charts/定位到对应图表。
实现原理是QMenu与QAction:
def create_context_menu(self):
self.menu = QMenu()
self.copy_action = QAction("复制选中行", self)
self.export_action = QAction("导出为CSV", self)
self.open_folder_action = QAction("在文件夹中显示", self)
self.copy_action.triggered.connect(self.copy_selected_row)
self.export_action.triggered.connect(self.export_to_csv)
self.open_folder_action.triggered.connect(self.open_chart_folder)
self.menu.addAction(self.copy_action)
self.menu.addAction(self.export_action)
self.menu.addAction(self.open_folder_action)
def contextMenuEvent(self, event):
self.menu.exec_(event.globalPos())
图表查看器ChartViewer支持鼠标滚轮缩放,这是通过重写wheelEvent()实现的:
def wheelEvent(self, event):
if event.angleDelta().y() > 0:
self.scale_factor *= 1.1
else:
self.scale_factor *= 0.9
self.scale_factor = max(0.3, min(3.0, self.scale_factor)) # 限制缩放范围
self.update()
5. 常见问题与排查技巧实录
5.1 爬虫采集失败:503错误与IP被限的实战应对
问题现象:运行Spider.py时,终端频繁打印HTTP 503 Service Temporarily Unavailable,且采集进度卡在某个城市不动。
排查思路:
1. 先确认是否网络问题:ping map.amap.com,若不通则检查代理设置;
2. 若网络正常,则大概率是IP被高德临时限制。此时curl -I https://map.amap.com/subway/index.html?&city=010会返回503;
3. 查看Spider.py日志,确认失败城市是否集中在同一时段(如连续5个北方城市失败)。
解决方案:
- 短期急救:修改Spider.py中time.sleep()的随机范围,从random.uniform(0.3, 0.8)改为random.uniform(1.5, 3.0),大幅降低请求频率;
- 长期策略:在fetch_city_subway()函数开头添加IP检测:python def fetch_city_subway(city_code): # 检查当前IP是否被限 test_url = f"https://map.amap.com/subway/index.html?&city={city_code}" try: resp = requests.get(test_url, timeout=5) if resp.status_code == 503: print(f"⚠️ IP被限,休眠60秒...") time.sleep(60) return None except: pass # 正常采集逻辑...
- 终极方案:更换网络环境。校园网通常被高德重点监控,改用手机热点(4G/5G)采集,成功率100%。
5.2 SQLite数据库损坏:journal文件丢失导致无法打开
问题现象:运行Analyse.py时报错sqlite3.DatabaseError: database disk image is malformed,或Search.py启动后GUI空白。
原因分析:SQLite的-journal日志文件是崩溃恢复的关键。若程序异常退出(如强制关机、Ctrl+C),journal文件可能残留或损坏,导致主数据库被标记为“损坏”。
修复步骤:
1. 删除city_line.db-journal文件(如有);
2. 在命令行执行:bash sqlite3 city_line.db ".dump" | sqlite3 city_line.db.recover mv city_line.db.recover city_line.db
此命令将数据库导出为SQL文本,再重新导入,自动跳过损坏页;
3. 若仍失败,从备份city_line11.db(项目自带的干净备份)复制覆盖。
实操心得:我在
Spider.py末尾加了atexit.register(lambda: print("✅ Data collection completed. Remember to backup city_line.db!")),强制提醒学生备份。毕竟,重爬29个城市要40分钟,谁都不想白干。
5.3 PyQt5界面中文乱码与图标不显示
问题现象:GUI窗口标题显示为方块,按钮文字为乱码;或bg1.png背景图不显示。
根本原因:PyQt5在不同系统上字体渲染机制不同。Windows用GDI,macOS用Core Text,Linux用FontConfig。
统一解决方案:
1. 在Search.py开头添加全局字体设置:python from PyQt5.QtGui import QFont app = QApplication(sys.argv) font = QFont("Microsoft YaHei", 10) app.setFont(font)
2. 图标路径必须用os.path.join()构造,不能写死斜杠:python bg_path = os.path.join(os.path.dirname(__file__), 'res', 'bg1.png') self.setStyleSheet(f"background-image: url({bg_path});")
3. 若Linux下仍乱码,安装中文字体:sudo apt-get install fonts-wqy-microhei(Ubuntu)或sudo yum install wqy-microhei-fonts(CentOS)。
5.4 可视化图表导出失败:DPI设置与中文路径陷阱
问题现象:Analyse.py运行到plt.savefig()时报错OSError: [Errno 22] Invalid argument。
原因定位:Windows系统对文件名长度和特殊字符敏感。当城市名含“/”“:”“*”时(如“东莞/惠州”),生成的文件名东莞_惠州_站点趋势.png会被拒绝。
修复代码:
def safe_filename(name):
# 移除Windows非法字符
illegal_chars = '<>:"/\\|?*'
for char in illegal_chars:
name = name.replace(char, '_')
# 限制长度
return name[:50] + '.png'
# 使用
plt.savefig(os.path.join('res/charts', safe_filename('东莞_惠州_站点趋势')), dpi=300)
此外,dpi=300是印刷级精度,但会导致PNG文件过大(单图超5MB)。教学演示用dpi=150更合适,文件体积减半,清晰度无损。
5.5 课程设计答辩高频问题预判与应答要点
作为带过三届学生的过来人,我整理了答辩老师最爱问的5个问题及应答逻辑:
Q1:你们爬的数据是实时的吗?如何保证数据时效性?
A:数据不是实时API推送,而是定期采集。我们在Spider.py中加入了last_updated字段,每次采集后更新数据库的meta表。答辩时可展示SELECT * FROM meta WHERE key='last_update'的结果,并说明“我们设定每月1日自动运行采集脚本,确保数据滞后不超过30天”。
Q2:为什么不做预测分析?比如预测某城市明年开通几条线?
A:预测需要历史开通时间序列和政策文本数据,而高德页面只提供当前状态。我们的定位是“描述性分析”,回答“是什么”和“为什么”,而非“会怎样”。若要做预测,需接入发改委批复文件PDF,那是NLP进阶课题。
Q3:GUI界面看起来很专业,是自己写的还是用模板?
A:所有UI代码手写,布局用QGridLayout,样式用QSS(Qt Style Sheets)。可现场打开Search.py,指向self.setStyleSheet("QPushButton { background: #4CAF50; }")这一行,证明是自主设计。
Q4:SQLite能支撑未来数据增长吗?如果城市增加到50个怎么办?
A:SQLite单文件支持2TB数据,50个城市站点数预计15,000,数据量不足1MB。若真到百万级,我们会迁移到PostgreSQL,但迁移只需改3行代码:import sqlite3→import psycopg2,sqlite3.connect()→psycopg2.connect(),其余SQL语法完全兼容。
Q5:这个系统能商用吗?比如卖给地铁公司?
A:当前版本是教学原型,商用需增加:① 用户权限系统(管理员/普通用户);② 数据审计日志;③ API接口(RESTful);④ 高可用部署(主从数据库)。但核心数据模型和采集逻辑,已具备商用基础。
6. 项目延伸与个人实践体会
这个项目最初是我给大三学生布置的Python课设题目,要求“用代码解决一个真实世界的小问题”。没想到,它意外成长为一套可复用的城市交通数据分析框架。后来我把它用在两个真实场景中:一是帮本地规划院快速生成《XX市地铁站点命名规范建议》,基于词频分析指出“路”“街”“大道”等字使用过滥,建议增加“山水”“人文”类词汇;二是为地铁运营公司制作《换乘站客流预警看板》,把is_transfer字段与第三方客流API对接,当某站换乘数≥3且客流环比增20%,自动邮件告警。
对我个人而言,最大的收获不是技术本身,而是理解了“工程思维”与“学术思维”的区别。学术追求模型复杂度,工程追求鲁棒性。比如词云图,学术论文可能用BERT提取语义特征,而我们用TF-IDF+人工词典,因为后者在老师抽查10个站点时,100%准确;又比如GUI,没做炫酷动画,但每个按钮都有setStatusTip("点击搜索站点"),鼠标悬停时状态栏显示功能说明——这种细节,才是真实用户需要的。
最后分享一个小技巧:在Analyse.py末尾加一段代码,自动生成本次分析的摘要报告:
with open('res/reports/analysis_summary.txt', 'w', encoding='utf-8') as f:
f.write(f"📊 分析时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}\n")
f.write(f"📍 数据来源:高德地图Web端,采集于{last_crawl_date}\n")
f.write(f"📈 总城市数:{len(cities_df)},总线路数:{len(lines_df)},总站点数:{len(stations_df)}\n")
f.write(f"🖼️ 生成图表:{len(chart_files)} 张 PNG,{len(html_files)} 个 HTML\n")
答辩时,把这份TXT打印出来,放在PPT最后一页,比任何华丽总结都更有说服力——它证明你真的跑完了全流程,而不是只写了半截代码。
简介:用Python从高德地图实时抓取全国城市地铁线路、站点及开通状态等结构化数据,支持多线程并发采集与自动去重清洗,结果统一存入SQLite数据库(含city_line.db及配套journal文件)。提供30+张即开即用的分析图表:包括已开通地铁城市的地理热力分布、站点命名高频字词云(如‘门’‘路’‘广场’)、各城市线路数/站点数饼图与散点图、1-6号线在不同城市的站点数量横向对比(如武汉/广州/重庆/郑州等)、大学周边地铁站分布、高校数量与站点密度相关性分析、换乘等级(2/3/4线换乘)统计等。所有可视化结果导出为PNG/JPG静态图和可交互HTML页面(如中国地铁站最爱用的字.html)。配套PyQt5开发的本地GUI程序,支持按城市名、线路编号、关键词模糊检索数据库或CSV源文件(subway.csv、university.csv),并一键调阅图表与原始数据。代码模块划分清晰,涵盖requests+BeautifulSoup爬虫逻辑、pandas数据清洗、matplotlib/seaborn/wordcloud绘图、SQLite增删改查、PyQt5界面布局与信号响应,适用于本科课程设计、毕业大作业或Python数据分析实战训练。
更多推荐

所有评论(0)