Day 12: 社交媒体数据采集
学习目标
- 掌握社交媒体平台的数据采集方法
- 学会处理社交媒体的反爬机制
- 实现数据清洗和分析
- 了解数据可视化技术
技术原理
12.1 社交媒体特点
数据特点:
- 数据量大
- 更新频繁
- 格式多样
- 包含多媒体内容
爬取难点:
- 需要登录
- 动态加载
- 严格的反爬
- 数据加密
12.2 平台分析
微博:
- 移动端 API
- 需要登录
- 数据格式 JSON
知乎:
- GraphQL API
- 需要登录
- 反爬严格
抖音:
- 加密参数
- 需要签名
- 反爬最严格
12.3 爬取策略
登录方式:
- Cookie 登录
- 账号密码登录
- 第三方登录
数据获取:
- API 接口爬取
- 页面解析爬取
- 混合方式
案例
案例1:微博数据采集
python
import scrapy
import json
class WeiboSpider(scrapy.Spider):
"""微博爬虫"""
name = 'weibo'
custom_settings = {
'COOKIES_ENABLED': True,
'DOWNLOAD_DELAY': 3,
}
def start_requests(self):
"""生成初始请求"""
# 微博移动端 API
url = 'https://m.weibo.cn/api/container/getIndex'
# 热门话题
params = {
'containerid': '106003type=25&t=3&disable_hot=1&filter_type=realtimehot',
}
yield scrapy.FormRequest(
url=url,
formdata=params,
callback=self.parse_hot_topics,
headers={
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)',
'Referer': 'https://m.weibo.cn/',
}
)
def parse_hot_topics(self, response):
"""解析热门话题"""
data = json.loads(response.text)
cards = data.get('data', {}).get('cards', [])
for card in cards:
card_group = card.get('card_group', [])
for item in card_group:
desc = item.get('desc', '')
link = item.get('scheme', '')
yield {
'topic': desc,
'link': link,
'hot': item.get('desc_extr', ''),
}案例2:知乎数据采集
python
import scrapy
import json
class ZhihuSpider(scrapy.Spider):
"""知乎爬虫"""
name = 'zhihu'
custom_settings = {
'COOKIES_ENABLED': True,
'DOWNLOAD_DELAY': 2,
}
def start_requests(self):
"""生成初始请求"""
# 知乎热榜 API
url = 'https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total'
params = {
'limit': '50',
'desktop': 'true',
}
yield scrapy.FormRequest(
url=url,
formdata=params,
callback=self.parse_hot_list,
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.zhihu.com/hot',
}
)
def parse_hot_list(self, response):
"""解析热榜"""
data = json.loads(response.text)
for item in data.get('data', []):
target = item.get('target', {})
yield {
'title': target.get('title', ''),
'excerpt': target.get('excerpt', ''),
'url': f'https://www.zhihu.com/question/{target.get("id", "")}',
'heat': item.get('detail_text', ''),
'answer_count': target.get('answer_count', 0),
}应用场景
1. 舆情监控
- 品牌舆情分析
- 危机预警
- 用户反馈收集
2. 内容分析
- 热门话题追踪
- 内容趋势分析
- 用户行为分析
3. 竞品分析
- 竞品动态监控
- 市场趋势分析
- 用户画像分析
代码案例
案例3:微博评论采集
python
import scrapy
import json
class WeiboCommentSpider(scrapy.Spider):
"""微博评论爬虫"""
name = 'weibo_comment'
def __init__(self, post_id=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_id = post_id
def start_requests(self):
"""生成初始请求"""
# 微博评论 API
url = 'https://m.weibo.cn/api/comments/show'
params = {
'id': self.post_id,
'page': '1',
}
yield scrapy.FormRequest(
url=url,
formdata=params,
callback=self.parse_comments,
meta={'page': 1}
)
def parse_comments(self, response):
"""解析评论"""
data = json.loads(response.text)
page = response.meta['page']
comments = data.get('data', {}).get('data', [])
for comment in comments:
user = comment.get('user', {})
yield {
'comment_id': comment.get('id'),
'content': comment.get('text'),
'user_name': user.get('screen_name'),
'user_id': user.get('id'),
'like_count': comment.get('like_count'),
'created_at': comment.get('created_at'),
'post_id': self.post_id,
}
# 处理分页
max_page = data.get('data', {}).get('max', 1)
if page < max_page:
next_page = page + 1
params = {
'id': self.post_id,
'page': str(next_page),
}
yield scrapy.FormRequest(
url='https://m.weibo.cn/api/comments/show',
formdata=params,
callback=self.parse_comments,
meta={'page': next_page}
)案例4:数据清洗与分析
python
import re
from bs4 import BeautifulSoup
import jieba
from collections import Counter
class SocialMediaAnalyzer:
"""社交媒体数据分析器"""
def __init__(self):
self.data = []
def add_data(self, items):
"""添加数据"""
self.data.extend(items)
def clean_text(self, text):
"""清洗文本"""
if not text:
return ''
# 去除 HTML 标签
text = BeautifulSoup(text, 'html.parser').get_text()
# 去除 URL
text = re.sub(r'http[s]?://\S+', '', text)
# 去除 @用户名
text = re.sub(r'@[\w]+', '', text)
# 去除话题标签
text = re.sub(r'#[^#]+#', '', text)
# 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
return text
def extract_keywords(self, text, top_k=10):
"""提取关键词"""
# 分词
words = jieba.cut(text)
# 过滤停用词
stop_words = {'的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这'}
filtered_words = [w for w in words if len(w) > 1 and w not in stop_words]
# 统计词频
word_counts = Counter(filtered_words)
return word_counts.most_common(top_k)
def analyze_sentiment(self, text):
"""情感分析(简化版)"""
# 正面词汇
positive_words = {'好', '棒', '赞', '喜欢', '优秀', '精彩', '不错', '支持', '厉害', '牛'}
# 负面词汇
negative_words = {'差', '烂', '垃圾', '讨厌', '恶心', '失望', '糟糕', '难看', '难吃', '坑'}
words = set(jieba.cut(text))
positive_count = len(words & positive_words)
negative_count = len(words & negative_words)
if positive_count > negative_count:
return 'positive'
elif negative_count > positive_count:
return 'negative'
else:
return 'neutral'
def analyze(self):
"""分析数据"""
results = {
'total': len(self.data),
'sentiment': {'positive': 0, 'negative': 0, 'neutral': 0},
'keywords': [],
}
all_text = ''
for item in self.data:
content = item.get('content', '')
cleaned = self.clean_text(content)
# 情感分析
sentiment = self.analyze_sentiment(cleaned)
results['sentiment'][sentiment] += 1
all_text += cleaned + ' '
# 提取关键词
results['keywords'] = self.extract_keywords(all_text)
return results
# 使用示例
analyzer = SocialMediaAnalyzer()
# 添加数据
comments = [
{'content': '这个产品真的很棒!'},
{'content': '太失望了,质量很差'},
{'content': '还不错,值得推荐'},
]
analyzer.add_data(comments)
# 分析
results = analyzer.analyze()
print(f'总数: {results["total"]}')
print(f'情感分布: {results["sentiment"]}')
print(f'关键词: {results["keywords"]}')案例5:数据可视化
python
import matplotlib.pyplot as plt
import matplotlib
from wordcloud import WordCloud
import numpy as np
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
class SocialMediaVisualizer:
"""社交媒体数据可视化"""
def __init__(self):
pass
def plot_sentiment_pie(self, sentiment_data, title='情感分布'):
"""绘制情感分布饼图"""
labels = ['正面', '负面', '中性']
sizes = [
sentiment_data.get('positive', 0),
sentiment_data.get('negative', 0),
sentiment_data.get('neutral', 0),
]
colors = ['#66b3ff', '#ff9999', '#99ff99']
plt.figure(figsize=(8, 6))
plt.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
plt.title(title)
plt.axis('equal')
plt.savefig('sentiment_pie.png', dpi=150, bbox_inches='tight')
plt.show()
def plot_wordcloud(self, keywords, title='关键词词云'):
"""绘制词云图"""
# 构建词频字典
word_freq = {word: count for word, count in keywords}
# 生成词云
wc = WordCloud(
font_path='simhei.ttf',
width=800,
height=400,
background_color='white',
max_words=100,
)
wc.generate_from_frequencies(word_freq)
plt.figure(figsize=(12, 6))
plt.imshow(wc, interpolation='bilinear')
plt.title(title)
plt.axis('off')
plt.savefig('wordcloud.png', dpi=150, bbox_inches='tight')
plt.show()
def plot_time_series(self, dates, values, title='时间趋势', xlabel='日期', ylabel='数量'):
"""绘制时间序列图"""
plt.figure(figsize=(12, 6))
plt.plot(dates, values, marker='o', linewidth=2, markersize=6)
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.savefig('time_series.png', dpi=150, bbox_inches='tight')
plt.show()
# 使用示例
visualizer = SocialMediaVisualizer()
# 情感分布
sentiment = {'positive': 60, 'negative': 20, 'neutral': 20}
visualizer.plot_sentiment_pie(sentiment)
# 词云
keywords = [('Python', 100), ('爬虫', 80), ('数据', 70), ('分析', 60), ('学习', 50)]
visualizer.plot_wordcloud(keywords)案例6:完整社交媒体爬虫
python
import scrapy
import json
from datetime import datetime
class SocialMediaSpider(scrapy.Spider):
"""社交媒体爬虫"""
name = 'social_media'
custom_settings = {
'DOWNLOAD_DELAY': 3,
'CONCURRENT_REQUESTS': 2,
'COOKIES_ENABLED': True,
}
def __init__(self, platform='weibo', keyword=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.platform = platform
self.keyword = keyword
def start_requests(self):
"""生成初始请求"""
if self.platform == 'weibo':
yield from self.start_weibo()
elif self.platform == 'zhihu':
yield from self.start_zhihu()
def start_weibo(self):
"""微博爬取"""
url = 'https://m.weibo.cn/api/container/getIndex'
params = {
'containerid': f'100103type=1&q={self.keyword}',
'page_type': 'searchall',
}
yield scrapy.FormRequest(
url=url,
formdata=params,
callback=self.parse_weibo_search,
meta={'page': 1}
)
def parse_weibo_search(self, response):
"""解析微博搜索结果"""
data = json.loads(response.text)
page = response.meta['page']
cards = data.get('data', {}).get('cards', [])
for card in cards:
card_group = card.get('card_group', [])
for item in card_group:
mblog = item.get('mblog', {})
if mblog:
yield {
'platform': 'weibo',
'id': mblog.get('id'),
'content': mblog.get('text'),
'user': mblog.get('user', {}).get('screen_name'),
'reposts': mblog.get('reposts_count'),
'comments': mblog.get('comments_count'),
'likes': mblog.get('attitudes_count'),
'created_at': mblog.get('created_at'),
'keyword': self.keyword,
}
# 分页
if page < 10:
next_page = page + 1
params = {
'containerid': f'100103type=1&q={self.keyword}',
'page_type': 'searchall',
'page': str(next_page),
}
yield scrapy.FormRequest(
url='https://m.weibo.cn/api/container/getIndex',
formdata=params,
callback=self.parse_weibo_search,
meta={'page': next_page}
)
def start_zhihu(self):
"""知乎爬取"""
url = f'https://www.zhihu.com/api/v4/search_v3'
params = {
't': 'general',
'q': self.keyword,
'correction': '1',
'offset': '0',
'limit': '20',
}
yield scrapy.FormRequest(
url=url,
formdata=params,
callback=self.parse_zhihu_search,
meta={'offset': 0}
)
def parse_zhihu_search(self, response):
"""解析知乎搜索结果"""
data = json.loads(response.text)
offset = response.meta['offset']
for item in data.get('data', []):
obj = item.get('object', {})
if item.get('type') == 'search_result':
yield {
'platform': 'zhihu',
'type': obj.get('type'),
'id': obj.get('id'),
'title': obj.get('title'),
'content': obj.get('excerpt'),
'url': obj.get('url'),
'voteup_count': obj.get('voteup_count'),
'comment_count': obj.get('comment_count'),
'keyword': self.keyword,
}
# 分页
paging = data.get('paging', {})
if not paging.get('is_end') and offset < 100:
next_offset = offset + 20
params = {
't': 'general',
'q': self.keyword,
'correction': '1',
'offset': str(next_offset),
'limit': '20',
}
yield scrapy.FormRequest(
url='https://www.zhihu.com/api/v4/search_v3',
formdata=params,
callback=self.parse_zhihu_search,
meta={'offset': next_offset}
)课后练习
练习1:爬取微博热搜
实现一个爬取微博热搜榜的爬虫。
练习2:爬取知乎回答
实现一个爬取知乎问题下所有回答的爬虫。
练习3:舆情分析系统
实现一个简单的舆情分析系统。
常见问题
Q1: 如何处理社交媒体的登录?
A: 使用 Cookie 登录,或使用 Selenium 模拟登录。
Q2: 如何处理反爬?
A: 使用代理 IP、控制请求频率、伪装请求头。
Q3: 如何处理大量数据?
A: 使用分布式爬虫、数据库存储、增量更新。