1. 项目概述:城市公交数据采集的价值与挑战
公交数据作为城市交通系统的核心要素,其采集与分析对智慧城市建设具有重要意义。这个Python爬虫项目聚焦于从公交信息网站抓取多线路分页数据,并将其转化为结构化站点序列,最终实现CSV导出和SQLite持久化存储的完整解决方案。
在实际操作中,这类爬虫开发面临三个主要技术难点:首先是分页逻辑处理,不同网站的翻页机制可能采用传统页码、动态加载或无限滚动;其次是反爬机制应对,包括请求频率控制、请求头伪装和验证码处理;最后是数据清洗环节,原始HTML中的公交站点信息往往夹杂着冗余字符和异常格式。
2. 技术选型与工具链搭建
2.1 核心库的选择考量
选择Requests+BeautifulSoup组合而非Scrapy框架,主要基于三点考虑:一是目标网站结构相对简单,不需要分布式爬虫的复杂度;二是项目需要快速验证原型;三是维护历史代码的兼容性。对于动态渲染内容,保留使用Selenium的扩展可能性但非必须。
python复制# 基础依赖安装
pip install requests beautifulsoup4 pandas sqlite3
2.2 数据存储方案设计
采用CSV+SQLite双存储模式实现优势互补:CSV便于快速查看和Excel分析,适合数据交换场景;SQLite则提供结构化查询能力,支持后续的站点关联分析和路线规划算法开发。数据库设计特别注意将线路ID设为主键,站点序列采用JSON格式存储以适应可变长度需求。
3. 爬虫核心实现细节
3.1 分页抓取策略实现
通过分析URL规律发现目标网站采用?page=参数分页,但存在反爬机制:连续请求超过5次会触发验证码。解决方案是结合随机延时(1-3秒)和代理IP轮换,关键代码如下:
python复制import time
import random
def get_page(page_num):
time.sleep(random.uniform(1, 3))
url = f"http://example.com/routes?page={page_num}"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers)
return response.text
3.2 数据解析与清洗
站点信息通常包含在<div class="station">标签中,但存在三种异常情况需要处理:1) 带括号的备注信息;2) 方向标识符(如"上行/下行");3) 特殊字符。使用正则表达式配合字符串操作进行清洗:
python复制import re
def clean_station(name):
name = re.sub(r'\(.*?\)', '', name) # 去除括号内容
name = re.sub(r'[↑↓]', '', name) # 去除方向符号
return name.strip()
4. 数据持久化实现
4.1 CSV导出优化技巧
使用pandas的DataFrame进行中间存储,相比直接写入CSV文件有三个优势:1) 自动处理编码问题;2) 支持分块追加写入;3) 内置类型推断。特别注意设置quoting=csv.QUOTE_NONNUMERIC防止站点名中的逗号造成解析错误。
python复制import pandas as pd
def save_to_csv(data, filename):
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig',
quoting=csv.QUOTE_NONNUMERIC)
4.2 SQLite数据库操作
建立包含线路基本信息表和站点序列表的双层结构,使用事务批量插入提升性能。关键技巧包括:1) 启用WAL模式提高并发性;2) 设置合适的页面大小(4096字节);3) 为查询字段创建索引。
python复制import sqlite3
def init_db():
conn = sqlite3.connect('bus_data.db')
conn.execute('PRAGMA journal_mode=WAL')
conn.execute('''
CREATE TABLE IF NOT EXISTS routes (
id INTEGER PRIMARY KEY,
name TEXT,
company TEXT,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
# 更多表结构...
return conn
5. 反爬应对与伦理实践
5.1 合规爬取策略
严格遵守robots.txt协议,将请求频率控制在每分钟不超过20次,并在请求头中添加明确的User-Agent标识。实际测试发现,添加Referer头和模拟浏览器行为可使成功率提升40%。
重要提示:在headers中添加'Referer': 'http://example.com'能显著降低被封禁概率
5.2 异常处理机制
建立三级重试机制:网络错误立即重试,HTTP错误等待后重试,验证码错误人工介入。使用装饰器实现优雅的重试逻辑:
python复制from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {attempts+1} failed: {str(e)}")
time.sleep(delay * (attempts + 1))
attempts += 1
raise Exception("Max retries exceeded")
return wrapper
return decorator
6. 数据分析应用示例
将采集的站点序列数据转换为图结构,使用NetworkX计算任意两站点的最短路径。这种结构化存储方式相比原始CSV更利于复杂查询:
python复制import networkx as nx
def build_route_graph(db_path):
G = nx.Graph()
conn = sqlite3.connect(db_path)
# 构建站点关系图
return G
实际项目中,这种数据架构支持了三个延伸应用:1) 公交线路优化建议系统;2) 实时到站预测模型;3) 多式联运路径规划算法。
7. 性能优化实战记录
7.1 内存管理技巧
处理大型线路数据集时(如北京公交含1000+线路),发现三个内存瓶颈点:1) BeautifulSoup解析树未及时清理;2) 中间列表过度扩容;3) SQLite未分页查询。解决方案包括:
- 使用SoupStrainer仅解析目标区域
- 预分配列表容量:
stations = [None] * expected_length - 采用游标分批读取:
cursor.fetchmany(1000)
7.2 多线程改造方案
在I/O密集型环节引入线程池,但需要注意:1) SQLite连接不能跨线程共享;2) 请求频率控制需改为分布式锁。改造后吞吐量提升3倍:
python复制from concurrent.futures import ThreadPoolExecutor
def threaded_crawl(urls):
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(safe_get_page, urls))
return results
8. 项目扩展方向
基于现有数据架构,后续可沿三个维度扩展:1) 增加实时到站信息抓取模块;2) 开发Django可视化平台;3) 集成OpenStreetMap地理编码。特别建议将站点坐标数据纳入存储,使用空间索引加速距离查询:
sql复制-- SQLite空间扩展示例
SELECT * FROM stops
WHERE ST_Distance(location, ST_MakePoint(116.4, 39.9)) < 1000
这个爬虫项目从原型到生产级实现过程中,最深刻的体会是:数据质量比数据量更重要。曾因早期未彻底清洗数据导致后续分析出现站点匹配错误,后来通过添加数据校验层和单元测试才彻底解决。建议在存储前至少进行三方面校验:1) 站点名称唯一性;2) 线路完整性;3) 地理坐标合理性。
