Chord - Ink Shadow 与Python爬虫结合:自动化舆情分析系统
Chord - Ink & Shadow 与Python爬虫结合:自动化舆情分析系统
最近和几个做市场、公关的朋友聊天,他们都在抱怨同一件事:每天要花大量时间刷微博、看论坛、盯新闻,就为了搞清楚自家产品或者竞争对手在网上的口碑怎么样。人工看不仅累,还容易漏掉关键信息,等负面舆情发酵起来再处理,往往已经晚了。
这让我想起之前做的一个小项目,正好能解决这个问题。我把Python爬虫和一款叫Chord - Ink & Shadow的大模型工具结合了起来,搭了一套自动化的舆情分析系统。简单来说,就是让爬虫去网上帮你“看”和“听”,然后把看到听到的内容交给大模型去“想”和“总结”,最后你拿到手的,就是一份清晰、有条理的舆情报告。
今天这篇文章,我就来详细聊聊这套系统是怎么工作的,以及如何从零开始把它搭建起来。整个过程并不复杂,即使你不是技术专家,跟着步骤走也能搞定。
1. 为什么需要自动化舆情分析?
在聊具体技术之前,我们先看看传统舆情监控的痛点。
想象一下,你是一家手机品牌的市场专员。新品发布后,你需要知道用户反馈。于是你打开微博,搜索品牌名,一页页翻看评论;再去几个主流的科技论坛,浏览相关的讨论帖;还要关注新闻客户端,看看有没有媒体报道。这个过程耗时耗力,而且:
- 信息过载:有效信息被淹没在海量的重复、无关甚至垃圾内容里。
- 主观偏差:人工阅读容易受情绪影响,对正面和负面信息的判断可能不准确。
- 效率低下:一个人一天能看完的内容非常有限,无法做到7x24小时监控。
- 反应滞后:等人工发现负面苗头时,可能已经错过了最佳干预时机。
自动化系统的价值就在于,它能不知疲倦地、客观地、全天候地完成这些工作,把我们从重复劳动中解放出来,去处理更需要人类判断力的决策。
2. 系统核心:爬虫抓取与大模型分析
这套系统的核心流程可以概括为三步:抓取 -> 处理 -> 分析。
- 抓取(Python爬虫):这是系统的“眼睛”和“耳朵”。我们编写Python脚本,让它自动访问指定的网站、论坛或社交媒体平台,按照我们设定的规则(比如关键词、时间范围、板块)抓取相关的帖子、评论、新闻报道等文本内容。
- 处理(数据清洗):原始抓取的数据往往很“脏”,包含HTML标签、广告、无关链接、重复内容等。这一步就是做“清洗”,把干净的文本内容提取出来,为下一步分析做好准备。
- 分析(Chord - Ink & Shadow):这是系统的“大脑”。我们将清洗后的文本批量送入大模型。这里主要让它做三件事:
- 情感分析:判断这段文本是正面、负面还是中性的评价。
- 主题归纳:从大量文本中自动提炼出几个核心讨论话题(例如,“电池续航”、“拍照效果”、“系统流畅度”)。
- 摘要生成:对于长篇的评测或报道,生成一段简洁的概要,让你快速了解核心观点。
最终,系统会生成一份结构化的报告,告诉你:过去24小时,关于你的品牌,网上主要讨论了哪几个话题,整体情绪是偏正面还是负面,以及有哪些代表性的声音。
3. 动手搭建:从数据抓取开始
我们先从最基础的爬虫部分开始。这里以抓取一个模拟的科技论坛板块为例。在实际操作中,请务必遵守目标网站的robots.txt协议,并控制请求频率,避免对对方服务器造成压力。
我们需要安装几个Python库:
pip install requests beautifulsoup4 pandasrequests:用于发送网络请求。beautifulsoup4:用于解析HTML,提取我们需要的数据。pandas:用于后续的数据处理和保存。
下面是一个简单的爬虫示例,用于抓取论坛帖子列表的标题和链接:
import requests from bs4 import BeautifulSoup import pandas as pd import time def fetch_tech_forum(base_url, pages=3): """ 抓取科技论坛的帖子列表 :param base_url: 论坛板块的基础URL :param pages: 要抓取的页数 :return: 包含帖子标题和链接的DataFrame """ all_posts = [] for page in range(1, pages + 1): # 构造每一页的URL,假设分页参数是 ‘page=‘ url = f"{base_url}?page={page}" print(f"正在抓取: {url}") try: # 发送请求 headers = {'User-Agent': 'Mozilla/5.0'} # 模拟浏览器访问 response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 检查请求是否成功 # 解析HTML soup = BeautifulSoup(response.text, 'html.parser') # 假设帖子标题在 class 为 ‘post-title‘ 的 <a> 标签里 # 你需要根据目标网站的实际HTML结构修改这里的选择器 post_links = soup.select('a.post-title') for link in post_links: post_title = link.get_text(strip=True) post_url = link.get('href') # 确保链接是完整的URL if post_url and not post_url.startswith('http'): post_url = requests.compat.urljoin(base_url, post_url) if post_title and post_url: all_posts.append({'title': post_title, 'url': post_url}) # 礼貌性延迟,避免请求过快 time.sleep(1) except requests.RequestException as e: print(f"抓取第{page}页时出错: {e}") continue # 转换为DataFrame并去重 df_posts = pd.DataFrame(all_posts) df_posts.drop_duplicates(subset=['url'], inplace=True) return df_posts # 使用示例 (请替换为真实的、允许爬取的URL) # base_url = "https://example-tech-forum.com/phone-reviews" # posts_df = fetch_tech_forum(base_url, pages=2) # print(f"共抓取到 {len(posts_df)} 条帖子") # print(posts_df.head())这段代码定义了一个函数,可以抓取多页的帖子列表。抓取到的数据(标题和链接)被保存在一个pandas DataFrame里。这只是第一步,我们只拿到了帖子的入口。接下来,我们需要根据这些链接,去抓取每个帖子里的详细内容和评论。
4. 数据清洗与预处理
抓取到的原始文本通常不能直接扔给大模型。我们还需要进行清洗。
import re def clean_text(raw_text): """ 清洗单条文本 :param raw_text: 原始文本 :return: 清洗后的文本 """ if not isinstance(raw_text, str): return "" # 1. 移除HTML标签 text = re.sub(r'<[^>]+>', '', raw_text) # 2. 移除多余的空白字符(换行、制表符、连续空格) text = re.sub(r'\s+', ' ', text) # 3. 移除一些常见的无关字符(根据实际情况调整) text = re.sub(r'[\u200b-\u200f\u202a-\u202e]', '', text) # 移除零宽字符 text = text.strip() # 4. 简单过滤:如果文本太短,可能是无意义内容(如“顶”、“沙发”) if len(text) < 5: return "" return text def preprocess_posts(posts_df): """ 预处理帖子DataFrame,新增清洗后的内容字段 :param posts_df: 包含‘title‘和‘content‘(待抓取)的DataFrame :return: 预处理后的DataFrame """ # 假设我们已经通过另一个函数抓取到了帖子的详细内容,并存放在‘raw_content‘列 if 'raw_content' not in posts_df.columns: print("DataFrame中缺少‘raw_content‘列,请先抓取帖子内容。") return posts_df # 应用清洗函数 posts_df['cleaned_content'] = posts_df['raw_content'].apply(clean_text) # 过滤掉清洗后为空的内容 posts_df = posts_df[posts_df['cleaned_content'] != ""] # 可以合并标题和内容,作为最终的分析文本 posts_df['analysis_text'] = posts_df['title'] + "。 " + posts_df['cleaned_content'] return posts_df # 假设我们已经有了带‘raw_content‘的DataFrame: full_posts_df # cleaned_df = preprocess_posts(full_posts_df) # print(cleaned_df[['title', 'analysis_text']].head())清洗后,我们得到了相对干净、结构化的文本数据,存放在analysis_text字段中。现在,这些数据已经准备好交给“大脑”进行处理了。
5. 接入“大脑”:Chord - Ink & Shadow 分析
Chord - Ink & Shadow 提供了API接口,可以方便地集成到我们的Python脚本中。这里的关键是构造合适的提示词(Prompt),让大模型理解我们想要它做什么。
假设我们已经部署好了Chord - Ink & Shadow服务,并获得了API访问的基地址和密钥。
import requests import json class ChordAnalyzer: def __init__(self, base_url, api_key): self.base_url = base_url.rstrip('/') self.api_key = api_key self.headers = { 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' } def analyze_sentiment_and_topic(self, text): """ 调用大模型进行情感分析和主题归纳 :param text: 待分析的文本 :return: 包含情感、主题和摘要的字典 """ # 构造提示词。这是决定分析质量的关键! prompt = f""" 请对以下关于科技产品的用户评论进行深入分析: 「{text}」 请按照以下JSON格式回复: {{ "sentiment": "正面/负面/中性", "primary_topic": "总结为一个核心话题,如‘电池续航‘、‘拍照效果‘", "summary": "用一句话概括这段评论的核心观点" }} 注意:只返回JSON对象,不要有其他任何解释。 """ payload = { "model": "ink-shadow-latest", # 根据实际模型名称调整 "messages": [ {"role": "user", "content": prompt} ], "temperature": 0.2, # 较低的温度使输出更稳定、更聚焦 "max_tokens": 300 } try: response = requests.post( f"{self.base_url}/v1/chat/completions", headers=self.headers, data=json.dumps(payload), timeout=30 ) response.raise_for_status() result = response.json() # 提取模型返回的内容 content = result['choices'][0]['message']['content'].strip() # 尝试解析JSON analysis_result = json.loads(content) return analysis_result except (requests.RequestException, json.JSONDecodeError, KeyError) as e: print(f"分析文本时出错: {e}, 文本片段: {text[:50]}...") # 返回一个默认的错误结构 return { "sentiment": "未知", "primary_topic": "分析失败", "summary": "" } def batch_analyze(self, texts_list): """批量分析文本列表""" results = [] for text in texts_list: result = self.analyze_sentiment_and_topic(text) results.append(result) # 短暂延迟,避免API速率限制 time.sleep(0.5) return results # 使用示例 # analyzer = ChordAnalyzer(base_url="http://your-chord-server:port", api_key="your-api-key") # sample_text = cleaned_df['analysis_text'].iloc[0] # analysis = analyzer.analyze_sentiment_and_topic(sample_text) # print(analysis)这段代码定义了一个分析器类。核心是analyze_sentiment_and_topic方法,它构造了一个清晰的提示词,要求模型以指定的JSON格式返回情感、主题和摘要。清晰的指令是获得高质量、结构化结果的前提。
6. 整合与报告生成
现在,我们把前面所有步骤串联起来,并生成一份可视化的报告。
def run_full_pipeline(base_url, forum_pages=2): """ 运行完整的舆情分析流水线 """ print("步骤1: 抓取帖子列表...") posts_df = fetch_tech_forum(base_url, pages=forum_pages) if posts_df.empty: print("未抓到任何帖子,流程终止。") return None print(f"步骤2: 抓取到 {len(posts_df)} 条帖子,开始抓取详情...") # 这里需要实现另一个函数 fetch_post_details(posts_df) 来抓取每个链接的详细内容 # full_df = fetch_post_details(posts_df) # 为了示例,我们假设 full_df 已经存在并包含 ‘raw_content‘ print("步骤3: 清洗数据...") cleaned_df = preprocess_posts(full_df) # 假设full_df已定义 print("步骤4: 使用大模型进行分析...") # 初始化分析器 (请填入你的真实地址和密钥) analyzer = ChordAnalyzer(base_url="http://localhost:8080", api_key="fake-key-for-demo") # 取前10条进行演示,避免长时间运行 sample_texts = cleaned_df['analysis_text'].head(10).tolist() analysis_results = analyzer.batch_analyze(sample_texts) # 将分析结果合并回DataFrame result_df = cleaned_df.head(10).copy() result_df = result_df.reset_index(drop=True) result_df['sentiment'] = [r.get('sentiment', '未知') for r in analysis_results] result_df['topic'] = [r.get('primary_topic', '未知') for r in analysis_results] result_df['ai_summary'] = [r.get('summary', '') for r in analysis_results] print("步骤5: 生成分析报告...") generate_report(result_df) return result_df def generate_report(df): """生成简单的文本报告""" print("\n" + "="*50) print("舆情分析报告") print("="*50) # 1. 情感分布 sentiment_counts = df['sentiment'].value_counts() print(f"\n1. 情感分布:") for sentiment, count in sentiment_counts.items(): print(f" - {sentiment}: {count} 条") # 2. 热门话题 print(f"\n2. 热门讨论话题:") topic_counts = df['topic'].value_counts().head(5) # 取前5 for topic, count in topic_counts.items(): print(f" - {topic}: {count} 次提及") # 3. 代表性负面评价摘要 negative_samples = df[df['sentiment'] == '负面'] if not negative_samples.empty: print(f"\n3. 代表性负面评价摘要:") for idx, row in negative_samples.head(3).iterrows(): # 取前3条 print(f" - 主题「{row['topic']}」: {row['ai_summary']}") # 4. 保存详细结果到CSV report_filename = f"sentiment_report_{pd.Timestamp.now().strftime('%Y%m%d_%H%M')}.csv" df.to_csv(report_filename, index=False, encoding='utf-8-sig') print(f"\n详细分析结果已保存至: {report_filename}") print("="*50) # 启动完整流程 (需要先完善 fetch_post_details 函数) # final_result = run_full_pipeline("https://example-forum.com/板块")generate_report函数创建了一个简单的命令行报告,展示了情感分布、热门话题和关键负面评价。同时,所有详细数据会被保存到CSV文件中,方便进一步用Excel或BI工具进行深度分析或可视化。
7. 实际应用中的几点建议
在实际跑这套系统时,有几个地方需要特别注意:
- 爬虫的伦理与法律:始终尊重
robots.txt,控制请求速率,不要抓取个人隐私信息或受版权严格保护的内容。最好在抓取前咨询法律意见。 - 提示词工程:大模型的分析效果极大程度上依赖于提示词。多花时间调试你的提示词,让它更清晰、更具体。对于不同的分析维度(比如区分“产品功能吐槽”和“售后服务投诉”),可能需要设计不同的提示词模板。
- 数据质量:垃圾进,垃圾出。数据清洗环节非常重要,清洗得越干净,大模型的分析结果就越可靠。
- 成本与性能:大模型API调用通常按token收费。对于海量文本,可以先通过关键词过滤、去重等传统方法减少需要深度分析的文本量,以控制成本。同时,关注API的响应速度,这会影响整个系统的时效性。
- 系统扩展:本文是一个单机版的演示。如果要监控成百上千个信源,你需要考虑任务队列(如Celery)、分布式爬虫、数据库存储等更复杂的架构。
8. 总结
把Python爬虫和Chord - Ink & Shadow这样的模型结合起来,搭建自动化舆情分析系统,思路其实很直接:让擅长大规模、规则化数据采集的爬虫,和擅长理解、归纳自然语言的模型各司其职。
从实践来看,这套方案对于从公开的论坛、新闻、社交媒体中快速把握舆论风向非常有效。它不能完全取代人工研判,因为模型可能无法理解某些反讽、隐喻或高度依赖背景知识的评论,但它是一个极其强大的辅助工具,能帮你节省至少80%的信息搜集和初步整理时间。
如果你正被海量的网络信息所困扰,不妨试着用这个思路搭建一个属于你自己的监控小系统。从一个小的、具体的信源开始,比如就盯一个竞争对手的官方微博评论区,跑通整个流程,看到价值后再逐步扩大范围。技术本身并不神秘,关键是把工具用在真正能提升效率的地方。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
