资讯动态

Python爬取东方财富股吧评论并进行情感分析实战

发布时间:2026/10/5 4:59:25 来源:尧图企业网站定制
写这个系列是因为“python爬取东方财富网股吧评论并进行情感分析”这套组合在后台被问过太多次了。很多人想研究市场情绪又不想碰那些封闭的商业数据接口于是把目光放到股吧这种公开社区也有人单纯想练手爬虫和NLP需要一个有真实数据、有业务含义的案例。不管是哪种诉求这个项目都挺合适的。先把这个项目能做什么说清楚用Python从东方财富网股吧抓取指定股票的帖子列表把标题、正文、作者、时间、阅读量、评论数这些字段落库然后对文本做情感打分最后得到一个类似“最近市场讨论偏乐观还是偏悲观”的量化结果。适合的人群很广——想入门爬虫的、想接触情感分析的、或者正在做金融舆情方向毕业设计的人都可以拿这套流程当起点。作为一个系列的第一篇这一篇不会一上来就上多复杂的模型。目标是把地基打牢环境怎么搭、页面结构怎么分析、爬虫怎么写、数据怎么存、snownlp怎么做初版情感分析全部走通一遍。等你跑通了这一版后续再去优化反爬、换更高级的情感模型就有明确的参照物了。1. 项目整体设计与思路拆解1.1 需求拆解先想清楚要抓什么、分析什么很多人一上来就急着写代码结果抓回来一堆乱七八糟的字段分析的时候发现啥也用不上。我的习惯是先花五分钟把需求落到纸面上。这个项目的核心需求有两块抓取和情感分析。抓取的目标是股吧帖子那就要先明确“抓取哪些帖子”“保留哪些字段”。股吧列表页的每个帖子通常包含标题、阅读量、评论数、作者、发布日期、最后更新时间、帖子链接等信息。其中最关键的两个字段是标题和正文——情感分析主要依赖文本内容阅读量、评论数可以作为热度权重作者和时间用来做后续的趋势分析。情感分析的需求也要拆。你要回答的是“市场情绪偏正还是偏负”不是“这篇文章写了什么”。所以需要的是文本倾向性打分而不是主题分类或摘要生成。这样就把技术选型范围缩小了——不需要上BERT之类的重型模型先找一个轻量、中文友好、能直接出0到1置信分的方案就够了。按这个思路首版功能边界如下能按股票代码抓取列表页、能翻页、能提取核心字段、存成结构化文件、能用情感分析库给标题/正文文本打分、能简单汇总情绪比例。做完了这些“一”的任务就算完成。1.2 技术选型为什么用requestsBeautifulSoupsnownlp选型这块我直接说结论再解释理由。爬虫部分用requests加BeautifulSoup不用Scrapy。不是因为Scrapy不好而是这个项目当前阶段用不上分布式、管道、中间件那一套。股吧列表页是普通的服务端渲染页面requests拿到HTMLBeautifulSoup解析DOM几十行代码就跑通了。Scrapy的学习曲线和项目结构对新手来说反而是一种负担。你先把轻量方案跑明白之后真遇到大规模采集需求再迁移到Scrapy也来得及解析逻辑基本可以复用。情感分析部分用snownlp而不是训练一个深度学习模型。snownlp是一个纯Python实现的中文情感分析库基于贝叶斯分类调用简单开箱即用。对于股吧评论这种短文本在没有任何标注数据的情况下它能给你一个可解释的参考分数。金融领域因为有很多专业术语snownlp的准确率不会特别高这个我会在后面的章节专门讲怎么弥补。数据存储用CSV而不是数据库。首版项目数据量不大几千条帖子用CSV完全够了pandas读起来也方便。等后续要做增量采集、写定时任务再考虑换成SQLite或MySQL不迟。记住一个原则能用简单方案解决的事不要急着上复杂方案。1.3 整体流程设计从URL到情绪分数的完整链路这个项目的完整技术链路是这样的构造列表页URL带上必要的请求头用requests请求页面拿到HTML文本用BeautifulSoup定位帖子列表的DOM节点提取字段清洗数据处理空值、去重、格式统一保存为CSV文件读取文本列调用snownlp计算情感分数汇总统计输出情绪分布这个流程的每一环都可以独立替换。比如把requests换成httpx或者把snownlp换成其它模型都不会影响整条链路的结构。模块化是工程性的基本素养哪怕你只是写一个脚本也值得把函数拆开别把所有逻辑堆在一个main函数里。2. 环境准备与依赖安装2.1 Python环境与虚拟环境配置开始写代码前先把环境收拾干净。我建议使用Python 3.9以上版本太老的版本对部分库的支持会有问题。如果你电脑里已经装了Python可以先在终端验证一下版本python --version如果提示找不到命令检查一下安装时是否勾选了“Add Python to PATH”。这一步经常有人忽略导致后面pip安装库的时候出现“python was not found”之类的报错其实不是Python没装好就是环境变量没配上。强烈建议用虚拟环境不要图省事直接往全局环境装包。虚拟环境的好处是项目之间的依赖互不干扰。比如你另一个项目用的是pandas 1.x而这个项目需要pandas 2.x放在同一个全局环境里就会打架。创建虚拟环境的命令很简单python -m venv venv激活虚拟环境Windows系统和macOS/Linux的命令不一样别搞混了# Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活后命令行前面会出现一个(venv)前缀说明你现在已经在虚拟环境里了。后面用pip装的包都会进入这个环境不会污染全局。2.2 安装本次项目需要的依赖库这个项目需要用到的Python库其实不多核心就这几个requests发HTTP请求拿网页源码beautifulsoup4解析HTML提取数据lxmlBeautifulSoup的解析器速度比自带的html.parser快pandas数据处理和CSV读写snownlp中文文本情感分析jiebasnownlp底层会用到分词通常装snownlp时自动装好但显式安装可以确保版本没问题执行下面的命令一键安装pip install requests beautifulsoup4 lxml pandas snownlp如果你网络环境不好可以换成国内镜像源速度会快很多pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 lxml pandas snownlp2.3 验证安装是否成功装完之后别急着写业务代码先做一个快速验证确保每个库都能正常导入import requests import bs4 import pandas as pd from snownlp import SnowNLP print(requests版本:, requests.__version__) print(bs4版本:, bs4.__version__) print(pandas版本:, pd.__version__) print(snownlp版本:, SnowNLP.__version__ if hasattr(SnowNLP, __version__) else 未知)再顺手测一下snownlp能不能正常打分from snownlp import SnowNLP s SnowNLP(这只股票今天涨得不错市场情绪很乐观) print(s.sentiments)如果输出一个介于0到1之间的小数说明环境没问题。常见的坑是snownlp第一次调用时需要加载数据文件如果你的机器性能一般可能会卡几秒钟这是正常的不是死机。提示snownlp的sentiments属性返回的是“文本倾向为正面的概率”数值越接近1表示越正面越接近0表示越负面0.5左右表示中性。这个概念后面会反复用到建议先记住。3. 目标网站分析东方财富网股吧页面结构与数据定位3.1 确定入口URL以股票代码构造列表页地址爬虫的第一步不是写代码是打开浏览器观察目标网站。东方财富网股吧的列表页URL有固定的规律通常长这样http://guba.eastmoney.com/list,600519,1,f.html其中600519是股票代码1是页码f.html是列表页的固定后缀。不同股票的列表页只需要替换股票代码这一部分。以贵州茅台为例第一页地址就是上面这个想看第二页把1改成2就行。实际项目中我建议把股票代码定义成一个变量这样换股票调研时只改一处即可。比如stock_code 600519 base_url fhttp://guba.eastmoney.com/list,{stock_code},1,f.html如果你不确定某个股票的代码直接在东方财富网搜索股票名称就能看到。这个系列后续如果要写个股对比分析这个URL构造方式可以帮你省不少事。3.2 用浏览器开发者工具定位数据节点拿到URL之后在浏览器里打开按F12进入开发者工具点左上角的“选取元素”按钮然后把鼠标移到帖子标题上。你会看到页面高亮对应的HTML代码那就是标题所在的位置。以股吧列表页为例帖子列表通常在一个ul或div容器里每个帖子的标题是一个a标签href属性是详情页链接标签文本就是标题内容。阅读量和评论数通常在各列对应的span标签里作者和时间也都有对应的节点。这里我教大家一个通用方法不依赖具体class名因为网站改版class名会变但DOM结构规律不容易变。用BeautifulSoup定位时先找到所有包含帖子标题的a标签然后向上找父级容器再在父级容器里找其他字段。这样即使class名变了只要结构没变代码还能跑。3.3 判断页面是静态渲染还是动态加载股吧列表页是服务端渲染的也就是你请求一次URL返回的HTML里直接带着帖子数据不需要像某些网站那样模拟浏览器执行JavaScript。怎么判断呢在浏览器里右键“查看网页源代码”搜索页面上的帖子标题。如果能在源码里直接搜到说明是静态渲染搜不到说明数据是通过异步接口加载的得另想办法。这个判断非常关键它直接决定了你用requests就能搞定还是得上selenium或者Playwright。东方财富的列表页实测是静态渲染这也是我推荐它作为爬虫入门案例的原因之一——技术门槛低数据又是真实业务场景。3.4 请求头伪装与基础安全策略虽然股吧的反爬不算特别严格但直接裸请求还是容易被拦住。网页服务器会检查请求头里的User-Agent如果发现是Python默认的python-requests/x.x.x可能会拒绝响应。所以我们要伪装成一个正常的浏览器访问。一个最小可用的请求头配置如下headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, }这里的User-Agent我建议直接复制你自己浏览器的值在开发者工具里随便找一个网络请求就能看到。伪装成真实浏览器后被拦的概率会大幅下降。另外请求频率一定要控制。我见过有人写个循环不设延时几秒钟就把一个页面的所有分页请求完了结果IP被暂时限制。做人留一线爬虫也是如此。每请求一页至少time.sleep(1)到time.sleep(3)随机延时更保险。4. 首版爬虫代码与核心字段提取4.1 搭建基础爬虫框架环境搞定、页面结构也摸清了接下来进入正题写代码。我习惯先搭一个基础框架把请求、解析、保存分层写好后面调试起来很清晰。import requests import time import random import pandas as pd from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } def fetch_page(url): 请求页面返回BeautifulSoup对象 resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding if resp.status_code ! 200: print(f请求失败: {url}, 状态码: {resp.status_code}) return None return BeautifulSoup(resp.text, lxml) def parse_list_page(soup): 解析列表页提取帖子核心字段 items [] # 这里先用一个宽松的选择器后续按实际页面结构调整 post_nodes soup.select(div.articleh) for node in post_nodes: title_tag node.select_one(a.title) if title_tag is None: continue title title_tag.get_text(stripTrue) link title_tag.get(href, ) # 阅读量和评论数一般在特定列这里通过选择器提取 read_tag node.select_one(span.read) comment_tag node.select_one(span.comment) read_count read_tag.get_text(stripTrue) if read_tag else 0 comment_count comment_tag.get_text(stripTrue) if comment_tag else 0 # 作者和时间字段按实际结构进一步定位 author_tag node.select_one(a.user) author author_tag.get_text(stripTrue) if author_tag else items.append({ 标题: title, 链接: link, 阅读量: read_count, 评论数: comment_count, 作者: author, }) return items if __name__ __main__: stock_code 600519 url fhttp://guba.eastmoney.com/list,{stock_code},1,f.html soup fetch_page(url) if soup: data parse_list_page(soup) df pd.DataFrame(data) print(df.head(10)) print(f共解析到 {len(df)} 条数据)注意这里的CSS选择器div.articleh、span.read是当前页面结构下的常见写法。网站改版后class名可能变化你需要用开发者工具确认实际值然后把选择器替换成最新的。4.2 字段提取逻辑的细节优化上面这段代码能跑通但离“好用”还有点距离。真实场景下有几个细节需要处理。第一个是阅读量和评论数可能是“万”这种缩写形式。比如“1.2万”代表12000如果直接拿来做数值分析会出错。我一般会写一个小函数做转换def parse_count(text): 将 1.2万 转为 12000普通数字原样返回 text text.strip() if 万 in text: return int(float(text.replace(万, )) * 10000) try: return int(text) except ValueError: return 0第二个是帖子的发布时间列表页可能显示“今天 10:30”或者“昨天”如果你想做时间的趋势分析最好抓取详情页里的完整时间或者通过帖子链接去详情页单独解析。这个属于后续优化首版可以先把这个字段存成字符串后续再做时间格式化。第三个是正文内容。列表页只有标题没有正文。如果你要对帖子正文做情感分析就得进入详情页再抓一次。这会大幅增加请求量所以首版我建议先把列表页标题作为情感分析的主要文本来源。标题虽然短但通常能比较直接地反映发帖人的倾向比如“看好这个票拿住了”“垃圾公司天天跌”情感倾向一目了然。4.3 翻页采集与批量数据获取单页数据量太少了我们需要翻页。翻页逻辑就是循环拼接URL里的页码参数。不过要注意东方财富股的翻页有一个特点超过一定页数后URL规则可能有变化有的板块超过第100页就需要换接口。首版我们先做前几页够用就行def crawl_pages(stock_code, max_pages10): all_data [] for page in range(1, max_pages 1): url fhttp://guba.eastmoney.com/list,{stock_code},{page},f.html print(f正在抓取第 {page} 页...) soup fetch_page(url) if soup: page_data parse_list_page(soup) all_data.extend(page_data) # 随机延时降低访问频率 time.sleep(random.uniform(1.5, 3.5)) return all_data实测下来抓10页大概需要半分钟左右能得到300到500条帖子数据足够做情感分析的初版实验了。5. 数据存储与初步清洗5.1 为什么先把数据存成CSV有些教程喜欢直接跑通从爬虫到模型的全流程但我更建议中间加一步“落盘”。原因很简单网络请求是不可靠的爬虫和模型之间如果不加缓存每次调试都要重新请求一遍页面既慢又容易被封。CSV是一个很好的中间格式。pandas直接支持Excel也能打开方便你肉眼检查数据质量。保存方式很简单df.to_csv(guba_posts.csv, indexFalse, encodingutf-8-sig)这里有个细节要注意编码要用utf-8-sig不要用纯utf-8。因为纯utf-8编码的CSV用Excel打开时会乱码这是Excel的BOM问题utf-8-sig会在文件开头加上BOM标记Excel就能正确识别了。5.2 数据清洗把脏数据挡在分析之前从网页上抓下来的数据通常不会很干净。常见问题包括标题里有换行符和多余空格、阅读量是“万”单位、作者字段为空、链接是相对路径需要拼接域名等。清洗函数我习惯单独写不跟爬虫逻辑混在一起def clean_data(df): # 去除标题中的空白字符 df[标题] df[标题].str.replace(r\s, , regexTrue) # 处理链接相对路径拼接完整地址 df[链接] df[链接].apply(lambda x: x if x.startswith(http) else http://guba.eastmoney.com x) # 解析阅读量/评论数统一转为数值型 df[阅读量] df[阅读量].apply(parse_count) df[评论数] df[评论数].apply(parse_count) # 删除完全重复的行 df df.drop_duplicates(subset[链接]) # 删除标题为空的数据 df df.dropna(subset[标题]) return df这个流程执行完后建议打印一下数据形状看看清洗前后差了多少行。如果发现清洗掉的行数异常多说明解析逻辑可能有问题需要回头检查选择器。5.3 数据复核用肉眼快速检查结果清洗完之后打开CSV文件随机看几行。这里我提供一个简单的复核方法用pandas抽样打印20行观察标题是否完整、阅读量是否像正常的数值、作者是否为空。df_clean clean_data(df) df_clean.sample(20).to_string()我遇到过的情况是某个股票的帖子列表里混杂着官方公告“标题”列抓到了公告内容而不是用户评论。虽然公告也是文本但它不是“市场情绪”的代表后续分析时可能要单独过滤。判断依据是标题里是否带“公告”“提示”等词或者发帖账号是否是官方账号。“官方发言”和“散户吐槽”混在一起做情感分析结果会失真。这个坑我建议大家提前规避。6. 情感分析初体验用snownlp给帖子打分6.1 snownlp的原理与使用边界snownlp的底层是一个基于朴素贝叶斯分类器的情感判断模型。它内部已经训练好了中文语料所以你不需要自己标注数据直接调用就能得到情感分数。对短文本来说snownlp的效果“够用”但不要期待它能理解“这里说的涨其实是反讽”这种复杂语境。它的使用极其简单from snownlp import SnowNLP text 跌成这个鬼样子真是服了 s SnowNLP(text) print(s.sentiments)sentiments返回的是0到1之间的浮点数。我做了一个粗粒度的划分方便后续统计大于0.6算正面小于0.4算负面中间算中性。当然这个阈值你可以自己调不同场景下最优阈值不一样。6.2 批量情感打分与结果分布拿到清洗后的DataFrame我们可以批量跑情感分析。这里注意一个问题snownlp处理速度不算快如果几条帖子还没有感觉几千条数据体量下你就能感受到瓶颈。先跑个首版看看效果是没问题的真要大规模计算再考虑并行优化。df_clean[情感分] df_clean[标题].apply(lambda x: SnowNLP(str(x)).sentiments) def mood_label(score): if score 0.6: return 正面 elif score 0.4: return 负面 else: return 中性 df_clean[情绪] df_clean[情感分].apply(mood_label)跑完之后用pandas做个简单汇总mood_dist df_clean[情绪].value_counts() print(mood_dist) print(平均情感分:, df_clean[情感分].mean())假设你抓了400条帖子输出可能类似正面180条中性120条负面100条。平均分0.58整体偏乐观。这就完成了从“文本数据”到“情绪指标”的第一步转化。6.3 金融场景下snownlp的局限与应对思路实测用下来snownlp在股吧场景有一个明显的短板它对金融领域词汇的判断不够准确。比如“涨停”这个词在股吧语境里一定是正面的但snownlp没学过财经语料可能把它当中性词甚至负面词。类似的还有“放量拉升”“主力进场”“割肉”等这些词的倾向性都带有强烈的金融色彩。应对方案有两种。第一种是给snownlp做增量训练用少量标注数据微调它的模型。snownlp提供了train方法但需要准备标注好的正面和负面文本。这对新手可能有点门槛但网上能找一些开源的金融情感标注数据集做补充。第二种更省事维护一个自定义情感词典对特定词做加权修正。比如在打分结果基础上如果标题里出现“涨停”“大涨”“牛气”这类词直接额外加上一个正向偏移出现“跌停”“暴雷”“退市”等词则加上负向偏移。这个方法虽然粗糙但在工程上很实用尤其是你没有精力做完整模型微调的时候。positive_boost [涨停, 大涨, 牛气, 利好, 拉升, 吃肉] negative_boost [跌停, 大跌, 暴雷, 利空, 套牢, 割肉, 退市] def adjust_score(text, score): for word in positive_boost: if word in text: score min(1.0, score 0.1) for word in negative_boost: if word in text: score max(0.0, score - 0.1) return score df_clean[调整后情感分] df_clean.apply( lambda row: adjust_score(row[标题], row[情感分]), axis1 )当然这只是“能出结果”的初版方案。后续如果想做得更专业可以考虑用金融领域预训练模型如FinBERT类模型或者自己标注几百条数据训练一个分类器这个我在系列后续篇章里再展开聊。7. 常见问题与排查技巧实录7.1 请求被拒绝或返回异常状态码这是新手最常遇到的问题。表现是requests请求返回403或者直接被重定向到验证页面。排查顺序如下检查User-Agent有没有伪装。裸的python-requests很容易被识别。检查请求频率。如果前面已经连续请求了十几页先停下来等几分钟再试。检查URL是否拼写正确。股票代码位数、分隔符、后缀任何一处错误都可能拿到异常页面。试试在浏览器里手动访问同一个URL。如果浏览器正常但requests异常基本就是反爬拦截或者是你的请求头缺字段。东方财富的反爬在同类财经网站里算中等偏弱只要你不“暴力访问”一般不会有大问题。我个人的经验是单线程爬取、每页2秒以上延时、最多连续抓50页基本不会被封。真遇到被封了别硬试停一段时间再继续。7.2 中文乱码问题页面返回的中文如果是乱码问题几乎一定出在编码识别上。东方财富有些页面用的是GBK编码requests默认会猜测编码但猜测结果不一定对。解决办法是在拿到响应后立刻设置编码resp requests.get(url, headersheaders, timeout10) resp.encoding gbk或者更自动化的方式resp.encoding resp.apparent_encodingapparent_encoding是根据页面内容自动检测的编码通常准确率不错。遇到个别页面检测失败那就手动指定。7.3 明明有数据却解析不到选择器写好了BeautifulSoup也返回了soup对象但parse_list_page返回空列表。这种情况最让人头疼原因通常有这么几个页面结构改了原来的class名不存在了。怎么验证把soup.prettify()输出到本地文件打开搜索关键词看数据到底在哪个标签里。数据是通过iframe加载的。列表页本身没有帖子帖子在嵌套的iframe里。这种情况需要先拿到iframe的src再请求那个子页面。请求被重定向到了一个空白页或验证页。打印soup.title看看页面标题是不是正常的帖子列表页面。排查这类问题时最有效的办法是把拿下来的HTML保存成文件用文本编辑器慢慢看。别在脑子里猜结构数据不会说谎。7.4 snownlp加载缓慢或首次调用卡顿snownlp在第一次使用时会加载内置模型和数据文件这个过程在某些机器上可能长达几秒。如果是在循环里每处理一条都创建一个新的SnowNLP对象会重复加载数据速度慢得让人抓狂。优化方法是把分析封装在同一个对象里尤其是文本长度较短时直接用sentiments属性即可不要反复实例化。真嫌慢的话可以用多线程或者分批处理但要注意snownlp本身不是线程安全的多线程时每个线程独立实例化。我一般先用单线程跑小批量验证结果确认无误后再考虑优化速度。7.5 常见问题速查表问题现象可能原因解决办法返回403User-Agent未伪装或频率过高设置浏览器UA降低请求频率中文乱码编码识别错误设置resp.encoding gbk或使用apparent_encoding解析为空选择器失效或页面结构变化保存HTML到本地重新定位节点情感分全部接近0.5snownlp对金融文本不敏感调整阈值或使用自定义词典加权读取CSV时Excel乱码编码不是utf-8-sig保存时指定encodingutf-8-sig请求到一半被封IP请求间隔太短增加延时控制总页数8. 写在第一篇最后实操中的几点体会这个系列做到“一”核心目标是把整条链路跑通。我个人在实操中最大的感受是爬虫部分的技术难度其实不大真正花时间的是页面结构分析和数据清洗情感分析部分则是“结果易得、精度难求”snownlp给你一个分数很容易但这个分数能不能反映真实的股吧情绪取决于你对业务语境的理解和对数据的处理。一个小建议抓下来的数据不要只当训练作废品攒着。第一篇跑通了之后你可以每天定时抓一批积累一份自己的股吧情绪历史数据。后续不管是做时间序列分析还是训练一个金融领域的情感模型这些数据都是最宝贵的资产。数据这东西越积累越值钱。下一篇我会把重点放在两个方向上一是爬虫的健壮性提升比如详情页正文抓取、异常的自动重试、更细力度的时间字段解析二是情感分析的效果优化包括标注少量金融评论、训练专属分类模型、以及如何把情绪分数和股票行情联动起来做对比分析。到时候见。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑