在数据驱动的时代,获取并理解公共数据已成为技术决策和商业分析的关键环节。12306作为中国铁路客运服务的核心系统,其背后庞大的城市站点数据不仅对旅行规划至关重要,更是观察中国城市化进程和交通网络布局的独特窗口。本文将带领您构建一个完整的技术工具链,从数据爬取、解析处理到最终的可视化呈现,全方位挖掘12306城市数据的价值。
技术架构与工具选型
在开始实现之前,我们先规划整个技术栈:
● 数据获取层:使用 requests 模块处理HTTP请求
● 数据处理层:利用 json 和 pandas 进行数据清洗与结构化
● 数据存储层:采用轻量级的SQLite数据库
● 可视化层:基于 pyecharts 创建交互式地理图表
这种分层架构确保了各模块的职责单一,同时保持了整个流程的连贯性和可维护性。
第一阶段:数据爬取 - 精准获取城市JSON
12306的城市数据通过一个特定的接口提供,我们需要模拟浏览器请求来获取这些数据。
import requests
import json
import re
from typing import Dict, Any
class CityDataCrawler:
def init(self):
self.session = requests.Session()
# 设置请求头,模拟浏览器行为
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://www.12306.cn/index/'
}
# 代理配置
self.proxyHost = "www.16yun.cn"
self.proxyPort = "5445"
self.proxyUser = "16QMSOML"
self.proxyPass = "280651"
# 设置代理
self._setup_proxy()
def _setup_proxy(self):
"""设置代理配置"""
proxy_url = f"http://{self.proxyUser}:{self.proxyPass}@{self.proxyHost}:{self.proxyPort}"
self.proxies = {
'http': proxy_url,
'https': proxy_url
}
# 为session设置代理
self.session.proxies.update(self.proxies)
def get_city_data(self) -> Dict[str, Any]:
"""
获取12306城市数据
返回字典格式的城市信息
"""
try:
# 12306城市数据接口
url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9270"
# 使用代理发送请求
response = self.session.get(
url,
headers=self.headers,
timeout=10,
verify=False # 如果代理有SSL证书问题,可以暂时关闭验证
)
response.encoding = 'utf-8'
if response.status_code == 200:
return self._parse_raw_data(response.text)
else:
print(f"请求失败,状态码:{response.status_code}")
return {}
except requests.exceptions.ProxyError as e:
print(f"代理连接错误:{e}")
return {}
except requests.exceptions.ConnectTimeout as e:
print(f"连接超时:{e}")
return {}
except requests.exceptions.RequestException as e:
print(f"请求异常:{e}")
return {}
except Exception as e:
print(f"获取数据时发生错误:{e}")
return {}
def _parse_raw_data(self, raw_text: str) -> Dict[str, Any]:
"""
解析原始数据,提取城市信息
"""
# 使用正则表达式提取车站数据
pattern = r"@[a-z]+\|([^\|]+)\|([a-z]+)\|([a-z]+)\|([a-z]+)\|([a-z]+)\|([0-9]+)\|([a-z]+)"
matches = re.findall(pattern, raw_text)
cities = {}
for match in matches:
city_data = {
'name': match[0], # 中文站名
'code': match[1], # 车站代码
'pinyin': match[2], # 拼音
'abbr': match[3], # 缩写
'number': match[6] # 编号
}
cities[city_data['code']] = city_data
print(f"成功解析 {len(cities)} 个车站数据")
return cities
def test_proxy_connection(self):
"""测试代理连接是否正常"""
test_url = "http://httpbin.org/ip"
try:
response = self.session.get(test_url, timeout=5)
if response.status_code == 200:
print("代理连接测试成功")
print(f"当前IP信息:{response.text}")
return True
else:
print("代理连接测试失败")
return False
except Exception as e:
print(f"代理测试异常:{e}")
return False
执行爬取
if name == "main":
crawler = CityDataCrawler()
# 测试代理连接
print("正在测试代理连接...")
if crawler.test_proxy_connection():
print("代理连接正常,开始爬取数据...")
city_data = crawler.get_city_data()
if city_data:
print(f"数据爬取完成!共获取 {len(city_data)} 个城市数据")
# 打印前5个城市作为示例
for i, (code, city) in enumerate(list(city_data.items())[:5]):
print(f"{i+1}. {city['name']} - 代码: {code}")
else:
print("数据爬取失败!")
else:
print("代理连接失败,请检查代理配置")
第二阶段:数据解析与存储 - 构建结构化数据体系
获得原始数据后,我们需要进行数据清洗、结构化,并存储到数据库中以便后续分析。
import pandas as pd
import sqlite3
from datetime import datetime
class DataProcessor:
def init(self, city_data: Dict[str, Any]):
self.city_data = city_data
self.df = None
def create_dataframe(self):
"""将城市数据转换为DataFrame"""
records = []
for code, info in self.city_data.items():
records.append({
'station_code': code,
'station_name': info['name'],
'pinyin': info['pinyin'],
'abbreviation': info['abbr'],
'station_number': info['number']
})
self.df = pd.DataFrame(records)
print(f"创建DataFrame成功,共 {len(self.df)} 条记录")
# 数据质量检查
self._data_quality_check()
def _data_quality_check(self):
"""执行数据质量检查"""
print("\n=== 数据质量报告 ===")
print(f"总记录数: {len(self.df)}")
print(f"空值统计:")
print(self.df.isnull().sum())
print(f"重复车站数: {self.df.duplicated('station_code').sum()}")
print("====================\n")
def save_to_sqlite(self, db_path: str = "12306_cities.db"):
"""保存数据到SQLite数据库"""
try:
conn = sqlite3.connect(db_path)
# 创建数据表
create_table_sql = """
CREATE TABLE IF NOT EXISTS railway_stations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
station_code TEXT UNIQUE NOT NULL,
station_name TEXT NOT NULL,
pinyin TEXT,
abbreviation TEXT,
station_number TEXT,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
conn.execute(create_table_sql)
# 保存数据
self.df.to_sql('railway_stations', conn, if_exists='replace', index=False)
conn.commit()
conn.close()
print(f"数据已保存到数据库: {db_path}")
except Exception as e:
print(f"数据库操作失败: {e}")
def get_regional_statistics(self):
"""生成区域统计信息"""
if self.df is not None:
# 简单的统计分析
stats = {
'total_stations': len(self.df),
'stations_per_province': self._count_stations_by_province()
}
return stats
return {}
def _count_stations_by_province(self):
"""按省份统计车站数量(简化版本)"""
# 在实际应用中,这里需要更复杂的地理编码逻辑
province_keywords = ['北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都']
province_count = {}
for keyword in province_keywords:
count = self.df[self.df['station_name'].str.contains(keyword)].shape[0]
if count > 0:
province_count[keyword] = count
return province_count
数据处理流程
processor = DataProcessor(city_data)
processor.create_dataframe()
processor.save_to_sqlite()
输出统计信息
stats = processor.get_regional_statistics()
print("区域统计信息:", stats)
第三阶段:数据可视化 - 让数据说话
数据只有通过可视化才能真正展现其价值。我们将使用pyecharts创建交互式的地理分布图。
from pyecharts import options as opts
from pyecharts.charts import Map, Bar, Page
from pyecharts.globals import ThemeType
import random
class DataVisualizer:
def init(self, df):
self.df = df
def create_station_distribution_map(self):
"""创建车站分布地图"""
# 模拟各省份车站数量(实际项目中应从准确的地理编码获取)
province_data = [
("北京市", 15), ("天津市", 12), ("河北省", 45),
("山西省", 38), ("内蒙古自治区", 28), ("辽宁省", 42),
("吉林省", 35), ("黑龙江省", 40), ("上海市", 18),
("江苏省", 55), ("浙江省", 48), ("安徽省", 42),
("福建省", 38), ("江西省", 36), ("山东省", 52),
("河南省", 58), ("湖北省", 45), ("湖南省", 44),
("广东省", 62), ("广西壮族自治区", 40), ("海南省", 12),
("重庆市", 25), ("四川省", 65), ("贵州省", 35),
("云南省", 42), ("西藏自治区", 8), ("陕西省", 38),
("甘肃省", 32), ("青海省", 18), ("宁夏回族自治区", 12),
("新疆维吾尔自治区", 28), ("台湾省", 15)
]
distribution_map = (
Map(init_opts=opts.InitOpts(
theme=ThemeType.ROMA,
width="1200px",
height="600px"
))
.add(
series_name="车站数量",
data_pair=province_data,
maptype="china",
is_map_symbol_show=False,
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="12306全国铁路车站分布图",
subtitle="数据来源:12306官方",
pos_left="center"
),
visualmap_opts=opts.VisualMapOpts(
min_=0,
max_=70,
is_calculable=True,
orient="horizontal",
pos_left="center",
pos_bottom="10%",
range_color=["#B0E0E6", "#1E90FF", "#0000CD"]
),
legend_opts=opts.LegendOpts(is_show=False)
)
.set_series_opts(
label_opts=opts.LabelOpts(is_show=True)
)
)
return distribution_map
def create_top_cities_chart(self):
"""创建主要城市车站数量柱状图"""
# 基于实际数据生成主要城市统计
major_cities = ['北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都', '西安', '重庆']
city_counts = []
for city in major_cities:
count = len(self.df[self.df['station_name'].str.contains(city)])
city_counts.append(count)
bar_chart = (
Bar(init_opts=opts.InitOpts(
theme=ThemeType.ROMA,
width="1000px",
height="500px"
))
.add_xaxis(major_cities)
.add_yaxis(
"车站数量",
city_counts,
itemstyle_opts=opts.ItemStyleOpts(color="#5470C6")
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="主要城市铁路车站数量统计",
pos_left="center"
),
xaxis_opts=opts.AxisOpts(
axislabel_opts=opts.LabelOpts(rotate=45)
),
yaxis_opts=opts.AxisOpts(
name="车站数量"
)
)
)
return bar_chart
def generate_dashboard(self):
"""生成完整的数据仪表板"""
page = Page(layout=Page.SimplePageLayout)
# 添加各个图表
page.add(
self.create_station_distribution_map(),
self.create_top_cities_chart()
)
# 渲染为HTML文件
page.render("12306_cities_dashboard.html")
print("可视化仪表板已生成: 12306_cities_dashboard.html")
执行可视化
visualizer = DataVisualizer(processor.df)
visualizer.generate_dashboard()
技术深度解析与业务价值
- 反爬虫策略应对
在数据爬取阶段,我们采用了完整的请求头模拟,包括User-Agent和Referer,这对于绕过基础的反爬虫机制至关重要。在实际生产环境中,可能需要进一步处理IP轮换、验证码识别等复杂情况。 - 数据质量保障
通过实现数据质量检查流程,我们能够及时发现数据缺失、重复等问题,确保后续分析的准确性。这是工业级数据管道不可或缺的环节。 - 可视化技术选型
选择pyecharts而非matplotlib等传统库,是因为其出色的交互性和对中文的良好支持。用户可以通过悬停、缩放等操作与图表深度交互,获得更丰富的数据洞察。
应用场景与扩展方向
这个完整的工具链不仅限于技术演示,在实际业务中具有广泛的应用价值:
● 城市规划分析:通过车站分布密度了解区域交通发展水平
● 商业选址支持:为物流、零售等行业提供交通便利性参考
● 旅游产品开发:基于铁路网络优化旅行路线规划
● 投资决策辅助:分析交通基础设施建设的区域重点
总结
通过本文的完整实现,我们构建了一个从数据采集到价值呈现的端到端技术解决方案。这个工具链展示了现代数据工程的核心理念:自动化、结构化、可视化。每个技术环节都经过精心设计,既保证了功能的完整性,又为后续扩展留出了充足空间。