㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~
㊙️本期爬虫难度指数:⭐⭐⭐
🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:
-
- [🌟 开篇语](#🌟 开篇语)
- [📌 章节摘要](#📌 章节摘要)
- [🌐 新闻网站爬取:反爬对抗与内容提取](#🌐 新闻网站爬取:反爬对抗与内容提取)
- [🧹 文本清洗:从脏数据到干净语料](#🧹 文本清洗:从脏数据到干净语料)
- [🔍 文本去重:SimHash与MinHash](#🔍 文本去重:SimHash与MinHash)
- [✂️ 中文分句:处理歧义与嵌套](#✂️ 中文分句:处理歧义与嵌套)
- [📊 TF-IDF特征提取](#📊 TF-IDF特征提取)
- [🎯 文本聚类:发现主题](#🎯 文本聚类:发现主题)
- [🏗️ 完整舆情语料处理流程](#🏗️ 完整舆情语料处理流程)
- [💡 最佳实践总结](#💡 最佳实践总结)
-
- [1. 新闻爬取策略](#1. 新闻爬取策略)
- [2. 文本清洗要点](#2. 文本清洗要点)
- [3. 去重算法选择](#3. 去重算法选择)
- [4. TF-IDF优化技巧](#4. TF-IDF优化技巧)
- [📚 本章总结](#📚 本章总结)
- [🌟 文末](#🌟 文末)
-
- [✅ 专栏持续更新中|建议收藏 + 订阅](#✅ 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
- [✅ 免责声明](#✅ 免责声明)
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。
💕订阅后更新会优先推送,按目录学习更高效💯~
📌 章节摘要
在信息爆炸的时代,舆情监控 已成为企业、政府、媒体的核心需求。如何从海量新闻、博客、社交媒体中快速发现热点事件、追踪舆论走向、识别潜在危机?这需要构建一套端到端的舆情语料处理系统。
本章将深入讲解如何构建生产级舆情语料项目,从新闻网站爬取原始数据,到文本清洗、去重、分句,再到TF-IDF特征提取、主题聚类,最终形成可用于机器学习的高质量语料库。
读完你将掌握:
- 🌐 新闻/博客抓取的专业技巧(反爬对抗、内容提取、时效性保障)
- 🧹 文本清洗的完整方案(去除HTML/噪声、繁简转换、错字纠正)
- 🔍 文本去重的4种算法(SimHash、MinHash、Jaccard、语义去重)
- ✂️ 中文分句的挑战与解决(歧义处理、引号嵌套、省略号)
- 📊 TF-IDF特征工程(改进方案、动态更新、领域词典)
- 🎯 文本聚类的实战(K-Means、DBSCAN、层次聚类、主题模型)
🌐 新闻网站爬取:反爬对抗与内容提取
新闻网站特征分析
python
import requests
from bs4 import BeautifulSoup
from newspaper import Article
import feedparser
from datetime import datetime, timedelta
import time
from urllib.parse import urljoin, urlparse
import json
from pathlib import Path
class NewsSourceAnalyzer:
"""
新闻源分析器
功能:
1. 识别新闻网站类型(通用CMS、自定义、API)
2. 检测反爬机制
3. 提取内容规则
4. 评估更新频率
"""
def __init__(self):
# 常见新闻CMS标识
self.cms_signatures = {
'WordPress': ['wp-content', 'wp-includes', 'wp-json'],
'Drupal': ['drupal.js', 'sites/all/themes'],
'Joomla': ['joomla', 'com_content'],
'DedeCMS': ['dedecms', 'templets'],
'Discuz': ['discuz', 'forum.php'],
'Z-Blog': ['zb_system', 'zblogphp']
}
# 反爬特征
self.anti_spider_features = [
'cloudflare',
'captcha',
'verify',
'robot',
'access denied'
]
def analyze(self, url):
"""
分析新闻网站
参数:
url: 网站首页URL
返回:
{
'cms_type': str, # CMS类型
'has_rss': bool, # 是否有RSS
'rss_urls': list, # RSS链接
'anti_spider': list, # 反爬特征
'content_selector': str, # 内容选择器(猜测)
'update_frequency': str # 更新频率估计
}
"""
print(f"\n🔍 分析新闻网站: {url}")
result = {
'url': url,
'cms_type': 'Unknown',
'has_rss': False,
'rss_urls': [],
'anti_spider': [],
'content_selector': None,
'update_frequency': 'Unknown'
}
try:
# 请求页面
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
html = response.text.lower()
soup = BeautifulSoup(response.text, 'html.parser')
# 检测CMS类型
for cms, signatures in self.cms_signatures.items():
if any(sig.lower() in html for sig in signatures):
result['cms_type'] = cms
print(f" CMS类型: {cms}")
break
# 检测反爬机制
for feature in self.anti_spider_features:
if feature in html:
result['anti_spider'].append(feature)
if result['anti_spider']:
print(f" ⚠️ 检测到反爬特征: {result['anti_spider']}")
# 查找RSS链接
rss_links = soup.find_all('link', type='application/rss+xml')
atom_links = soup.find_all('link', type='application/atom+xml')
for link in rss_links + atom_links:
rss_url = urljoin(url, link.get('href', ''))
result['rss_urls'].append(rss_url)
if result['rss_urls']:
result['has_rss'] = True
print(f" ✅ 发现RSS订阅: {len(result['rss_urls'])} 个")
# 猜测内容选择器
common_selectors = [
'article',
'.article-content',
'#article-content',
'.post-content',
'.entry-content',
'.content',
'main'
]
for selector in common_selectors:
if soup.select(selector):
result['content_selector'] = selector
print(f" 内容选择器(猜测): {selector}")
break
return result
except Exception as e:
print(f" ❌ 分析失败: {e}")
return result
# 使用示例
analyzer = NewsSourceAnalyzer()
# 分析主流新闻网站
news_sites = [
'https://news.sina.com.cn',
'https://news.163.com',
'https://www.thepaper.cn',
'https://www.bbc.com/news',
'https://www.nytimes.com'
]
for site in news_sites[:1]: # 示例只分析第一个
analysis = analyzer.analyze(site)
通用新闻爬虫(基于newspaper3k)
python
from newspaper import Article, Source
import newspaper
from datetime import datetime
import hashlib
import re
class UniversalNewsCrawler:
"""
通用新闻爬虫
基于newspaper3k库,支持自动内容提取
优势:
- 自动识别标题、正文、作者、发布时间
- 支持多语言(70+种语言)
- 自动过滤广告和导航
- 支持图片提取
"""
def __init__(self, language='zh'):
"""
初始化爬虫
参数:
language: 语言代码('zh', 'en'等)
"""
self.language = language
# 配置
self.config = newspaper.Config()
self.config.language = language
self.config.browser_user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
self.config.request_timeout = 10
self.config.number_threads = 3
self.config.memoize_articles = False # 不缓存(避免遗漏更新)
def crawl_article(self, url):
"""
爬取单篇文章
参数:
url: 文章URL
返回:
{
'url': str,
'title': str,
'text,
'keywords': list,
'summary': str,
'images': list,
'movies': list,
'top_image': str,
'html': str,
'meta_description': str,
'meta_keywords': str,
'meta_lang': str,
'canonical_link': str
}
"""
try:
# 创建Article对象
article = Article(url, config=self.config)
# 下载
article.download()
# 解析
article.parse()
# NLP处理(关键词提取、摘要)
try:
article.nlp()
except:
pass # NLP处理可能失败,不影响主流程
# 提取数据
result = {
'url': article.url,
'title': article.title,
'text': article.text,
'authors': article.authors,
'publish_date': article.publish_date,
'keywords': article.keywords,
'summary': article.summary,
'images': list(article.images),
'movies': article.movies,
'top_image': article.top_image,
'html': article.html,
'meta_description': article.meta_description,
'meta_keywords': article.meta_keywords,
'meta_lang': article.meta_lang,
'canonical_link': article.canonical_link,
# 额外信息
'crawl_time': datetime.now(),
'source_domain': urlparse(url).netloc,
'text_length': len(article.text),
'word_count': len(article.text.split()) if article.text else 0,
'hash': hashlib.md5(article.text.encode()).hexdigest() if article.text else None
}
print(f"✅ 成功抓取: {article.title[:50]}...")
print(f" 字数: {result['word_count']}")
print(f" 发布时间: {article.publish_date}")
return result
except Exception as e:
print(f"❌ 抓取失败 [{url}]: {e}")
return None
def crawl_source(self, source_url, max_articles=100):
"""
爬取整个新闻源
参数:
source_url: 新闻源首页URL
max_articles: 最大文章数
返回:
文章列表
"""
print(f"\n📰 爬取新闻源: {source_url}")
try:
# 创建Source对象
source = Source(source_url, config=self.config)
# 下载并解析
source.download()
source.parse()
print(f" 发现 {source.size()} 篇文章")
# 爬取文章
articles = []
for i, article_obj in enumerate(source.articles[:max_articles]):
if i >= max_articles:
break
try:
article_obj.download()
article_obj.parse()
if article_obj.text:
result = {
'url': article_obj.url,
'title': article_obj.title,
'text': article_obj.text,
'authors': article_obj.authors,
'publish_date': article_obj.publish_date,
'top_image': article_obj.top_image,
'crawl_time': datetime.now(),
'source_domain': urlparse(source_url).netloc,
'text_length': len(article_obj.text),
'hash': hashlib.md5(article_obj.text.encode()).hexdigest()
}
articles.append(result)
print(f" [{i+1}/{max_articles}] {article_obj.title[:40]}...")
except Exception as e:
print(f" ⚠️ 跳过文章: {e}")
continue
# 礼貌延迟
time.sleep(0.5)
print(f"✅ 完成! 成功抓取 {len(articles)} 篇文章")
return articles
except Exception as e:
print(f"❌ 爬取失败: {e}")
return []
def save_articles(self, articles, output_file):
"""
保存文章到JSON
参数:
articles: 文章列表
output_file: 输出文件路径
"""
output_path = Path(output_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
# 转换datetime为字符串
for article in articles:
if article.get('publish_date'):
article['publish_date'] = article['publish_date'].isoformat()
if article.get('crawl_time'):
article['crawl_time'] = article['crawl_time'].isoformat()
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(articles, f, ensure_ascii=False, indent=2)
print(f"💾 已保存到: {output_file}")
# 使用示例
crawler = UniversalNewsCrawler(language='zh')
# 示例1: 爬取单篇文章
article_url = 'https://www.thepaper.cn/newsDetail_forward_12345678'
article = crawler.crawl_article(article_url)
# 示例2: 爬取新闻源
# source_url = 'https://www.thepaper.cn'
# articles = crawler.crawl_source(source_url, max_articles=20)
# crawler.save_articles(articles, 'data/news/thepaper_articles.json')
RSS订阅爬虫(高效获取更新)
python
import feedparser
from datetime import datetime, timedelta
import time
class RSSNewsCrawler:
"""
RSS订阅爬虫
优势:
- 高效(只获取更新)
- 结构化数据(标题、链接、摘要、时间)
- 低负载(服务器友好)
- 实时性好
劣势:
- 依赖网站提供RSS
- 可能缺少完整正文
"""
def __init__(self):
self.feeds = {} # 已订阅的feeds
def add_feed(self, name, rss_url):
"""
添加RSS订阅
参数:
name: 订阅名称
rss_url: RSS URL
"""
self.feeds[name] = {
'url': rss_url,
'last_check': None,
'entries_count': 0
}
print(f"✅ 添加订阅: {name} ({rss_url})")
def fetch_feed(self, name, since=None):
"""
获取RSS更新
参数:
name: 订阅名称
since: 仅获取此时间之后的条目
返回:
条目列表
"""
if name not in self.feeds:
print(f"❌ 未找到订阅: {name}")
return []
feed_info = self.feeds[name]
print(f"\n📡 获取RSS更新: {name}")
try:
# 解析RSS
feed = feedparser.parse(feed_info['url'])
if feed.bozo:
print(f"⚠️ RSS格式异常: {feed.bozo_exception}")
# 提取条目
entries = []
for entry in feed.entries:
# 解析发布时间
publish_date = None
if hasattr(entry, 'published_parsed'):
publish_date = datetime(*entry.published_parsed[:6])
elif hasattr(entry, 'updated_parsed'):
publish_date = datetime(*entry.updated_parsed[:6])
# 过滤时间
if since and publish_date and publish_date < since:
continue
# 提取数据
entry_data = {
'title': entry.get('title', ''),
'link': entry.get('link', ''),
'summary': entry.get('summary', ''),
'content': entry.get('content', [{}])[0].get('value', '') if hasattr(entry, 'content') else '',
'author': entry.get('author', ''),
'publish_date': publish_date,
'tags': [tag.get('term', '') for tag in entry.get('tags', [])],
'feed_name': name,
'fetch_time': datetime.now()
}
entries.append(entry_data)
# 更新统计
feed_info['last_check'] = datetime.now()
feed_info['entries_count'] += len(entries)
print(f" ✅ 获取 {len(entries)} 条新条目")
return entries
except Exception as e:
print(f"❌ 获取失败: {e}")
return []
def fetch_all(self, since_hours=24):
"""
获取所有订阅的更新
参数:
since_hours: 获取最近N小时的更新
返回:
所有条目列表
"""
since = datetime.now() - timedelta(hours=since_hours)
all_entries = []
for name in self.feeds:
entries = self.fetch_feed(name, since=since)
all_entries.extend(entries)
time.sleep(1) # 礼貌延迟
print(f"\n📊 总计获取 {len(all_entries)} 条条目")
return all_entries
def extract_full_content(self, entries, crawler):
"""
提取完整正文(RSS通常只有摘要)
参数:
entries: RSS条目列表
crawler: UniversalNewsCrawler实例
返回:
包含完整正文的条目列表
"""
print(f"\n📄 提取完整正文...")
for i, entry in enumerate(entries):
try:
# 使用通用爬虫获取完整内容
article = crawler.crawl_article(entry['link'])
if article and article.get('text'):
entry['full_text'] = article['text']
entry['text_length'] = len(article['text'])
entry['images'] = article.get('images', [])
entry['top_image'] = article.get('top_image')
print(f" [{i+1}/{len(entries)}] {entry['title'][:40]}...")
time.sleep(0.5)
except Exception as e:
print(f" ⚠️ 提取失败: {e}")
continue
return entries
# 使用示例
rss_crawler = RSSNewsCrawler()
# 添加订阅
rss_crawler.add_feed('新浪新闻', 'http://news.sina.com.cn/rss/news.xml')
rss_crawler.add_feed('网易新闻', 'http://news.163.com/rss/news.xml')
rss_crawler.add_feed('BBC中文', 'https://feeds.bbci.co.uk/zhongwen/simp/rss.xml')
# 获取最近24小时的更新
entries = rss_crawler.fetch_all(since_hours=24)
# 提取完整正文
news_crawler = UniversalNewsCrawler()
full_entries = rss_crawler.extract_full_content(entries[:5], news_crawler) # 示例只提取前5条
🧹 文本清洗:从脏数据到干净语料
HTML标签与噪声移除
python
import re
from bs4 import BeautifulSoup
import html
class TextCleaner:
"""
文本清洗器
功能:
1. 移除HTML标签
2. 去除特殊字符
3. 规范化空白
4. 繁简转换
5. 去除URL/邮箱
6. 去除广告文案
"""
def __init__(self):
# 广告关键词
self.ad_keywords = [
'广告', '推广', '赞助', '合作',
'点击进入', '立即购买', '限时优惠',
'扫码关注', '微信公众号', '加入我们'
]
# 常见噪声模式
self.noise_patterns = [
r'\[.*?\]', # [编辑:xxx]
r'【.*?】', # 【来源:xxx】
r'\(.*?记者.*?\)', # (本报记者xxx)
r'责任编辑[::].*', # 责任编辑:xxx
r'原标题[::].*', # 原标题:xxx
]
def clean(self, text, remove_html=True, remove_urls=True,
remove_emails=True, remove_ads=True, simplify=False):
"""
清洗文本
参数:
text: 原始文本
remove_html: 是否移除HTML
remove_urls: 是否移除URL
remove_emails: 是否移除邮箱
remove_ads: 是否移除广告文案
simplify: 是否繁简转换(繁→简)
返回:
清洗后的文本
"""
if not text:
return ""
original_length = len(text)
# 1. HTML解码
text = html.unescape(text)
# 2. 移除HTML标签
if remove_html:
text = self._remove_html(text)
# 3. 移除URL
if remove_urls:
text = self._remove_urls(text)
# 4. 移除邮箱
if remove_emails:
text = self._remove_emails(text)
# 5. 移除常见噪声
text = self._remove_noise(text)
# 6. 移除广告
if remove_ads:
text = self._remove_ads(text)
# 7. 规范化空白
text = self._normalize_whitespace(text)
# 8. 繁简转换
if simplify:
text = self._traditional_to_simplified(text)
# 9. 移除多余标点
text = self._normalize_punctuation(text)
cleaned_length = len(text)
if original_length > 0:
reduction = (1 - cleaned_length / original_length) * 100
print(f"🧹 清洗完成: {original_length} → {cleaned_length} 字符 (减少 {reduction:.1f}%)")
return text
def _remove_html(self, text):
"""移除HTML标签"""
# 使用BeautifulSoup
soup = BeautifulSoup(text, 'html.parser')
# 移除script和style标签
for tag in soup(['script', 'style', 'iframe', 'noscript']):
tag.decompose()
# 获取纯文本
text = soup.get_text()
return text
def _remove_urls(self, text):
"""移除URL"""
# HTTP/HTTPS URL
text = re.sub(r'https?://\S+', '', text)
# www URL
text = re.sub(r'www\.\S+', '', text)
return text
def _remove_emails(self, text):
"""移除邮箱地址"""
text = re.sub(r'\S+@\S+\.\S+', '', text)
return text
def _remove_noise(self, text):
"""移除常见噪声"""
for pattern in self.noise_patterns:
text = re.sub(pattern, '', text)
return text
def _remove_ads(self, text):
"""移除广告文案"""
lines = text.split('\n')
filtered_lines = []
for line in lines:
# 如果行中包含广告关键词,跳过
if any(keyword in line for keyword in self.ad_keywords):
continue
filtered_lines.append(line)
return '\n'.join(filtered_lines)
def _normalize_whitespace(self, text):
"""规范化空白字符"""
# 替换所有空白为单个空格
text = re.sub(r'\s+', ' ', text)
# 移除行首行尾空白
text = text.strip()
# 中文标点后不需要空格
text = re.sub(r'([,。!?;:、])\s+', r'\1', text)
return text
def _traditional_to_simplified(self, text):
"""繁体转简体"""
try:
from opencc import OpenCC
cc = OpenCC('t2s') # 繁体到简体
return cc.convert(text)
except ImportError:
print("⚠️ 需要安装opencc-python-reimplemented: pip install opencc-python-reimplemented")
return text
def _normalize_punctuation(self, text):
"""规范化标点符号"""
# 移除连续标点
text = re.sub(r'([。!?]){2,}', r'\1', text)
text = re.sub(r'([,、;:]){2,}', r'\1', text)
# 统一引号
text = text.replace('"', '"').replace('"', '"')
text = text.replace(''', "'").replace(''', "'")
return text
def batch_clean(self, texts, **kwargs):
"""批量清洗"""
return [self.clean(text, **kwargs) for text in texts]
# 使用示例
cleaner = TextCleaner()
# 示例文本
dirty_text = """
<div class="article">
<h1>这是标题</h1>
<p>这是正文内容,包含一些<strong>HTML标签</strong>。</p>
<p>访问我们的网站:https://example.com 或发邮件到 contact@example.com</p>
<p>【来源:新华网】【编辑:张三】</p>
<p>点击进入 查看更多 广告推广内容</p>
<script>alert('广告');</script>
</div>
"""
clean_text = cleaner.clean(
dirty_text,
remove_html=True,
remove_urls=True,
remove_emails=True,
remove_ads=True
)
print(f"\n清洗后文本:")
print(clean_text)
中文分词与词性标注
python
import jieba
import jieba.posseg as pseg
class ChineseTokenizer:
"""
中文分词器
功能:
1. 分词
2. 词性标注
3. 自定义词典
4. 停用词过滤
5. 关键词提取
"""
def __init__(self, custom_dict=None, stopwords_file=None):
"""
初始化分词器
参数:
custom_dict: 自定义词典文件路径
stopwords_file: 停用词文件路径
"""
# 加载自定义词典
if custom_dict:
jieba.load_userdict(custom_dict)
print(f"✅ 加载自定义词典: {custom_dict}")
# 加载停用词
self.stopwords = set()
if stopwords_file:
with open(stopwords_file, 'r', encoding='utf-8') as f:
self.stopwords = set(line.strip() for line in f)
print(f"✅ 加载停用词: {len(self.stopwords)} 个")
else:
# 默认停用词
self.stopwords = self._get_default_stopwords()
def _get_default_stopwords(self):
"""获取默认停用词"""
return {
'的', '了', '在', '是', '我', '有', '和', '就',
'不', '人', '都', '一', '一个', '上', '也', '很',
'到', '说', '要', '去', '你', '会', '着', '没有',
'看', '好', '自己', '这'
}
def tokenize(self, text, mode='default'):
"""
分词
参数:
text: 文本
mode: 分词模式
- 'default': 精确模式
- 'search': 搜索引擎模式
- 'all': 全模式
返回:
词列表
"""
if mode == 'search':
words = jieba.cut_for_search(text)
elif mode == 'all':
words = jieba.cut(text, cut_all=True)
else:
words = jieba.cut(text)
return list(words)
def tokenize_with_pos(self, text):
"""
分词+词性标注
返回:
[(词, 词性), ...]
"""
return [(word, flag) for word, flag in pseg.cut(text)]
def remove_stopwords(self, words):
"""
移除停用词
参数:
words: 词列表
返回:
过滤后的词列表
"""
return [w for w in words if w not in self.stopwords and len(w) > 1]
def extract_keywords(self, text, topK=20, withWeight=False, allowPOS=()):
"""
提取关键词(TF-IDF算法)
参数:
text: 文本
topK: 返回前K个关键词
withWeight: 是否返回权重
allowPOS: 允许的词性(空表示全部)
返回:
关键词列表或[(词, 权重), ...]
"""
import jieba.analyse
keywords = jieba.analyse.extract_tags(
text,
topK=topK,
withWeight=withWeight,
allowPOS=allowPOS
)
return keywords
def extract_textrank_keywords(self, text, topK=20, withWeight=False):
"""
提取关键词(TextRank算法)
TextRank基于图的排序算法,不需要预先训练
"""
import jieba.analyse
keywords = jieba.analyse.textrank(
text,
topK=topK,
withWeight=withWeight
)
return keywords
# 使用示例
tokenizer = ChineseTokenizer()
# 示例文本
text = "中国人工智能技术发展迅速,深度学习算法在计算机视觉领域取得了重大突破。"
# 分词
words = tokenizer.tokenize(text)
print(f"\n分词结果: {words}")
# 词性标注
words_pos = tokenizer.tokenize_with_pos(text)
print(f"\n词性标注: {words_pos}")
# 移除停用词
filtered_words = tokenizer.remove_stopwords(words)
print(f"\n移除停用词: {filtered_words}")
# 提取关键词(TF-IDF)
keywords_tfidf = tokenizer.extract_keywords(text, topK=5, withWeight=True)
print(f"\nTF-IDF关键词: {keywords_tfidf}")
# 提取关键词(TextRank)
keywords_textrank = tokenizer.extract_textrank_keywords(text, topK=5, withWeight=True)
print(f"\nTextRank关键词: {keywords_textrank}")
🔍 文本去重:SimHash与MinHash
SimHash算法(局部敏感哈希)
python
import hashlib
from collections import defaultdict
class SimHashDeduplicator:
"""
SimHash文本去重器
原理:
将文本转换为64位SimHash指纹
通过汉明距离判断相似度
优势:
- 速度快(O(1)查找)
- 内存占用小
- 适合大规模数据
劣势:
- 可能有误判
- 不适合短文本
"""
def __init__(self, hash_bits=64, threshold=3):
"""
初始化去重器
参数:
hash_bits: 哈希位数(64/128)
threshold: 汉明距离阈值(<=threshold认为重复)
"""
self.hash_bits = hash_bits
self.threshold = threshold
# 已见过的指纹
self.fingerprints = {}
def simhash(self, text):
"""
计算SimHash指纹
算法步骤:
1. 对文本分词
2. 计算每个词的哈希值
3. 对每个哈希值,将二进制位为1的位+1,为0的位-1
4. 累加所有词的权重向量
5. >0的位设为1,<=0的位设为0
参数:
text: 文本
返回:
SimHash指纹(整数)
"""
# 分词
words = jieba.cut(text)
# 初始化权重向量
v = [0] * self.hash_bits
for word in words:
if not word.strip():
continue
# 计算词的哈希值
word_hash = int(hashlib.md5(word.encode()).hexdigest(), 16)
# 更新权重向量
for i in range(self.hash_bits):
bitmask = 1 << i
if word_hash & bitmask:
v[i] += 1 # 对应位为1,权重+1
else:
v[i] -= 1 # 对应位为0,权重-1
# 生成指纹
fingerprint = 0
for i in range(self.hash_bits):
if v[i] > 0:
fingerprint |= 1 << i
return fingerprint
def hamming_distance(self, hash1, hash2):
"""
计算汉明距离
汉明距离 = 二进制表示中不同位的个数
示例:
hash1 = 0b1010
hash2 = 0b1001
距离 = 2 (第2位和第4位不同)
"""
x = hash1 ^ hash2 # 异或,相同位为0,不同位为1
# 计算1的个数
distance = 0
while x:
distance += 1
x &= x - 1 # 移除最右边的1
return distance
def is_duplicate(self, text, text_id=None):
"""
检查文本是否重复
参数:
text: 文本
text_id: 文本ID(用于记录)
返回:
(is_dup, similar_id, distance)
"""
# 计算指纹
fingerprint = self.simhash(text)
# 检查是否重复
for stored_id, stored_fp in self.fingerprints.items():
distance = self.hamming_distance(fingerprint, stored_fp)
if distance <= self.threshold:
return True, stored_id, distance
# 不重复,记录指纹
if text_id:
self.fingerprints[text_id] = fingerprint
return False, None, None
def deduplicate(self, texts):
"""
批量去重
参数:
texts: [(text_id, text), ...]
返回:
{
'unique': [(text_id, text), ...],
'duplicates': [(text_id, text, similar_to), ...],
'stats': {...}
}
"""
unique = []
duplicates = []
for text_id, text in texts:
is_dup, similar_id, distance = self.is_duplicate(text, text_id)
if is_dup:
duplicates.append((text_id, text, similar_id, distance))
print(f" 🔁 重复: {text_id} ≈ {similar_id} (距离={distance})")
else:
unique.append((text_id, text))
stats = {
'total': len(texts),
'unique': len(unique),
'duplicates': len(duplicates),
'dedup_rate': len(duplicates) / len(texts) * 100 if texts else 0
}
print(f"\n📊 去重统计:")
print(f" 总数: {stats['total']}")
print(f" 唯一: {stats['unique']}")
print(f" 重复: {stats['duplicates']} ({stats['dedup_rate']:.1f}%)")
return {
'unique': unique,
'duplicates': duplicates,
'stats': stats
}
# 使用示例
deduplicator = SimHashDeduplicator(hash_bits=64, threshold=3)
# 测试文本
test_texts = [
('doc1', '中国人工智能技术发展迅速,深度学习算法在计算机视觉领域取得重大突破。'),
('doc2', '中国AI技术发展迅速,深度学习在计算机视觉领域取得突破。'), #似
('doc3', '今天天气不错,适合出门散步。'), # 完全不同
('doc4', '中国人工智能技术发展迅速,深度学习算法在计算机视觉领域取得重大突破。') # 与doc1完全相同
]
result = deduplicator.deduplicate(test_texts)
print(f"\n唯一文档:")
for text_id, text in result['unique'][:3]:
print(f" {text_id}: {text[:50]}...")
✂️ 中文分句:处理歧义与嵌套
python
import re
class ChineseSentenceSplitter:
"""
中文分句器
挑战:
1. 句号歧义(如"3.14"中的点号)
2. 省略号(。。。)
3. 引号嵌套
4. 对话场景
解决方案:
基于规则+正则表达式
"""
def __init__(self):
# 句末标点
self.sentence_delimiters = set('。!?!?;;...')
# 引号配对
self.quote_pairs = {
'"': '"',
''': ''',
'「': '」',
'『': '』',
'(': ')',
'(': ')'
}
def split(self, text, keep_empty=False):
"""
分句
参数:
text: 文本
keep_empty: 是否保留空句子
返回:
句子列表
"""
if not text:
return []
sentences = []
current_sentence = ""
quote_stack = [] # 引号栈
i = 0
while i < len(text):
char = text[i]
current_sentence += char
# 处理引号
if char in self.quote_pairs:
# 左引号,入栈
quote_stack.append(char)
elif char in self.quote_pairs.values():
# 右引号,出栈
if quote_stack:
left_quote = quote_stack[-1]
if self.quote_pairs[left_quote] == char:
quote_stack.pop()
# 判断是否句末
if char in self.sentence_delimiters:
# 如果在引号内,不切分
if quote_stack:
i += 1
continue
# 处理省略号(连续的句号)
if char == '。':
# 向后看,如果还有句号,继续
j = i + 1
while j < len(text) and text[j] == '。':
current_sentence += text[j]
j += 1
i += 1
# 向后看,如果是引号,包含进来
if i + 1 < len(text) and text[i + 1] in self.quote_pairs.values():
current_sentence += text[i + 1]
i += 1
# 添加句子
if current_sentence.strip() or keep_empty:
sentences.append(current_sentence.strip())
current_sentence = ""
i += 1
# 处理剩余部分
if current_sentence.strip() or keep_empty:
sentences.append(current_sentence.strip())
return sentences
def split_with_regex(self, text):
"""
基于正则表达式的简单分句
适用于简单场景
"""
# 分句正则
pattern = r'([^。!?!?;;]+[。!?!?;;]+)'
sentences = re.findall(pattern, text)
# 处理剩余部分(没有标点结尾的)
last_match = re.search(pattern, text)
if last_match:
remaining = text[last_match.end():]
if remaining.strip():
sentences.append(remaining.strip())
elif text.strip():
sentences.append(text.strip())
return [s.strip() for s in sentences if s.strip()]
# 使用示例
splitter = ChineseSentenceSplitter()
# 测试文本
test_text = """
他说:"今天天气真好!"我点了点头。
圆周率π≈3.14159。这是一个重要的数学常数。
她问:"你吃饭了吗??"我说:"还没有。。。"
(注:以上内容仅供参考)
"""
sentences = splitter.split(test_text)
print(f"\n分句结果({len(sentences)}句):")
for i, sent in enumerate(sentences, 1):
print(f"{i}. {sent}")
📊 TF-IDF特征提取
python
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import pandas as pd
class TFIDFFeatureExtractor:
"""
TF-IDF特征提取器
TF-IDF = TF(词频) × IDF(逆文档频率)
TF = 词在文档中出现次数 / 文档总词数
IDF = log(文档总数 / 包含该词的文档数)
作用:
- 识别文档的重要词汇
- 降低常见词(如"的")的权重
- 提升特征词的权重
"""
def __init__(self, max_features=5000, min_df=2, max_df=0.8,
ngram_range=(1, 2), use_idf=True):
"""
初始化提取器
参数:
max_features: 最大特征数
min_df: 最小文档频率(词至少出现在N个文档中)
max_df: 最大文档频率(词最多出现在X%的文档中)
ngram_range: N-gram范围((1,1)=unigram, (1,2)=unigram+bigram)
use_idf: 是否使用IDF
"""
self.vectorizer = TfidfVectorizer(
max_features=max_features,
min_df=min_df,
max_df=max_df,
ngram_range=ngram_range,
use_idf=use_idf,
smooth_idf=True,
sublinear_tf=True, # 使用log(TF)而非TF
token_pattern=r'\S+', # 自定义token模式(因为中文已分词)
)
self.feature_names = None
self.tfidf_matrix = None
def fit_transform(self, documents):
"""
训练并转换
参数:
documents: 文档列表(已分词,用空格分隔)
['词1 词2 词3', '词4 词5 词6', ...]
返回:
TF-IDF矩阵(稀疏矩阵)
"""
self.tfidf_matrix = self.vectorizer.fit_transform(documents)
self.feature_names = self.vectorizer.get_feature_names_out()
print(f"✅ TF-IDF特征提取完成:")
print(f" 文档数: {self.tfidf_matrix.shape[0]}")
print(f" 特征数: {self.tfidf_matrix.shape[1]}")
print(f" 稀疏度: {(1.0 - self.tfidf_matrix.nnz / (self.tfidf_matrix.shape[0] * self.tfidf_matrix.shape[1])) * 100:.2f}%")
return self.tfidf_matrix
def transform(self, documents):
"""
转换新文档
参数:
documents: 文档列表
返回:
TF-IDF矩阵
"""
return self.vectorizer.transform(documents)
def get_top_features(self, doc_index, top_n=10):
"""
获取文档的TopN特征
参数:
doc_index: 文档索引
top_n: 返回前N个特征
返回:
[(特征名, TF-IDF值), ...]
"""
if self.tfidf_matrix is None:
print("❌ 请先调用fit_transform")
return []
# 获取该文档的TF-IDF向量
doc_vector = self.tfidf_matrix[doc_index].toarray()[0]
# 排序
top_indices = doc_vector.argsort()[-top_n:][::-1]
top_features = [
(self.feature_names[i], doc_vector[i])
for i in top_indices
if doc_vector[i] > 0
]
return top_features
def get_similar_documents(self, doc_index, top_n=5):
"""
获取相似文档
参数:
doc_index: 文档索引
top_n: 返回前N个相似文档
返回:
[(文档索引, 相似度), ...]
"""
if self.tfidf_matrix is None:
print("❌ 请先调用fit_transform")
return []
# 计算余弦相似度
doc_vector = self.tfidf_matrix[doc_index:doc_index+1]
similarities = cosine_similarity(doc_vector, self.tfidf_matrix)[0]
# 排序(排除自己)
similar_indices = similarities.argsort()[::-1][1:top_n+1]
similar_docs = [
(idx, similarities[idx])
for idx in similar_indices
]
return similar_docs
def export_to_dataframe(self):
"""
导出为DataFrame
返回:
DataFrame(行=文档,列=特征,值=TF-IDF)
"""
if self.tfidf_matrix is None:
return None
df = pd.DataFrame(
self.tfidf_matrix.toarray(),
columns=self.feature_names
)
return df
# 使用示例
# 准备数据(已分词)
documents = [
'人工智能 技术 发展 迅速 深度学习 算法',
'机器学习 算法 应用 广泛 数据挖掘',
'自然语言 处理 NLP 技术 进步',
'计算机 视觉 图像识别 深度学习',
'人工智能 改变 世界 科技 创新'
]
# 提取TF-IDF特征
extractor = TFIDFFeatureExtractor(max_features=100, ngram_range=(1, 2))
tfidf_matrix = extractor.fit_transform(documents)
# 查看文档0的TopN特征
top_features = extractor.get_top_features(0, top_n=5)
print(f"\n文档0的Top5特征:")
for feature, score in top_features:
print(f" {feature}: {score:.4f}")
# 查找相似文档
similar_docs = extractor.get_similar_documents(0, top_n=3)
print(f"\n与文档0最相似的文档:")
for doc_idx, similarity in similar_docs:
print(f" 文档{doc_idx}: {similarity:.4f}")
# 导出DataFrame
df_tfidf = extractor.export_to_dataframe()
print(f"\nTF-IDF DataFrame:")
print(df_tfidf.head())
🎯 文本聚类:发现主题
K-Means聚类
python
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
class TextClusterer:
"""
文本聚类器
支持多种聚类算法:
- K-Means
- DBSCAN
- 层次聚类
"""
def __init__(self, n_clusters=5, algorithm='kmeans'):
"""
初始化聚类器
参数:
n_clusters: 聚类数(仅K-Means需要)
algorithm: 'kmeans' / 'dbscan' / 'hierarchical'
"""
self.n_clusters = n_clusters
self.algorithm = algorithm
self.model = None
self.labels = None
def fit(self, tfidf_matrix):
"""
训练聚类模型
参数:
tfidf_matrix: TF-IDF矩阵(稀疏矩阵)
返回:
聚类标签
"""
if self.algorithm == 'kmeans':
self.model = KMeans(
n_clusters=self.n_clusters,
init='k-means++',
max_iter=300,
n_init=10,
random_state=42
)
self.labels = self.model.fit_predict(tfidf_matrix)
print(f"✅ K-Means聚类完成:")
print(f" 聚类数: {self.n_clusters}")
print(f" 惯性(Inertia): {self.model.inertia_:.2f}")
elif self.algorithm == 'dbscan':
from sklearn.cluster import DBSCAN
self.model = DBSCAN(
eps=0.5,
min_samples=2,
metric='cosine'
)
self.labels = self.model.fit_predict(tfidf_matrix)
n_clusters = len(set(self.labels)) - (1 if -1 in self.labels else 0)
n_noise = list(self.labels).count(-1)
print(f"✅ DBSCAN聚类完成:")
print(f" 聚类数: {n_clusters}")
print(f" 噪声点: {n_noise}")
elif self.algorithm == 'hierarchical':
from sklearn.cluster import AgglomerativeClustering
self.model = AgglomerativeClustering(
n_clusters=self.n_clusters,
linkage='ward'
)
# 层次聚类需要密集矩阵
self.labels = self.model.fit_predict(tfidf_matrix.toarray())
print(f"✅ 层次聚类完成:")
print(f" 聚类数: {self.n_clusters}")
return self.labels
def get_cluster_sizes(self):
"""获取每个簇的大小"""
if self.labels is None:
return {}
from collections import Counter
return dict(Counter(self.labels))
def get_top_terms_per_cluster(self, tfidf_matrix, feature_names, top_n=10):
"""
获取每个簇的Top特征词
参数:
tfidf_matrix: TF-IDF矩阵
feature_names: 特征名列表
top_n: 每个簇返回前N个词
返回:
{cluster_id: [(词, 权重), ...], ...}
"""
if self.labels is None:
return {}
cluster_terms = {}
for cluster_id in set(self.labels):
if cluster_id == -1: # continue
# 获取该簇的所有文档
cluster_docs = tfidf_matrix[selfF-IDF
avg_tfidf = cluster_docs.mean(axis=0).A1
# 排序
top_indices = avg_tfidf.argsort()[-top_n:][::-1]
cluster_terms[cluster_id] = [
(feature_names[i], avg_tfidf[i])
for i in top_indices
]
return cluster_terms
def visualize_clusters(self, tfidf_matrix, output_file='clusters.png'):
"""
可视化聚类结果(降维到2D)
参数:
tfidf_matrix: TF-IDF矩阵
output_file: 输出图片路径
"""
if self.labels is None:
print("❌ 请先调用fit方法")
return
# PCA降维到2D
pca = PCA(n_components=2, random_state=42)
coords = pca.fit_transform(tfidf_matrix.toarray())
# 绘图
plt.figure(figsize=(12, 8))
for cluster_id in set(self.labels):
cluster_points = coords[self.labels == cluster_id]
if cluster_id == -1:
# 噪声点用灰色
plt.scatter(cluster_points[:, 0], cluster_points[:, 1],
c='gray', marker='x', label='Noise', alpha=0.5)
else:
plt.scatter(cluster_points[:, 0], cluster_points[:, 1],
label=f'Cluster {cluster_id}', alpha=0.6)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title(f'{self.algorithm.upper()} Clustering Visualization')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(output_file, dpi=150)
print(f"📊 聚类可视化已保存: {output_file}")
# 使用示例
# 继续使用之前的TF-IDF矩阵
clusterer = TextClusterer(n_clusters=3, algorithm='kmeans')
labels = clusterer.fit(tfidf_matrix)
# 查看簇大小
cluster_sizes = clusterer.get_cluster_sizes()
print(f"\n簇大小: {cluster_sizes}")
# 查看每个簇的Top词
cluster_terms = clusterer.get_top_terms_per_cluster(
tfidf_matrix,
extractor.feature_names,
top_n=5
)
print(f"\n每个簇的Top5特征词:")
for cluster_id, terms in cluster_terms.items():
print(f"\n簇 {cluster_id}:")
for term, weight in terms:
print(f" {term}: {weight:.4f}")
# 可视化
# clusterer.visualize_clusters(tfidf_matrix, 'data/clusters.png')
🏗️ 完整舆情语料处理流程
python
class SentimentCorpusPipeline:
"""
舆情语料完整处理流程
流程:
爬取 → 清洗 → 分词 → 去重 → 分句 → TF-IDF → 聚类 → 导出
"""
def __init__(self, output_dir='data/sentiment_corpus'):
"""
初始化流程
参数:
output_dir: 输出目录
"""
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
# 初始化各组件
self.crawler = UniversalNewsCrawler()
self.cleaner = TextCleaner()
self.tokenizer = ChineseTokenizer()
self.deduplicator = SimHashDeduplicator()
self.splitter = ChineseSentenceSplitter()
self.tfidf_extractor = TFIDFFeatureExtractor()
# 数据存储
self.raw_articles = []
self.cleaned_texts = []
self.tokenized_docs = []
self.unique_docs = []
self.sentences = []
self.tfidf_matrix = None
def run(self, news_urls, max_articles_per_source=50):
"""
执行完整流程
参数:
news_urls: 新闻源URL列表
max_articles_per_source: 每个源最多爬取文章数
返回:
处理结果统计
"""
print("\n" + "="*80)
print("🚀 舆情语料处理流程启动")
print("="*80)
# 步骤1: 爬取
print("\n【步骤1】爬取新闻...")
for url in news_urls:
articles = self.crawler.crawl_source(url, max_articles=max_articles_per_source)
self.raw_articles.extend(articles)
time.sleep(2) # 礼貌延迟
print(f"✅ 爬取完成: {len(self.raw_articles)} 篇文章")
# 步骤2: 清洗
print("\n【步骤2】文本清洗...")
for article in self.raw_articles:
if article.get('text'):
cleaned = self.cleaner.clean(
article['text'],
remove_html=True,
remove_urls=True,
remove_ads=True
)
if cleaned:
self.cleaned_texts.append({
'id': article.get('url', str(len(self.cleaned_texts))),
'title': article.get('title', ''),
'text': cleaned,
'source': article.get('source_domain', ''),
'publish_date': article.get('publish_date')
})
print(f"✅ 清洗完成: {len(self.cleaned_texts)} 篇有效文章")
# 步骤3: 分词
print("\n【步骤3】中文分词...")
for doc in self.cleaned_texts:
words = self.tokenizer.tokenize(doc['text'])
filtered_words = self.tokenizer.remove_stopwords(words)
self.tokenized_docs.append({
**doc,
'words': filtered_words,
'word_str': ' '.join(filtered_words)
})
print(f"✅ 分词完成: 平均每篇 {np.mean([len(d['words']) for d in self.tokenized_docs]):.0f} 词")
# 步骤4: 去重
print("\n【步骤4】文本去重...")
texts_for_dedup = [(d['id'], d['text']) for d in self.tokenized_docs]
dedup_result = self.deduplicator.deduplicate(texts_for_dedup)
unique_ids = set(d[0] for d in dedup_result['unique'])
self.unique_docs = [d for d in self.tokenized_docs if d['id'] in unique_ids]
print(f"✅ 去重完成: {len(self.unique_docs)} 篇唯一文章")
# 步骤5: 分句
print("\n【步骤5】文本分句...")
for doc in self.unique_docs:
sents = self.splitter.split(doc['text'])
self.sentences.extend([
{
'doc_id': doc['id'],
'sentence': sent,
'title': doc['title']
}
for sent in sents
])
print(f"✅ 分句完成: {len(self.sentences)} 个句子")
# 步骤6: TF-IDF特征提取
print("\n【步骤6】TF-IDF特征提取...")
word_strs = [d['word_str'] for d in self.unique_docs]
self.tfidf_matrix = self.tfidf_extractor.fit_transform(word_strs)
# 步骤7: 聚类
print("\n【步骤7】文本聚类...")
clusterer = TextClusterer(n_clusters=min(5, len(self.unique_docs)), algorithm='kmeans')
labels = clusterer.fit(self.tfidf_matrix)
# 添加聚类标签
for i, doc in enumerate(self.unique_docs):
doc['cluster'] = int(labels[i])
# 获取簇的Top词
cluster_terms = clusterer.get_top_terms_per_cluster(
self.tfidf_matrix,
self.tfidf_extractor.feature_names,
top_n=10
)
print(f"\n簇主题词:")
for cluster_id, terms in cluster_terms.items():
top_words = ', '.join([t[0] for t in terms[:5]])
print(f" 簇{cluster_id}: {top_words}")
# 步骤8: 导出
print("\n【步骤8】导出结果...")
self._export_results(cluster_terms)
# 统计
stats = {
'raw_articles': len(self.raw_articles),
'cleaned_texts': len(self.cleaned_texts),
'unique_docs': len(self.unique_docs),
'sentences': len(self.sentences),
'avg_words_per_doc': np.mean([len(d['words']) for d in self.unique_docs]),
'clusters': len(set(labels))
}
print("\n" + "="*80)
print("✅ 处理完成!")
print("="*80)
print(f"\n📊 统计:")
for key, value in stats.items():
print(f" {key}: {value}")
return stats
def _export_results(self, cluster_terms):
"""导出结果"""
# 1. 导出文档
with open(self.output_dir / 'documents.json', 'w', encoding='utf-8') as f:
json.dump(self.unique_docs, f, ensure_ascii=False, indent=2, default=str)
# 2. 导出句子
with open(self.output_dir / 'sentences.json', 'w', encoding='utf-8') as f:
json.dump(self.sentences, f, ensure_ascii=False, indent=2)
# 3. 导出TF-IDF矩阵
df_tfidf = self.tfidf_extractor.export_to_dataframe()
df_tfidf.to_csv(self.output_dir / 'tfidf_matrix.csv', encoding='utf-8')
# 4. 导出聚类结果
with open(self.output_dir / 'cluster_terms.json', 'w', encoding='utf-8') as f:
json.dump(cluster_terms, f, ensure_ascii=False, indent=2, default=str)
print(f"💾 结果已导出到: {self.output_dir}")
# 使用示例
pipeline = SentimentCorpusPipeline(output_dir='data/sentiment_corpus')
# 运行流程
news_sources = [
'https://www.thepaper.cn',
# 'https://news.sina.com.cn',
# 'https://news.163.com'
]
# stats = pipeline.run(news_sources, max_articles_per_source=20)
💡 最佳实践总结
1. 新闻爬取策略
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 大型新闻网站 | RSS订阅 + newspaper3k | 高效、结构化、低负载 |
| 小型博客 | 通用爬虫 | 灵活、适应性强 |
| 反爬严格 | Selenium + 代理池 | 绕过检测 |
| 实时监控 | RSS + Webhook | 及时性好 |
2. 文本清洗要点
- ✅ 先粗后细(先去HTML,再去噪声)
- ✅ 保留标点(有助于分句)
- ✅ 繁简统一(避免重复)
- ✅ 人工抽查(验证清洗质量)
3. 去重算法选择
| 算法 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| SimHash | 长文本(>100字) | 快速、可扩展 | 短文本误判 |
| MinHash | 海量数据 | 内存友好 | 实现复杂 |
| Jaccard | 短文本 | 简单直观 | 速度慢 |
| 语义去重 | 高质量要求 | 准确 | 计算昂贵 |
4. TF-IDF优化技巧
- ✅ 调整min_df/max_df(过滤噪声词)
- ✅ 使用sublinear_tf(平滑高频词)
- ✅ 使用bigram(捕捉短语)
- ✅ 领域词典(提升专业词权重)
📚 本章总结
本章系统讲解了舆情语料项目的完整流程:
✅ 新闻爬取 : 通用爬虫、RSS订阅、反爬对抗
✅ 文本清洗 : HTML去除、噪声过滤、繁简转换
✅ 文本去重 : SimHash、MinHash、语义去重
✅ 中文分句 : 歧义处理、引号嵌套
✅ TF-IDF特征 : 改进方案、动态更新
✅ 文本聚类: K-Means、DBSCAN、主题发现
关键要点
- 端到端自动化是舆情系统的核心
- 数据质量比数量更重要
- 增量更新优于全量重爬
- 人工验证必不可少
希望这一章能帮你构建生产级舆情语料系统!
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?
评论区留言告诉我你的需求,我会优先安排实现(更新)哒~
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
✅ 免责声明
本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。
使用或者参考本项目即表示您已阅读并同意以下条款:
- 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
- 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
- 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
- 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
