1. 项目背景与核心价值
图书馆作为城市文化基础设施,每周都会举办大量公益讲座、读书会、手工坊等活动。这些活动信息分散在各个分馆页面,市民需要逐个浏览才能获取完整信息。去年我帮本地读书会整理活动资料时,手动复制粘贴了整整三天,期间还遇到网页改版导致数据丢失的问题。这个经历让我意识到:用Python构建一个自动化采集系统不仅能节省时间,更能保证数据的完整性和时效性。
这个系统主要解决三个痛点:
- 信息碎片化:活动分散在10余个分馆网站
- 更新不及时:人工采集总有延迟
- 格式不统一:各馆页面结构差异大
通过本次实战,你将掌握:
- 动态网页内容的抓取技巧
- 反爬虫策略的应对方案
- 数据清洗的常见处理方法
- 结构化存储的最佳实践
2. 技术选型与工具链
2.1 核心工具对比
在爬虫领域,工具选型直接影响开发效率和运行稳定性。经过多次实测,我最终确定的工具组合如下:
| 工具 | 版本 | 选用理由 | 替代方案 |
|---|---|---|---|
| Requests | 2.31.0 | 处理静态内容效率高,社区支持完善 | urllib3 |
| BeautifulSoup | 4.12.0 | 对畸形HTML容错性强,学习曲线平缓 | PyQuery |
| Selenium | 4.10.0 | 解决动态渲染问题,支持浏览器自动化 | Playwright |
| Pandas | 2.0.3 | 数据清洗和导出一站式解决方案 | OpenPyXL |
| ChromeDriver | 114.0.5 | 与主流Chrome版本兼容性好 | GeckoDriver |
提示:建议固定这些库的版本,避免后续更新导致的API变更问题。可以用
pip freeze > requirements.txt生成依赖文件。
2.2 反爬虫策略应对
图书馆网站虽然没有刻意设置反爬机制,但我们在开发时仍要遵循最佳实践:
- 请求间隔:使用
time.sleep(random.uniform(1, 3))模拟人工操作 - 请求头伪装:必须包含完整的headers,特别是:
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.library.gov.cn/' } - IP轮换:如果采集量很大,建议使用代理池(本项目不需要)
- 异常处理:对HTTP 429/503状态码要有重试机制
3. 系统架构设计
3.1 采集流程分解
整个系统采用模块化设计,主要流程分为四个阶段:
URL管理器
- 维护待抓取URL队列
- 记录已抓取URL集合
- 处理分页逻辑
下载器
- 静态页面:Requests+BS4组合
- 动态内容:Selenium模拟点击
- 实现自动重试机制
解析器
- 使用CSS选择器定位元素
- 处理三种常见页面结构
- 日期时间标准化
存储器
- 数据去重(基于活动ID)
- 异常数据过滤
- CSV和JSON双输出
3.2 核心类结构
class LibrarySpider: def __init__(self): self.url_manager = UrlManager() self.downloader = Downloader() self.parser = Parser() self.storage = Storage() def run(self, start_url): # 主控制流程 pass class UrlManager: def add_new_urls(self, urls): # URL去重逻辑 pass class Downloader: def download(self, url): # 包含异常处理的重试逻辑 pass class Parser: def parse(self, html): # 多页面结构适配 pass class Storage: def save(self, data): # 数据持久化 pass4. 关键实现细节
4.1 动态内容抓取实战
当遇到通过JavaScript加载的活动列表时,常规请求无法获取完整内容。这时需要启动浏览器自动化:
from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def get_dynamic_content(url): chrome_options = Options() chrome_options.add_argument('--headless') # 无界面模式 driver = webdriver.Chrome(options=chrome_options) try: driver.get(url) # 显式等待"加载更多"按钮出现 wait = WebDriverWait(driver, 10) load_more = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, '.load-more')) ) load_more.click() time.sleep(2) # 等待数据加载 return driver.page_source finally: driver.quit()注意:Selenium执行效率较低,应该只在必要时使用。可以先尝试分析网页的XHR请求,直接调用API接口获取数据。
4.2 数据清洗技巧
图书馆活动数据常见的脏数据问题及处理方法:
日期格式混乱
- 原始数据:"本周六下午3点"、"2023/08/12"、"8月15日"
- 解决方案:
from dateparser import parse def normalize_date(text): return parse(text, settings={'TIMEZONE': 'Asia/Shanghai'})
地址信息缺失
- 处理规则:优先使用活动详情页地址,其次使用分馆默认地址
- 实现代码:
def fill_address(event): if not event.get('address'): event['address'] = BRANCH_DEFAULT_ADDRESS.get(event['branch']) return event
活动人数限制
- 清洗逻辑:提取数字部分,无效值设为-1
- 正则表达式:
import re def clean_quota(text): match = re.search(r'限(\d+)人', text) return int(match.group(1)) if match else -1
5. 数据存储优化
5.1 CSV导出进阶技巧
使用Pandas进行数据导出时,这几个参数能显著提升文件质量:
df.to_csv( 'library_events.csv', index=False, # 不保存行索引 encoding='utf_8_sig', # 支持Excel中文显示 quoting=csv.QUOTE_NONNUMERIC, # 非数字字段加引号 date_format='%Y-%m-%d %H:%M' # 统一日期格式 )5.2 数据去重策略
为避免同一活动被多次采集,采用复合主键去重:
def deduplicate(events): seen = set() unique_events = [] for event in events: # 使用标题+日期+地点作为唯一标识 key = (event['title'], event['start_time'], event['branch']) if key not in seen: seen.add(key) unique_events.append(event) return unique_events6. 完整代码结构
以下是核心组件的实现框架:
# config.py BASE_URL = 'https://www.library.gov.cn' BRANCHES = ['hongkou', 'pudong', 'xuhui'] # 分馆路径 HEADERS = {...} # spider.py class LibrarySpider: def __init__(self): self.session = requests.Session() self.session.headers.update(HEADERS) def crawl_branch(self, branch): url = f"{BASE_URL}/{branch}/events" html = self.download(url) events = self.parse_events(html) return self.process_events(events) def download(self, url, retry=3): # 实现下载逻辑 pass def parse_events(self, html): # 实现解析逻辑 pass # storage.py class DataStorage: @staticmethod def save_csv(events, filename): # 实现CSV导出 pass @staticmethod def save_json(events, filename): # 实现JSON导出 pass7. 常见问题排查
7.1 元素定位失败
现象:CSS选择器无法找到目标元素解决方案:
- 检查网页是否已完全加载(特别是SPA应用)
- 使用浏览器开发者工具验证选择器
- 尝试更宽松的选择器,如
div.event-item而不是div.col-md-8 > div.event-item
7.2 中文乱码问题
现象:保存的CSV文件用Excel打开是乱码解决方法:
- 确保写入时指定
encoding='utf_8_sig' - 或者在Excel中通过"数据→获取外部数据→从文本"导入,选择65001编码
7.3 动态加载超时
现象:Selenium等待元素超时优化方案:
# 增加超时时间并添加异常处理 try: element = WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.ID, "content")) ) except TimeoutException: print("加载超时,尝试备用方案") # 执行备用解析逻辑8. 项目扩展方向
这个基础框架可以进一步扩展为更完善的系统:
自动化部署
- 使用Scrapy框架重构代码
- 添加定时任务(APScheduler)
- 部署到云服务器
数据可视化
- 用Matplotlib生成活动热度日历图
- 按活动类型统计分布
- 构建活动时间分布直方图
通知服务
- 对接邮件/微信消息推送
- 设置关键词订阅提醒
- 实现活动冲突检测
在实际运行中,这个系统每周能自动采集约120-150场活动信息,相比人工采集效率提升约20倍。最重要的是,数据的准确性和完整性得到了保证,为后续分析提供了可靠的基础。