1. 项目概述为什么一个金融从业者要亲手写爬虫抓股价数据我做量化策略研究的第七年第一次在晨会现场被问住“你用的实时行情数据来源是哪家终端延迟多少毫秒快照时间戳怎么校准”——当时我支吾了三秒因为答案是我自己写的Python爬虫从交易所官网和主流财经门户定时抓取的。不是买不起Wind或同花顺L2而是很多小众标的、历史分时细节、甚至财报附注里的表格商业终端要么不提供要么价格高得离谱。这事儿说白了不是炫技是生存刚需。关键词里那个“Finance”就是整个项目的锚点。它决定了我们不能像普通网页爬虫那样只求“能拿到”而必须满足金融场景的硬性要求数据时效性分钟级更新、字段准确性小数点后四位不能错、结构稳定性财报表格列顺序不能乱、可追溯性每条数据必须带原始URL和抓取时间戳。我见过太多人用BeautifulSoup随便扒几个股票代码就跑回测结果发现某只ST股的涨跌幅字段在页面里被CSS隐藏了或者某家券商的行情页用了动态加载但没处理JavaScript渲染最后回测曲线漂亮得像PS出来的实盘一进场就爆仓。这个项目不是教你怎么“用Python爬网页”而是带你重建一套面向金融数据生产环境的轻量级采集系统。它不依赖任何付费API核心逻辑全部开源可控它不追求吞吐量每秒爬1000个页面但保证每个字段的提取逻辑经得起审计它甚至预留了和本地数据库、邮件告警、微信机器人对接的接口。如果你是刚入行的分析师想摆脱Excel手工复制粘贴如果你是个人投资者想监控冷门转债的溢价率变化或者你是风控岗同事需要定期比对不同信源的基金净值——那这套方案就是为你量身定做的最小可行数据管道。我试过三种主流方案纯RequestsBeautifulSoup适合静态页面、Selenium适合JS渲染但太重、Playwright新锐但学习成本高。最终选择RequestsBeautifulSoup作为基底不是因为它最先进而是因为它最“透明”——每个HTTP请求头、每次DOM解析路径、每个正则匹配规则都明明白白写在代码里出了问题你能立刻定位到是网站改版了还是自己正则写错了。金融数据容不得黑箱这点我踩过太多坑才明白。2. 整体架构设计与关键决策逻辑2.1 为什么放弃“全自动”而选择“半自动人工校验”流程很多人一上来就想做个“全自动监控系统”24小时跑着数据直接进数据库报表自动生成。听起来很美但金融数据的特殊性决定了这是危险的。我给你看个真实案例去年某天东方财富网的个股页面临时把“市盈率TTM”字段的HTML class从f14改成了f16持续了37分钟。我们的爬虫在这37分钟里把“市净率”的值错误地赋给了“市盈率”字段导致当天所有基于PE筛选的策略信号全错。如果系统是全自动闭环这37分钟的脏数据就会直接触发交易指令。所以我的架构核心是三层漏斗式设计采集层Raw Layer只做最基础的HTML下载和原始文本提取不做任何计算或转换。哪怕页面结构乱了这里也只存原始HTML快照和基础元数据URL、时间戳、HTTP状态码。这部分代码必须极简目标是“永远不报错”哪怕返回空数据也要记录日志。清洗层Clean Layer对采集层输出的原始数据进行字段校验、类型转换、异常值过滤。比如检查“最新价”是否为数字、是否在合理区间-1000到100000、是否与昨日收盘价偏差超过±9.9%触发ST股特殊逻辑。这里会生成清洗报告标出所有可疑字段。发布层Publish Layer只有通过清洗层所有校验的数据才允许写入最终数据库或发送给下游系统。发布前必须有人工确认环节——哪怕只是看一眼清洗报告里的红字警告。这个设计牺牲了“全自动”的便利性但换来了数据可信度。我给自己定了条铁律任何未经人工看过清洗报告的数据绝不参与实盘决策。这条规矩救过我两次。2.2 工具链选型为什么是RequestsBeautifulSoup而不是ScrapyScrapy确实更强大支持分布式、中间件、管道化处理。但它的复杂度在金融场景里反而是负担。举个例子Scrapy默认的并发请求数是16但你去爬雪球网的个股页面超过8个并发就会触发反爬机制返回403。这时候你得去写Downloader Middleware配置User-Agent轮换、IP代理池、请求间隔控制……而这些功能用原生Requests三行代码就能搞定import requests from time import sleep session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) # 控制并发每次循环后sleep 1秒 for url in urls: response session.get(url, timeout10) # 处理response... sleep(1)BeautifulSoup的DOM解析逻辑也比Scrapy的XPath/XPath更直观。金融页面的HTML结构往往很“野”同一个“总市值”字段在同花顺页面里是span classmarket-cap在新浪财经里是td总市值/td后面跟着td在东方财富里又变成div idgtzbsz。用BeautifulSoup你可以写三段独立的解析函数分别适配不同信源互不干扰。而Scrapy的Item Pipeline一旦定义好字段映射改起来就牵一发而动全身。更重要的是调试体验。当某个页面解析失败时你用RequestsBeautifulSoup可以立刻打印response.text[:500]看原始HTML再用soup.find(div, class_price)一步步试5分钟内就能定位问题。而Scrapy的调试需要启动crawler、看log、查spider output新手半小时都搞不定。在金融数据这种“今天出错明天就可能错过机会”的场景里调试速度就是生产力。2.3 数据存储策略为什么用SQLite而不直接上MySQL很多人觉得“金融数据必须上专业数据库”但实际需求远没那么复杂。我统计过自己过去一年的使用场景92%的查询是“查某只股票最近30天的收盘价”6%是“对比两只股票的市盈率走势”剩下2%才是复杂关联查询。这种读多写少、查询模式固定的场景SQLite的性能碾压MySQL——单表百万级数据SELECT * FROM stock_price WHERE symbol600519 ORDER BY date DESC LIMIT 30平均响应时间0.8毫秒MySQL要3.2毫秒。更关键的是部署成本。SQLite就是一个.db文件不用装服务、不用配用户权限、不用维护连接池。我把爬虫脚本和数据库文件打包成一个文件夹扔到公司内网服务器上运维同事双击start.bat就跑起来了。而MySQL部署光是解决“如何让Python脚本安全连接数据库”这个问题就得折腾SSL证书、防火墙端口、账号密码管理……这些额外工作对数据质量零贡献。当然SQLite有硬伤不支持多进程写入。所以我用了一个小技巧——所有写操作都通过一个单独的writer.py进程完成其他爬虫进程只负责采集和清洗把待写入的数据通过本地队列queue.Queue发给writer进程。这样既规避了并发写冲突又保持了架构简单。等哪天真需要支撑日均千万级数据量时再平滑迁移到PostgreSQL现在没必要为未来买单。3. 核心模块实现与金融特化细节3.1 采集层如何应对财经网站的反爬策略财经网站的反爬和电商、新闻站完全不同。它们不靠验证码而是用三招阴招第一招动态User-Agent指纹你以为换几个浏览器UA就行错。东方财富网会检测navigator.platform、screen.width等JS变量然后在HTML里埋一个隐藏字段input typehidden namefp valuexxx。如果你用Requests直接GET这个fp字段就是空的服务器一看就知道是爬虫。解决方案是用Selenium只加载一次登录页模拟真人操作拿到有效的fp值后后续所有请求都带上这个fp和对应的Cookie。我写了段精简代码from selenium import webdriver from selenium.webdriver.chrome.options import Options def get_valid_fp(): options Options() options.add_argument(--headless) driver webdriver.Chrome(optionsoptions) driver.get(https://www.eastmoney.com/) # 等待页面加载完fp字段 fp_input driver.find_element(name, fp) fp_value fp_input.get_attribute(value) cookies driver.get_cookies() driver.quit() return fp_value, cookies # 后续Requests请求带上 session.cookies.set(em_hq_fls, xxx, domain.eastmoney.com) session.headers.update({X-FP: fp_value})第二招时间窗口限流同花顺对同一IP的请求频率限制极严10秒内超过3次请求直接封IP 5分钟。但它的限制逻辑有个漏洞——只检查GET请求POST请求不限。所以我的策略是把所有“获取实时行情”的请求伪装成POST虽然服务器根本不处理POST bodyURL后面加个无意义参数?t123456789这样就能绕过它的GET频率检测。实测下来这个技巧让同花顺的采集成功率从42%提升到99.7%。第三招HTML结构随机化雪球网的页面会每天凌晨自动打乱DOM结构昨天div classquote-price今天变成div classq-p-2023class名带时间戳。硬编码class名必死。我的解法是用CSS选择器的通配符特性。比如找最新价不写soup.find(div, class_quote-price)而是写soup.select(div[class*price])利用*表示“包含字符串”。再结合位置关系——最新价永远在“涨跌幅”字段的上一个兄弟节点用sibling.previous_sibling定位比死磕class名可靠十倍。提示所有反爬对策都要写进配置文件而不是硬编码。我建了个anti_crawl_rules.yamleastmoney: fp_required: true delay: 2.5 method: POST sinajs: fp_required: false delay: 1.0 method: GET这样下次网站改版只需要改YAML不用碰核心代码。3.2 清洗层金融数据特有的校验逻辑清洗不是简单地float(price_str)而是要嵌入金融业务规则。我以“A股个股日线数据”为例列出必须执行的7项校验价格连续性校验今日收盘价与昨日收盘价偏差不能超过±10.01%A股涨停板规则。如果出现yesterday_close10.00, today_close11.02偏差10.2%必须标为异常。注意ST股是±5%要单独判断。成交量合理性校验A股主板股票单日成交量不能为0除停牌外也不能超过流通股本的300%。比如某股流通股本1亿股今日成交量显示3.5亿股大概率是数据源把“手”误当“股”了1手100股需要除以100修正。财务指标逻辑校验市盈率PE不能为负除非净利润为负但也不能无限大。我设了阈值PE 1000 或 PE -1000视为异常。同样市净率PB不能小于0.1净资产不可能这么低也不能大于50说明数据错位。日期格式强制统一不同信源日期格式五花八门——2023-05-24、2023/05/24、2023年05月24日、甚至24-May-2023。清洗层必须用dateutil.parser.parse()统一转成YYYY-MM-DD并验证是否为交易日调用本地交易日历CSV。字段缺失补全逻辑如果某信源没提供“换手率”但提供了“成交量”和“流通股本”就用turnover_rate volume / float_circulating_cap * 100计算补全。但必须标记为“计算值”和“原始值”区分开。多信源交叉验证对同一字段如“最新价”同时抓取东方财富、同花顺、新浪财经三个来源。如果其中两个一致第三个差异超过0.5%则采纳多数派并记录少数派为“需人工复核”。异常值标记而非删除清洗层从不直接删除数据而是添加is_dirty: true和dirty_reason: PE_out_of_range字段。这样发布层可以选择跳过也可以选择降权使用保留所有原始信息。这些规则不是凭空想的全部来自我整理的《A股数据异常案例库》里面收录了过去三年遇到的217个典型脏数据场景。比如2022年4月某天中证指数公司官网把“沪深300指数”的成分股权重数据错误地导出了“成分股数量”字段导致所有权重加起来是300而不是100%。这个案例就催生了第6条“多信源交叉验证”规则。3.3 发布层如何构建可审计的数据流水线发布不是INSERT INTO那么简单。真正的金融数据发布必须回答三个问题谁发布的什么时候发布的依据什么发布的我的解决方案是“三张表一个日志文件”表1raw_data原始数据表字段id, url, html_snapshot, http_status, fetch_time, fetch_by作用存每次抓取的原始HTML快照。fetch_by字段记录是哪个脚本如eastmoney_spider.py抓的方便溯源。表2clean_data清洗后数据表字段id, raw_id, symbol, date, open, high, low, close, volume, pe, pb, is_dirty, dirty_reason, clean_time, clean_by作用存清洗后的结构化数据。raw_id外键关联raw_data确保每条清洗数据都能回溯到原始HTML。表3publish_log发布日志表字段id, clean_id, publish_time, publish_by, status, comment作用记录每次发布的完整轨迹。status是枚举值pending(待审核)、approved(已批准)、rejected(已拒绝)、auto_published(自动发布仅用于测试数据)。comment字段必须填写人工审核意见比如“PE异常已联系信源方确认为临时数据错误”。日志文件publish_audit.log每次发布操作除了写数据库还会追加一行到文本日志[2023-05-24 09:15:22] USER: analyst_zhang APPROVED clean_id88232 for symbol600519, reasonPE28.32 matches Wind terminal这个日志文件每天压缩归档保存3年满足金融行业基本审计要求。注意所有数据库操作必须用事务包裹。比如一次发布涉及插入clean_data和publish_log两条记录必须在一个BEGIN TRANSACTION里完成避免数据不一致。我用的SQLAlchemy代码长这样with engine.begin() as conn: conn.execute(text(INSERT INTO clean_data (...) VALUES (...))) conn.execute(text(INSERT INTO publish_log (...) VALUES (...)))这套设计看起来繁琐但某次内部审计时合规部同事只花了15分钟就通过publish_log表查到了某条异常数据的完整生命周期——从哪个URL抓的、谁清洗的、谁批准的、批准时的备注是什么。他们当场就说“这个流程我们可以签字放行。”4. 实操全流程与关键参数详解4.1 从零开始搭建环境准备与依赖安装别跳过这一步。我见过太多人卡在环境配置上浪费半天时间。金融数据爬虫对环境有特殊要求必须用Python 3.9因为某些财经网站的TLS握手需要新版本SSL库必须禁用系统代理避免公司网络策略干扰必须指定中文编码防止财报PDF解析乱码。我的标准初始化脚本setup_env.pyimport sys import os import subprocess # 检查Python版本 if sys.version_info (3, 9): raise RuntimeError(Python 3.9 or higher is required) # 禁用系统代理关键 os.environ[HTTP_PROXY] os.environ[HTTPS_PROXY] os.environ[NO_PROXY] 127.0.0.1,localhost # 创建虚拟环境推荐避免包冲突 subprocess.run([sys.executable, -m, venv, venv]) # 激活虚拟环境Windows subprocess.run([venv\\Scripts\\activate.bat], shellTrue) # 安装核心依赖按此顺序避免版本冲突 requirements [ requests2.31.0, # 固定版本避免requests升级破坏Session行为 beautifulsoup44.12.2, lxml4.9.3, # 解析引擎比html.parser快3倍 pandas1.5.3, # 数据处理 pyyaml6.0.1, # 配置文件 dateutil2.8.2, # 日期解析 selenium4.8.3, # 只用于fp获取非主流程 chromedriver-autoinstaller0.4.0 # 自动下载ChromeDriver ] for pkg in requirements: subprocess.run([sys.executable, -m, pip, install, pkg])运行这个脚本后你会得到一个干净的虚拟环境。特别注意requests2.31.0这个版本——2.32.0修复了一个SSL证书验证bug但意外导致某些老财经网站如部分地方股交中心的HTTPS请求失败。这种细节只有踩过坑的人才知道。4.2 配置文件详解如何用YAML管理多信源所有网站的差异化配置都放在config/sources.yaml里。这不是简单的URL列表而是包含了金融业务规则的完整描述sources: - name: eastmoney base_url: https://quote.eastmoney.com # 动态参数symbol映射规则A股代码要加.SH/.SZ后缀 symbol_mapping: pattern: ^6\d{5}$ # 6开头的6位数 suffix: .SH # 反爬规则见2.3节 anti_crawl: fp_required: true delay: 2.5 method: POST # 字段提取规则用CSS选择器正则组合 fields: price: div.quote-price span change_percent: div.change-percent pe: td:contains(市盈率) td # 特殊处理PE字段可能带单位倍用正则清理 pe_clean_regex: ([\\d.])倍 - name: sinajs base_url: https://finance.sina.com.cn symbol_mapping: pattern: ^0\d{5}$|^\d{6}$ # 0开头或纯6位 suffix: .SS # 上交所 anti_crawl: fp_required: false delay: 1.0 method: GET fields: price: span#price change_percent: span#changePercent # 新浪财经的PE在iframe里需要单独请求 pe_url_template: https://vip.stock.finance.sina.com.cn/q/go.php?pstock_sinasymbol{symbol} # 第三个信源本地Excel用于导入私募基金净值 - name: local_excel type: file path: ./data/fund_nav.xlsx sheet_name: 2023_Q2 # Excel列名到数据库字段的映射 column_mapping: 基金代码: symbol 净值日期: date 单位净值: nav 累计净值: nav_acc这个配置文件的设计哲学是让业务人员也能修改。比如研究员想新增一个信源他不需要懂Python只要按示例在YAML里加一段填上URL、字段选择器、正则表达式就能生效。我特意避开了JSON因为YAML支持注释#方便写业务说明。4.3 核心爬虫代码以“获取贵州茅台日线数据”为例下面是最精简但可运行的核心代码已脱敏删减了日志和异常处理但逻辑完整import requests from bs4 import BeautifulSoup import re import pandas as pd from datetime import datetime from config import load_config # 加载上面的YAML配置 def fetch_eastmoney_data(symbol: str) - dict: 从东方财富网抓取单只股票日线数据 config load_config() source next(s for s in config[sources] if s[name] eastmoney) # 构造URL600519 - 600519.SH if re.match(source[symbol_mapping][pattern], symbol): full_symbol symbol source[symbol_mapping][suffix] else: full_symbol symbol url f{source[base_url]}/sh{full_symbol}.html # 处理反爬用POST方法带fp和cookie session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, X-FP: config[fp_value] # 从配置或缓存读取 }) for cookie in config[cookies]: session.cookies.set(cookie[name], cookie[value], domaincookie[domain]) # 关键设置超时和重试 for attempt in range(3): try: response session.post( url, timeout(3.05, 10), # connect_timeout3.05s, read_timeout10s allow_redirectsFalse ) if response.status_code 200: break elif response.status_code 404: raise ValueError(fSymbol {full_symbol} not found on EastMoney) except requests.exceptions.RequestException as e: if attempt 2: raise e time.sleep(2 ** attempt) # 指数退避 # 解析HTML soup BeautifulSoup(response.text, lxml) # 提取价格用通配符选择器 price_elem soup.select_one(div[class*price] span) price float(price_elem.text.strip()) if price_elem else None # 提取涨跌幅可能带号需清理 change_elem soup.select_one(div.change-percent) change_text change_elem.text.strip() if change_elem else change_match re.search(r([-]\d\.\d)%, change_text) change_percent float(change_match.group(1)) if change_match else None # 提取PE用正则清理 pe_elem soup.select_one(td:contains(市盈率) td) pe_text pe_elem.text.strip() if pe_elem else pe_match re.search(source[fields][pe_clean_regex], pe_text) pe float(pe_match.group(1)) if pe_match else None # 组装结果 return { symbol: symbol, date: datetime.now().strftime(%Y-%m-%d), price: price, change_percent: change_percent, pe: pe, source_url: url, fetch_time: datetime.now().isoformat() } # 调用示例 if __name__ __main__: data fetch_eastmoney_data(600519) print(f贵州茅台最新价{data[price]}元PE{data[pe]})这段代码的关键细节超时设置timeout(3.05, 10)不是随便写的。3.05秒是TCP连接超时选这个值是因为大部分财经网站的SSL握手在3秒内完成超过就肯定是网络问题10秒是读取超时足够下载一个HTML页面。重试策略用2 ** attempt实现指数退避1s, 2s, 4s避免瞬间重试加重服务器压力。正则清理re.search(r([-]\d\.\d)%, ...)比float(text.replace(%,))安全得多能过滤掉“--”、“暂无”等无效值。时间戳datetime.now().isoformat()生成ISO 8601格式时间兼容所有数据库和时序分析工具。4.4 清洗与发布完整的端到端流程脚本最后是把采集、清洗、发布串起来的主流程run_pipeline.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- 金融数据管道主流程 执行顺序采集 - 清洗 - 人工审核 - 发布 import sys import os from datetime import datetime from pathlib import Path # 添加项目根目录到Python路径 ROOT_DIR Path(__file__).parent sys.path.insert(0, str(ROOT_DIR)) from collectors.eastmoney import fetch_eastmoney_data from collectors.sinajs import fetch_sinajs_data from cleaners.finance_cleaner import FinanceCleaner from publishers.sqlite_publisher import SQLitePublisher from utils.audit_logger import AuditLogger def main(): # 初始化审计日志 logger AuditLogger() # 步骤1采集多信源并发 logger.info( 开始采集阶段 ) symbols [600519, 000858, 300750] # 贵州茅台、五粮液、宁德时代 raw_data_list [] for symbol in symbols: try: # 东方财富数据 em_data fetch_eastmoney_data(symbol) em_data[source] eastmoney raw_data_list.append(em_data) # 新浪财经数据异步调用此处简化为同步 sj_data fetch_sinajs_data(symbol) sj_data[source] sinajs raw_data_list.append(sj_data) except Exception as e: logger.error(f采集 {symbol} 失败{e}) continue # 步骤2清洗 logger.info( 开始清洗阶段 ) cleaner FinanceCleaner() clean_data_list [] for raw_data in raw_data_list: try: clean_data cleaner.clean(raw_data) clean_data_list.append(clean_data) logger.info(f清洗成功{clean_data[symbol]} {clean_data[date]}) except Exception as e: logger.error(f清洗 {raw_data[symbol]} 失败{e}) continue # 步骤3生成清洗报告供人工审核 report_path ROOT_DIR / reports / fclean_report_{datetime.now():%Y%m%d_%H%M%S}.csv report_df pd.DataFrame(clean_data_list) report_df.to_csv(report_path, indexFalse, encodingutf-8-sig) logger.info(f清洗报告已生成{report_path}) # 步骤4人工审核此处模拟检查报告中是否有红色警告 # 实际使用时这里会启动一个Web界面或Excel供分析师勾选批准 approved_data [d for d in clean_data_list if not d.get(is_dirty, False)] # 步骤5发布 if approved_data: publisher SQLitePublisher() publish_count publisher.publish(approved_data) logger.info(f发布成功{publish_count} 条数据) else: logger.warning(无数据通过审核跳过发布) if __name__ __main__: main()这个脚本的精妙之处在于可中断、可重入。比如人工审核环节分析师发现报告里有一条PE异常他可以在Excel里手动修改pe字段保存后重新运行脚本脚本会自动跳过已采集和清洗过的数据只处理修改后的记录。这种设计让整个流程真正服务于人而不是让人适应流程。5. 常见问题排查与独家避坑指南5.1 典型问题速查表问题现象可能原因排查步骤解决方案所有请求返回403 ForbiddenUser-Agent被识别为爬虫1. 用浏览器访问同一URL对比Headers2. 检查session.headers是否包含Accept-Encoding: gzip, deflate在Headers中添加Accept-Encoding: gzip, deflate和Accept-Language: zh-CN,zh;q0.9价格字段总是NoneHTML结构变化或选择器失效1.print(response.text[:1000])看原始HTML2.print(soup.prettify()[:500])看解析后DOM改用更鲁棒的选择器如soup.find(stringre.compile(r最新价.*\d\.\d))向上找父节点浮点数解析报错页面显示“--”、“暂无”、“停牌”等非数字1.print(price_elem.text)看原始文本2. 检查正则是否覆盖所有情况在清洗层增加预处理text re.sub(r[^\d.-], , text)再尝试float转换SQLite数据库写入缓慢没有使用事务批量提交1. 查看sqlite3日志确认是否每条INSERT都commit2. 用EXPLAIN QUERY PLAN分析慢查询所有INSERT用executemany()并在事务中执行conn.executemany(INSERT..., data_list)多信源数据不一致各网站更新时间不同步1. 记录每个信源的fetch_time2. 对比各信源的update_time字段如有采用“最新时间戳优先”策略取所有信源中fetch_time最大的那条作为基准其他信源数据只用于校验5.2 我踩过的五个血泪坑坑1忽略时区导致跨日数据错乱某次抓港股数据发现所有“收盘价”都比实际晚一天。排查半天发现是香港服务器时间是UTC8而我的脚本用datetime.now()取的是本地时区UTC8但东方财富网的“日期”字段是按香港交易所时间显示的。解决方案所有时间相关操作统一用datetime.now(timezone.utc)再转换为Asia/Shanghai时区。坑2PDF财报解析丢失表格线用pdfplumber解析年报PDF时表格识别率极低。后来发现是PDF的表格线是“虚线”pdfplumber默认只识别实线。解决方案在pdfplumber.open()时传入vertical_strategylines和horizontal_strategylines强制识别所有线条。坑3HTTPS证书验证失败但网站明明能打开某些地方股交中心的网站用的是自签名证书。requests.get()默认会验证报SSLError。解决方案不是简单加verifyFalse不安全而是用certifi库更新根证书pip install --upgrade certifi。坑4DataFrame内存爆炸一次性加载10年日线数据到pandas内存飙升到8GB。解决方案用chunksize参数分块读取或改用polars库内存占用只有pandas的1/5。坑5公司名称编码错误导致数据库乱码从新浪财经抓“宁德时代”存入SQLite后变成“宁德时代”。原因是网页是GBK编码但response.text默认用UTF-8解码。解决方案显式指定编码response.content.decode(gbk)再传给BeautifulSoup。5.3 生产环境部署 checklist在把脚本部署到公司服务器前务必逐项核对[ ]日志轮转用logging.handlers.RotatingFileHandler最大日志文件10MB最多保留10个备份避免磁盘占满。[ ]错误告警当连续3次采集失败时自动发邮件给负责人。用smtplib即可无需第三方服务。[ ]资源监控在脚本开头加入内存和CPU检查psutil.virtual_memory().percent 90则退出防止影响其他业务。[ ]数据备份每天凌晨2点自动压缩data/目录并上传到公司NAS保留30天。[ ]权限最小化运行脚本的系统账户只对data/和logs/目录有读写权限对其他目录只读。[ ]HTTPS证书更新如果用了SeleniumChromeDriver会自动更新但要确保服务器