ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python爬虫实战:抓取东方财富股吧情绪,构建热度评分系统

Python爬虫实战:抓取东方财富股吧情绪,构建热度评分系统 做量化或者盯盘的朋友应该都听说过一句话股市是情绪的放大器。但情绪这东西看不见摸不着怎么量化今天分享的这个小项目就是直接用 Python 爬虫去监听东方财富股吧的帖子热度把散户的讨论热度变成一个个可以排序、可以计算的数字。我给这玩意起了个名字叫“情绪挖掘机”。不是什么高大上的东西本质上就是把股吧里某个股票、某个板块的帖子标题、阅读量、评论数、发布时间这些公开信息定时抓下来再通过一个简单的评分公式算出每一只股票当前的热度分和情绪倾向——到底是看多的人多还是看空的人多讨论是在升温还是在降温。别小看这个数据你盯盘的时候觉得“今天这票好像突然有人聊了”其实就是热度在起变化。机器能帮你把这种“好像”变成具体的数值和曲线。这项目适合谁一是 Python 基础刚学完、想找个真实案例练手的朋友二是做量化交易、想引入情绪面辅助判断的玩家三就是纯粹满足好奇心——看看哪只股票最近在股吧被讨论得最凶。1. 项目拆解情绪挖掘机到底在挖什么1.1 核心需求与数据维度先说清楚这个项目要解决什么问题。东方财富股吧是国内散户聚集度最高的股票社区之一尤其是个股吧里面讨论的基本都是实时交易情绪。有人发帖建仓、有人发帖骂庄、有人发帖问“还能拿吗”这些内容天然就是情绪指标。我把“情绪”拆成了两个维度热度和情绪倾向。热度看的是帖子讨论的活跃程度参考三个指标——阅读量、评论数、发布时间情绪倾向看的是发帖人看多看空的立场这个用自然语言处理的简单办法做不搞大规模模型就先通过一组正负向关键词加规则判断。数据维度上每个帖子主要采集以下字段字段含义用途post_title帖子标题情绪分析文本来源post_id帖子唯一标识去重和增量更新read_count阅读数热度计算comment_count评论数热度计算like_count点赞数热度计算create_time发布时间新鲜度计算stock_code所属股票代码聚合分组有了这些字段就可以按股票代码分组计算每只股票的整体热度也能进一步看每只股票下面热门帖子都在聊什么方向。1.2 技术路线选型为什么是 requests 而不是 selenium说实话市面上很多人一提到爬虫就想到 selenium觉得“能渲染网页就万能”但这个项目我第一反应就是用 requests 直接抓接口。原因有三股吧的数据是接口返回的 JSON不是纯网页渲染。网页上看到的帖子列表实际上是通过一个 AJAX 接口动态加载的数据本身是一段结构化的 JSON。直接请求这个 JSON 接口拿到的数据干净、完整、好解析完全没必要让浏览器去渲染一遍。Selenium 的开销太大了。每启动一个浏览器实例内存和 CPU 占用都不小而且速度慢。如果是定时任务每 5 分钟跑一次用 Selenium 就是给自己找罪受。反爬的定位不同。股吧的核心数据接口并没有做太复杂的校验主要靠频率限制和部分 header 校验。requests 加上合理的请求头模拟实测下来完全能稳定抓取。当然如果哪天对方把整个页面改成完全的 JS 渲染、接口加了复杂的动态 token再考虑上 selenium 或者 playwright 不迟。但那是后话永远不要让“可能的未来复杂化”拖慢今天的项目进度。2. 抓包分析与协议还原最关键的 30 分钟2.1 找到正确的数据接口很多人写爬虫死在第一步不知道从哪里拿数据。这一步其实不靠猜打开浏览器开发者工具全部靠看。操作路径是这样的打开东方财富某个个股吧页面比如 600519 的贵州茅台吧。按 F12 打开开发者工具切到 Network网络面板。刷新页面在筛选框输入ajax或者直接找返回类型是 JSON/XHR 的请求。逐个看响应内容找到包含帖子列表数据的那个请求。核心接口结构大概是这样的一个 GET 请求https://guba.eastmoney.com/list/600519,1,f.html不过这个是网页版列表页直接抓这个拿到的是 HTML不是最理想的。更推荐的是找到真正的 JSON 数据接口。在开发者工具里看到的实际请求是一系列带参数的 URL形如https://gbapi.eastmoney.com/stock/list/600519?pageIndex1pageSize20sort1type1marketCode1具体参数名在不同时期会有调整但大致就这几类股票代码、页码、每页条数、排序方式。响应数据里会有一个re列表里面就是每条帖子记录的字典字段正好对应前面表格里的那几个。这一步的“为什么”很重要找到一个稳定的接口比写一万行抓取代码都值钱。接口找到了爬虫就完成了一半接口没找到后面全是白搭。2.2 请求参数与风控伪装拿到接口之后先在浏览器里复制成 cURL自己用 Postman 或者直接在 Python 里试一把。如果复制出来的 cURL 一跑就通说明这个接口对 cookie 的依赖不强可以直接用 requests 模拟。但直接用裸 requests 去请求大概率会出问题。我实测中发现它至少会校验几个东西User-Agent必须伪装成真实浏览器的 UA默认的python-requests/2.x很容易被识别。Referer得带上你从哪个页面进过来的一般填对应的股吧 URL表示你是从股吧页面跳转过来的正常访问。Cookie部分接口不校验 cookie 也能通但建议冷启动时先通过 requests.Session 访问一次股吧首页拿到初始 cookie再带着这个 session 去请求数据接口。完整的请求头我做成了下面这样的配置headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://guba.eastmoney.com/, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9, }这里有一个细节把 Accept 明确设置成 JSON 格式有些接口会根据这个字段决定返回 HTML 还是 JSON 数据反而是很多人忽略的点。3. 代码实现数据采集与热度评分3.1 采集器实现讲思路不如给代码。我写了一个轻量版的采集器核心逻辑分三层第一层fetch_page请求接口解析 JSON返回帖子列表。第二层parse_post清洗和转换数据把时间字符串转成时间戳把数字字符串转成 int。第三层crawl_stock按股票代码循环翻页拼接完整列表。伪代码大概长这样import requests import time import json import logging HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://guba.eastmoney.com/, Accept: application/json, text/plain, */*, } logging.basicConfig(levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s) def fetch_page(session, stock_code, page_index1, page_size20): url fhttps://gbapi.eastmoney.com/stock/list/{stock_code} params { pageIndex: page_index, pageSize: page_size, sort: 1, type: 1, marketCode: 1, } resp session.get(url, paramsparams, headersHEADERS, timeout10) if resp.status_code ! 200: logging.warning(f请求失败, status{resp.status_code}) return [] data resp.json() return data.get(re, [])这段代码注意两个点。第一session.get里的params参数会自动把字典序列化成 URL 后面的查询字符串不用自己拼 URL。第二每个股票翻页时用session.get而不是requests.get目的就是维持 cookie 和连接池减少握手次数减少被风控的概率。抓取的主循环再加一个time.sleep(random.uniform(0.5, 1.5))的随机延时把访问节奏模拟得接近真人同时避免对服务器造成压力。3.2 热度评分模型数据到手了怎么变成“热度分”我试过不少方案最后沉淀下来一套计算公式。先说为什么直接用阅读数不行。原因很简单阅读数的量级远大于评论数和点赞数。如果直接把三个数相加评论和点赞就被阅读数完全淹没了。所以我做了一步对数变换把极端的大数值压缩到合理区间。核心公式score 0.40 * log1p(read_count) 0.30 * log1p(comment_count) 0.20 * freshness 0.10 * log1p(like_count)逐项解释log1p(x)就是log(1 x)在 Python 里是math.log1p。添加 1 是为了避免 x0 时对数为负无穷。这个变换的逻辑是阅读量从 100 涨到 1000对热度的影响远大于从 10000 涨到 100000对数能体现这种边际递减效应——一篇文章从没人看到有人看是质变爆款之后再涨只是量变。freshness是新鲜度得分按照发布时间衰减。我的做法是hours_age (now - create_ts) / 3600 freshness max(0.0, 1.0 - hours_age / 24.0)也就是说24 小时内的帖子新鲜度得分在 0 到 1 之间线性递减超过 24 小时统一为 0。这是模拟“热帖效应”刚发出来的帖子讨论热度天然高于旧帖。四个权重加起来正好 1.0这样每篇帖子的基础分在 0 到几分的区间内。单帖分数看起来不高但几千篇帖子聚合到股票维度之后差距就拉开了。聚合打分公式对应到代码里就是def calc_post_score(post): import math read_score math.log1p(post[read_count]) comment_score math.log1p(post[comment_count]) like_score math.log1p(post[like_count]) freshness max(0.0, 1.0 - (time.time() - post[create_ts]) / 86400) return 0.40 * read_score 0.30 * comment_score 0.20 * freshness 0.10 * like_score至于情绪的看多看空判断我用的是一组正负向关键词字典配合几个简单规则比如标题中“涨停”“突破”“利好”“抄底”视为看多“跌停”“破位”“利空”“割肉”视为看空。对标题做一次分词匹配看命中了哪些词汇总正负得分。这个模型简单但胜在稳定可解释后续要换大模型或者接入成熟的情感分析库接口也方便替换。3.3 增量更新与主流程编排定时任务最怕重复抓全量数据所以增量更新的核心是去重。对于每篇帖子post_id是天然的唯一键。我在内存里维护一个已见集合每抓到一个新帖子如果post_id已经在集合里就跳过否则更新数据并记录。首次运行时是全量抓取之后每隔 5 分钟增量抓取一次。5 分钟这个间隔是我实测后定的。太频繁容易被限流而且 1 分钟内的讨论量变化没有统计意义太稀疏又抓不到短时爆发比如突发利空后半小时内的情绪骤变。5 分钟算是一个临界线数据分辨率和请求频率之间比较平衡。主流程的结构我用的是最简单但可靠的方案schedule库做定时每 5 分钟触发一次任务函数任务函数先抓增量数据再重算热度榜最后把结果写入 CSV 文件。逻辑清晰、排错方便不建议一上来就上 scrapy 这类重型框架杀鸡不要用牛刀。4. 结果产出从数据到可用信息4.1 榜单输出数据抓下来不能堆在那里落灰。我给输出设计了三个榜单分别对应不同使用场景第一张是热度榜。按股票代码聚合所有帖子的热度分取前 20 名展示哪些股票当前讨论最激烈。这张榜适合开盘前快速扫一眼看看今天的市场焦点在哪里。第二张是情绪榜。统计每只股票下面看多帖子和看空帖子的数量比计算出情绪偏多、偏空还是中性。我把它定义为sentiment (bull_count - bear_count) / (bull_count bear_count 1)结果范围在 -1 到 1 之间。1 表示全盘看多-1 表示全盘看空0 表示分歧很大。这个数值配合热门股票代码能辅助判断一只股票当前的市场预期。第三张是趋势榜。对比上一周期和当前周期的热度分找出增幅最大的股票。方法就是每个周期结束时拍一张快照下次计算时用当前分减去上一次的快照分。这个榜单的价值在于发现“正在被讨论”而不是“已经被讨论很久”的标的对短线敏感度高。三个榜单一出来数据就不是一堆乱糟糟的帖子了而是可直接浏览的决策素材。我把三个榜分别导出到 CSV用 pandas 处理然后直接打印在终端运行完之后一眼就能看到全局情况。4.2 简单可视化与记录历史我强烈建议爬虫抓到的数据一定要落盘。不要只存在内存里因为程序一重启历史数据就全丢了而情绪分析最值钱的就是历史对比。落盘方案我用的是 CSV 文件按日期分文件夹存放data/ 2024-01-15/ 600519.csv 300750.csv ...每个 CSV 的字段对应帖子完整信息包括标题、阅读数、评论数、发布时间、热度分、情绪倾向。代码层面用csv.DictWriter追加写入就够不需要上数据库。等数据量积累到几万条级别再考虑迁移到 SQLite 或 MySQL 也不迟。如果是想做个简易可视化pandas matplotlib 画个热度的曲线图完全够用。我常用的就三行df pd.read_csv(600519.csv, parse_dates[create_time]) df.set_index(create_time)[heat_score].plot() plt.show()当然如果你想把这份数据接入到自己的量化回测系统里建议输出格式直接用 JSON 或者 pandas 的 pickle 格式后续处理更灵活。5. 反爬对抗与合规红线边界要清楚5.1 常见的反爬手段与排查经验聊到反爬先说一个我踩过的坑第一次写完代码本地运行一切正常开心地挂到服务器上结果第二天早上起来一看请求全部超时被限制访问了。排查下来的原因很简单服务器 IP 段被重点关注了。数据中心 IP 的访问行为特征和普通住宅 IP 差异明显单纯靠请求头伪装是骗不过的。后来我的调整方案是三管齐下控制频率。每两次请求之间随机延迟 1 到 2 秒绝不并发。加长周期。把 1 分钟一次的频率调低到 5 分钟一次采集密度完全够用。增加随机性。除了固定 UA还准备了一个浏览器列表每次请求时随机抽取一个从行为模式上打散指纹。另外一个常被忽略的细节是深夜维护窗口。东方财富的系统每天会有固定的维护时段凌晨 3 点左右容易出现接口异常返回空数据或者直接超时。遇到这个情况不要慌写个重试机制失败 3 次后跳到下一轮不要原地死磕。有一些现象能提前预警风控总结成表现象可能原因应对措施接口返回 403IP 被临时限制停止采集等待 10-30 分钟返回数据为空但状态码 200接口变动或维护窗口检查接口字段观察返回内容read_count 全部异常为 0触发了测试版页面重置 session更换 UA请求超时率高频率过高被限制降低频率加延时5.2 合规与道德哪些事绝对不能做这个话题我必须单独拿出来说因为踩线的代价远大于收益。东方财富股吧的帖子数据是公开数据任何人打开网页都能看到所以页面信息的采集原则上属于公开信息收集。但这不代表可以无限度地薅绝不绕过登录验证股吧的帖子列表无需登录也能看那就不要搞什么自动登录、模拟密码那已经构成对服务条款的违反。绝不高频抓取公开数据也要讲究克制。把对方服务器打挂了最后修复接口的是人家蒙受损失的是所有依赖这个平台的人。绝不用于骚扰或商业侵权抓下来的帖子数据里含有发帖人的 ID、内容重新打包成所谓“用户画像”往外卖这是明显的违法边界想都不要想。我自己的使用范围很简单本地运行数据自己看最多拿来写写量化策略的研究分析。不公开分享原始数据不做商业化不对服务器增加不合理负担。一句话总结合规准则访问频率低到像个人在浏览网页数据使用严格限定在自我分析不碰任何非公开信息和用户隐私。6. 常见问题与排查技巧实录这个项目跑起来之后你大概率会遇到下面这些问题我提前把坑填好。6.1 问题速查表问题核心原因解决方案安装依赖报错环境没有 requests/pandaspip install requests pandas schedule注意 pip 版本返回 JSON 解析失败接口返回了 HTML 而非 JSON检查请求头 Accept 字段检查 cookie 是否过期帖子列表为空股票代码格式错误确认代码是 6 位数字不要带“.SH”后缀再请求阅读数字段为空接口字段名变化到开发者工具里重新看接口返回字段名做一次映射更新时间解析报错时间格式不是标准 ISO用datetime.strptime按实际格式解析或先用正则提取长时间运行后请求全部失败session 过期定时重建 session重新访问首页获取 cookie服务器上运行代码乱码编码环境问题顶部加# -*- coding: utf-8 -*-输出时指定encodingutf-86.2 我的几个独家调试技巧技巧一先缓存后解析。我最初是拿到响应就直接解析结果接口一旦变动连原始数据长什么样都不知道。后来改成了先把响应 body 存成.json文件再写解析代码调试效率翻倍。接口出问题了直接打开缓存文件看原始结构一眼就能定位是字段名变了还是数据格式换了。技巧二小规模验证再跑全量。写爬虫的直觉是做任务就全量跑但正确的姿势是先跑 1 页、2 页确认解析正确、字段完整再放开循环跑全量。很多时候你以为的问题在小规模测试里根本复现不出来。技巧三日志要留痕。不要只用print输出建议用logging。跑定时任务的时候print 的输出刷掉就没了而logging能写到文件里。出了问题翻日志是最快的排查路径。日志里建议至少记录每个股票代码的抓取页数、成功条数、耗时这三个指标足够判断系统的健康状态。7. 后续扩展从情绪挖掘到策略联动别把项目停在“看看热度”这一步再往下走几步价值就完全不一样了。我目前做的一个扩展是把热度榜和情绪榜数值导出到 CSV 之后用一个小脚本和行情数据的涨跌幅做关联分析。具体做法是每天收盘后对比前一天的股吧情绪分和当天的股价涨跌幅看两者有没有统计上的相关性。虽然不是严格意义上的因果验证但长期积累下来已经能找到一些规律比如某些股票在情绪分快速拉升后的 1-2 天往往出现明显的放量走势。更进一步这个数据可以接入量化交易系统。常见的量化策略大多基于技术指标但技术指标是滞后的——价格涨了 MACD 才金叉成交量放大了量能指标才变化。而股吧情绪数据有一个特殊的优势它领先于成交量的变化。主力资金进场之前讨论热度往往先起来。如果能把情绪因子作为选股的一个前置过滤器配合技术指标确认入场点策略的胜率理论上能得到改善。还有一种玩法是做“情绪预警”——监控特定持仓股的情绪分一旦出现异常暴增比如从 1 分飙到 10 分以上的极端波动就触发提醒让你及时关注盘面。这比一直盯盘轻松多了机器替你盯着散户的情绪变化你来判断该不该操作。但这里必须说清楚一点情绪数据是参考因子不是稳赚信号。股吧里也存在水军、控评、垃圾帖干扰你看到的情绪分暴涨可能是真实利好也可能是有人在刻意制造热度。所以任何结论都要结合行情、基本面和技术面做交叉验证不要把一个粗糙的情绪分当圣旨。这个项目本身的代码量不大核心逻辑也不复杂但麻雀虽小五脏俱全接口分析、数据清洗、增量更新、频率控制、结果产出、合规边界爬虫工程里该涉及的知识点全都覆盖到了。跑通一遍你对 Python 爬虫的整个工作流会有一个完整的体感比刷十篇教程都管用。最后分享一个我个人的小习惯每天早上开盘前我先看一眼自己维护的那个热度榜再打开行情软件看盘。机器给我的是一份客观事实——哪只股票正在被讨论盘面给我的是一份市场价格——哪只股票正在被交易。把这两份信息放在一起对照有时候能发现一些很有意思的预期差这大概就是情绪挖掘最大的乐趣所在。
返回列表