ARTICLE DETAIL

资讯详情

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

NLPIR+Python爬虫:网易新闻舆情分析系统实战

NLPIR+Python爬虫:网易新闻舆情分析系统实战 简介基于NLPIR的网易新闻舆情分析系统是一套高分本科毕业设计Python项目面向计算机、通信、人工智能、自动化等专业学生和老师也可作为课程设计或大作业参考。项目围绕新闻采集、中文分词、情感分析与可视化展开代码经调试测试包含完整后端逻辑与前端页面可运行、可复现。资料包共264个文件、20.19MB核心含59个py脚本、16个js及多套css/scss/less前端样式辅以jpg/png界面图、NLPIR词典与模型文件、exe/dll运行组件err/pdat等辅助文件可帮助排查运行中的问题便于定位调试。目前已有88人学习下载目录按模块拆分、结构清晰还可借助wordlist、pos、dict、model等NLP资源理解中文分词、词性标注和舆情研判的细节。基础扎实者可在现有代码上修改抓取规则、分析维度或界面快速迁移到其他新闻源或扩展情感标签用于毕业设计时可作为完整原型进行演示与二次开发。1. NLPIR 网易新闻舆情分析系统拿 Python 源码直接跑通的高分毕设如果你正在做舆情分析方向的 Python 毕业设计大概率绕不开两个痛点一是中文分词和情感分析交给谁二是新闻数据从哪来、怎么存、怎么出图。这套源码的核心思路是用 NLPIR 做中文分词与情感计算用 Python 爬虫抓网易新闻再按时间和情感维度算出舆情指数。比单纯用 jieba 随机数据要扎实得多答辩时也能讲出「为什么选 NLPIR 而不是纯 Python 库」的理由。下面我按自己拆项目时习惯的路径先讲选型再讲数据层、分析层和踩坑记录最后给一份能直接照着跑的验证清单。2. NLPIR 中文分词与情感分析调用参数和 JSON 输出怎么接 Python2.1 舆情分析里中文分词为什么绕不开 NLPIR做舆情分析的人应该都清楚新闻文本和微博、评论的差异很大长句多、专有名词多、标点规范但网易新闻里又有大量编辑改写的标题和「原标题」这类干扰词。常见做法是先用 jieba 快速跑一版你会发现两个问题分词粒度不稳「许家印」「乐视网」这类复合词经常被切碎情感判断没有语境生态环保类新闻里「污染」「破坏」全是负面词但整体报道其实是中性的。NLPIR原 ICTCLAS是处理中文分词的成熟方案优势在于词性标注、未登录词识别和命名实体识别而且它以动态库形式暴露 C 接口Python 通过 ctypes 直接调用在性能上明显优于纯 Python 实现。对毕业设计而言用 NLPIR 还有一个隐性好处它自带词频统计和情感分析思路你可以在答辩时对比「jieba 方案 vs NLPIR 方案」的准确率差异这部分工作量很加分。2.2 初始化 NLPIR 的完整代码与授权文件处理拿到源码包后先别急着跑主程序NLPIR 的第一步是把动态库和 Data 目录的路径对好。下面是一段常见初始化代码我在 Windows 上验证过import ctypes from ctypes import c_char_p, c_int # 注意这里用到绝对路径部署到其他机器要改成相对路径 nlpir ctypes.CDLL(rD:\NLPIR\NLPIR64.dll) # 编码参数1 表示 UTF-8老版本需要 0(GBK) init_flag nlpir.Init( c_char_p(rD:\NLPIR\Data.encode(utf-8)), c_int(1), c_int(1), c_int(0) ) if init_flag 0: raise RuntimeError(NLPIR 初始化失败检查 Data 目录和授权文件) else: print(NLPIR 初始化成功)逻辑说明Init的第一个参数是 Data 目录第二、三个参数控制编码和运行模式第四个参数是保留项传 0 即可。代码里encode(utf-8)这一步很容易漏因为CDLL要求 bytes 而不是 str忘掉就直接报 TypeError。Data目录里通常有Configure.xml和授权文件如user.lic授权文件过期会返回 0我有一年在答辩前两天遇到这个问题后文避坑部分细说。参数说明如果你的新闻数据量在几万条内建议用NLPIR64.dll如果机器是 32 位 Python 环境请换 32 位版本Init的返回值不是布尔值正确判断方式是init_flag 0视为失败非零值才继续。2.3 情感分析的返回结果怎么解析NLPIR 的情感分析接口在不同发行包里名字不一致有的叫GetParagraphSentiment有的在主函数的ParagraphProcess之后才能调用。我的血泪经验是别默认接口存在先看解压包里的头文件通常叫NLPIR.h或NLPIR64.h搜Sentiment关键字以实际头文件为准。下面是我项目里常用的兼容写法先分词再按情感词表兜底打分即使 NLPIR 自带情感接口不可用也能保证流程不断# 注意这是兜底方案可以用 NLPIR 自带情感接口替换 neg_words [下降, 亏损, 暴跌, 违规, 调查] pos_words [增长, 盈利, 突破, 利好, 签署] degree_words {非常: 2.0, 较: 1.5, 略显: 0.5} def sentiment_score(seg_list): score 0.0 for i, word in enumerate(seg_list): s 0.0 if word in pos_words: s 1.0 elif word in neg_words: s -1.0 if s ! 0 and i 0 and seg_list[i-1] in degree_words: s * degree_words[seg_list[i-1]] score s return max(-1.0, min(1.0, score / max(1, len(seg_list))))逻辑说明把分词结果逐词匹配正向词表和负向词表遇到程度副词会加倍或减半最后除以词数做归一化避免长文章天然得分偏高。这一段是为了兜底真正交付给答辩老师看的是 NLPIR 分词后的情感统计表它能把「正面占比、负面占比、中性占比」作为舆情指数口径。参数说明seg_list来自ParagraphProcess的分词结果通常是空格分隔的字符串先split()再传入。毕业设计如果时间紧直接用这个兜底方案也够但论文里要写清楚词表来源和阈值设定依据。3. 网易新闻采集层爬虫去重、时间窗口和评论字段设计3.1 采集范围与 URL 规律资讯列表到正文详情网易新闻的页面结构和几年一样列表页里的每条新闻链接藏在a href...标签里。我一般先抓news.163.com下几个固定频道国内、国际、社会、科技再用正则从列表页提取详情 URL。下面的代码是一个可运行的采集器骨架import re import time import requests 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 } def fetch_article_urls(channel_url): resp requests.get(channel_url, headersHEADERS, timeout10) resp.encoding utf-8 html resp.text # 网易正文 URL 基本都带 /article/ 或 /a/ 等特征 pattern re.compile(rhref(https?://www\.163\.com/\d/(?:article|a)/[^])) return list(set(pattern.findall(html)))逻辑说明先请求频道首页再用正则匹配详情链接。这里的pattern是按网易常见 URL 结构写的如果你的源程序包或抓包结果不一致可以按href(...163.com/...)放宽匹配但要注意放宽后会混入广告和推荐位链接所以正文解析阶段还要套标题规则过滤。参数说明encoding必须显式设置否则 requests 可能按 ISO-8859-1 默认解码中文全变乱码。timeout10不能省网易页面偶发卡顿不设超时会让整个爬虫线程挂死。3.2 爬虫入库的字段设计与数据清洗抓到正文后入库前要做三件事去 HTML 标签、去噪「本文来源」「责任编辑」这类固定文案、清洗不可见字符。这是一个很常见的清洗函数import re def clean_content(raw_html): text re.sub(r[^], , raw_html) text re.sub(r\s, , text) text text.replace(本文来源, ).replace(责任编辑, ) return text.strip()逻辑说明第一行正则去掉标签第二行合并空白第三行直接替换编辑页的固定后缀。实际操作中网易正文里还会夹带「分享到微信」「扫描二维码」这类无意义内容建议在入库前做一次包含关键词的过滤把长度小于 50 的正文视为解析失败丢入异常表。字段设计我建议直接用这张表答辩时也方便解释数据维度字段名类型说明urlvarchar(500)详情页链接作为唯一键titlevarchar(255)新闻标题contentmediumtext清洗后的正文channelvarchar(50)频道国内/国际/科技publish_timedatetime发布时间用于按天聚合comment_countint评论数如果采集到sentimentfloat情感得分范围 -1 到 1crawl_timedatetime入库时间用于增量判断这张表可以直接套进 MySQL 或 SQLiteSQLite 更适合毕业设计演示直接把.db文件拷到笔记本就能跑省去装数据库的麻烦。3.3 断点续抓与增量采集防止重复跑爬虫最怕跑一半失败重跑时又重复入库。我在代码里会加一道「抓前查库」先查url是否已存在存在就跳过。同时加入简单的限速逻辑避免请求频率太高触发反爬。import sqlite3 import time conn sqlite3.connect(news.db) cur conn.cursor() def already_exists(url): cur.execute(SELECT 1 FROM news WHERE url ?, (url,)) return cur.fetchone() is not None for url in article_urls: if already_exists(url): continue detail fetch_article(url) if detail: insert_news(url, detail) time.sleep(0.5) # 限速防止被封逻辑说明每次循环先查重再处理最后 sleep 0.5 秒。time.sleep的间隔按量调整抓 1000 条以上建议 0.8~1 秒只演示 200 条以内的场景 0.3 秒足够别为了省时间把 IP 搭进去网易的反爬策略是封 IP 后整个网段都要等很久。参数说明sleep(0.5)看起来简单但它是整个采集策略里最关键的稳定性参数。如果你在教室演示时网络波动大可以把它改成random.uniform(0.3, 0.8)让请求间隔不完全规律这是我在实际项目里常用的遮羞布式优化。4. 舆情指标计算和可视化从词频统计到热度曲线4.1 正负情感指数怎么归一化数据入库后舆情分析的核心不是分词本身而是把零散的新闻变成可解释的指标。我常用的口径是每天的正向新闻数减负向新闻数除以当天采集总量得到一个 [-1, 1] 的舆情指数。如果某天全是负面报道指数趋近 -1既无正面也无负面趋近 0。import pandas as pd df pd.read_sql(SELECT publish_time, sentiment FROM news, conn) df[publish_time] pd.to_datetime(df[publish_time]) df[day] df[publish_time].dt.date def group_sentiment(g): pos_cnt (g[sentiment] 0.2).sum() neg_cnt (g[sentiment] -0.2).sum() total len(g) return (pos_cnt - neg_cnt) / total if total 0 else 0 day_index df.groupby(day).apply(group_sentiment)逻辑说明先按天分组再分别统计情感得分大于 0.2 的正向新闻和小于 -0.2 的负向新闻。0.2 这个阈值不是写死的我在项目里测过阈值过低比如 0.05会把中性的客观报道都算进正向阈值过高比如 0.5又只捞得出极强情绪的文章所以 0.2 是我在网易新闻语料上试出来比较稳的默认值。参数说明groupby(day).apply(...)在小数据集上没问题数据量超过 5 万条后apply会很慢建议直接改用透视表先构造pos_flag和neg_flag两列再按天求和。毕业设计的展示数据量一般几千条用apply反而更直观。4.2 词云和趋势图matplotlib 和 wordcloud 的参数坑可视化是答辩时的门面也是最容易翻车的一环。中文词云如果直接拿默认字体生成大概率吐一堆方块因为 wordcloud 默认字体不支持中文。我踩过以后固定用下面这套配置from wordcloud import WordCloud import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决 Matplotlib 中文乱码 plt.rcParams[axes.unicode_minus] False wc WordCloud( font_pathrC:\Windows\Fonts\simhei.ttf, width1200, height800, background_colorwhite, max_words200, stopwords{新闻, 网易, 原标题, 记者, 报道} ) wc.generate(text_data) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(wordcloud.png, dpi150)逻辑说明font_path指定黑体文件路径这是中文词云不出现方块的前提。stopwords要按语料微调网易新闻里「网易」「原标题」这类词频极高不屏蔽的话词云正中间会被它们占满真正有分析价值的「经济」「政策」反而被挤到边缘。参数说明max_words200控制词数量论文配图建议 150 左右更清晰interpolationbilinear让词云边缘平滑导出 png 时dpi150足够打印清晰再高会导致文件过大。热度曲线的做法类似先把按天聚合好的day_index转成 DataFrame再用plt.plot()绘制曲线下方用fill_between填充渐变颜色一眼能看出突发事件导致的舆情尖峰plt.figure(figsize(12, 5)) plt.plot(df_index[day], df_index[index], color#d33, linewidth2) plt.fill_between(df_index[day], df_index[index], 0, color#d33, alpha0.2) plt.title(网易新闻舆情指数趋势, fontsize14) plt.xlabel(日期) plt.ylabel(舆情指数) plt.xticks(rotation45) plt.tight_layout() plt.savefig(trend.png, dpi150)逻辑说明fill_between里第二个参数是 y 轴下界我传 0 表示在指数 0 线上方填充负面为主的日子曲线下方留白视觉效果更清楚。rotation45让日期标签错开不然 4 月中旬多个日期的文字叠成一团黒。4.3 批量导出报表Excel 和 JSON 双格式答辩时老师大概率会问「这些数据能导出来吗」。我建议在代码里直接集成两类导出Excel 给答辩现场的演示材料JSON 给后续扩展比如接入前端图表框架。import json from datetime import datetime df.to_excel(舆情分析结果.xlsx, indexFalse, engineopenpyxl) with open(舆情日报.json, w, encodingutf-8) as f: json.dump({ generated_at: datetime.now().strftime(%Y-%m-%d %H:%M:%S), total_news: len(df), index_series: day_index.to_dict() }, f, ensure_asciiFalse, indent2)逻辑说明to_excel需要额外装openpyxl源码包如果在 Python 3.10 环境里跑记得pip install openpyxl否则会在导出时报 ModuleNotFoundError。JSON 导出时ensure_asciiFalse不能省否则中文全部变成\uXXXX转义老师在记事本里打开是乱码。5. 常见问题与避坑记录NLPIR 授权、编码和内存翻车实录5.1 NLPIR 授权过期导致 Init 返回 0现象代码一启动就报RuntimeError: NLPIR 初始化失败但昨天还能跑。 原因NLPIR 的动态库带授权期限日期过了就拒绝初始化报错信息还很不明确。 解决检查Data目录下的授权文件把系统时间临时改回授权期内只能应急验证最终要更新授权。这个坑我建议答辩前一周主动排查不要等现场再翻车。5.2 编码不一致导致分词结果全是热词乱码现象分词结果里每个词都带\x00或一堆乱码情感得分为 0。 原因Init里编码参数设为 1UTF-8但待分析的字符串用 gbk 编码传入动态库按 UTF-8 解析出一堆非法字符。 解决统一入口所有待分析文本在进入ParagraphProcess之前强制encode(utf-8)不要依赖外部文件的原编码。这个问题的排查顺序应该是先打印repr(text[:20])看是 str 还是 bytes再看编码参数是否一致。5.3 批量分析时内存爆炸现象抓了 2 万条新闻后程序跑到一半 MemoryError屏幕直接卡死。 原因我把所有正文一次性读完并存在 list 里然后用循环逐条调 NLPIR动态库返回的中文字符串没及时释放Python 侧引用堆积。 解决改成逐条读取、逐条分析、逐条写回 SQLite不再保存全量 list。处理完一条就del text, seg_result。NLPIR 在循环里高频调用时建议每处理 1000 条做一次nlpir.Exit()再重新Init()释放动态库内部缓存。5.4 情感分析把中性新闻误判为负面现象一篇常规政策解读文章只因为出现了「下降」「波动」就被打上 -0.4把某天的舆情指数直接拉低。 原因我的兜底情感词表里把「波动」「下调」这类中性词归入了负面词表而且没考虑否定词。 解决把「波动」「调整」「变化」等词移出词表增加否定词处理如果负面词前面两个字内出现「不」「未」「无」则翻转情感符号。这是朴素做法但比纯词表匹配的准确率明显提升够毕业设计使用。5.5 答辩现场图表中文全部变方块现象趋势图标题、坐标轴全部是方块词云没有中文只剩英文和数字。 原因现场电脑没有安装 SimHei 字体而代码里plt.rcParams只设置了SimHei一个候选字体系统找不到就直接显示方块。 解决字体配置改为[SimHei, Microsoft YaHei, Arial Unicode MS]多候选列表词云的字体路径改为项目内相对路径即把simhei.ttf拷贝到源码包的fonts目录下用os.path拼接路径。从那以后我每次答辩演示前都会先在现场的机器上把字体路径打印出来确认存在再决定是否调参数。6. 让系统更耐看从跑通到可验收的验证脚本做到这一步系统的核心链路已经通了NLPIR 分词与分析、网易新闻采集入库、舆情指数计算、趋势图和词云导出。最后值得做的事是把整个链路做成一条严格控制顺序的验证流程输出带时间戳的分步日志。一来方便自己排查二来在答辩现场面对老师的演示要求能直接跑一个「一键全链路」的脚本比临时敲命令从容得多。import traceback from datetime import datetime def step(name): def decorator(func): def wrapper(*args, **kwargs): print(f[{datetime.now().strftime(%H:%M:%S)}] 开始: {name}) try: result func(*args, **kwargs) print(f[{datetime.now().strftime(%H:%M:%S)}] 完成: {name}) return result except Exception: print(f[{datetime.now().strftime(%H:%M:%S)}] 失败: {name}) traceback.print_exc() raise return wrapper return decorator用法很简单给初始化、采集、分析、可视化四个函数各加一行step(初始化NLPIR)之类的装饰器。每一步失败时日志会立即告诉你失败位置至少能定位到是授权文件、网络请求还是字体问题不用再黑匣子式地猜。验证时我习惯三连先跑一次小样本只抓 50 条看四条日志是否全绿再跑全量观察耗时最后把生成的两个 PNG 文件大小确认一下如果趋势图只有 5KB 大概率是空数据如果是 200KB 以上基本说明图表内容正常。从那以后我每次拿这套系统做演示前都强制走一遍这个小脚本宁可多花两分钟也不在现场让老师看我修改参数。希望帮到你。本文还有配套的精品资源点击获取
返回列表