爬虫实战(九):爬微博评论
目录
爬虫实战(九):爬微博评论
一、 网站分析
1、 页面分析
安倍jj了,那就让我们来看一看大家对此的评价如何?并且做词云
我们对这条微博的评论进行爬取
首先,还是先分析一下评论数据吧:
通过源码分析,我们发现,微博的评论数据是动态加载出来的,所以我们要进行抓包分析,最后,我们找到了一个,名为buildComments
的数据包,里面存储了响应数据,以及发起下一次评论请求的一个必要参数max_id
,当max_id = 0
时,评论加载完全!

2、 请求参数分析
请求的url:https://weibo.com/ajax/statuses/buildComments
第一个请求包的请求参数:
params = { 'is_reload': 1, # 是否重新加载数据到页面 'id': 4788943477806466, # 微博文章的id,可以在搜索页面中获得 'is_show_bulletin': 2, 'is_mix': 0, 'count': 10, # 推测是获取每页评论条数 'uid': 2803301701, # 发布这篇微博的用户id }
第二个请求包的请求参数:
params = { 'flow': 0, # 根据什么获取,0为热度,1为发布时间 'is_reload': 1, # 是否重新加载数据到页面 'id': 4788943477806466, # 微博文章的id 'is_show_bulletin': 2, 'is_mix': 0, 'max_id': 748402646132114, # 用来控制页数的,这个可以在上一个数据包的响应的max_id 'count': 20, # 推测是获取每页评论条数 'uid': 2803301701, # 发布这篇微博的用户id }
3、搜索功能
通过源码分析,我们发现搜索出来的内容是直接存放到源码中,然后返回的,故其为静态网站

同时,通过对网站分析,我们发现了,id和uid的位置

4、 爬取过程
-
如果是要通过搜索框
- 则可以获取文章的id,和用户的uid来获取到第一个请求链接的params参数
如果是直接使用文章id,和用户uid来获取,则不需要这一步
-
通过第一个评论链接来获取后续的参数,使用递归持续发送请求,注意请求的间隔
-
对数据进行处理
-
生成词云
二、 编写代码
1、 通过搜索获取参数
import requests, time, re # 发送请求,接收JSON数据,正则解析 from prettytable import PrettyTable # 美化展示 from fake_useragent import UserAgent # 随机请求头 from lxml import etree # 进行xpath解析 from urllib import parse # 将中文转换为url编码 search_url = "https://s.weibo.com/weibo?q=%s" # 微博有cookie反爬,如果要使用其搜索功能的话,最好添加cookie headers = { 'authority': 's.weibo.com', 'method': 'GET', 'scheme': 'https', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', 'cache-control': 'no-cache', 'cookie': 'SINAGLOBAL=6849256018210.763.1640318151683; UOR=,,tophub.today; SCF=ArxHEBnS4gWA2vpCEAZLWdEt3WZ41VwutzJfy5_Y0G5csLa_ffzUixMCM9VGFLlHI_NYhIVN-KhWG3pM1VgAOK8.; login_sid_t=f238365342825b4ed0ec7e03a108002a; cross_origin_o=SSL; _s_tentry=weibo.com; Apache=9854327619564.12.1657262555880; ULV=1657262555884:5:1:1:9854327619564.12.1657262555880:1654528223100; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WFcW9qsrf3VzYncb2aZUJFP5JpX5o275NHD95QNeoqfS0zRSoe7Ws4Dqcjdi--fiK.Ri--4iK.Xi-iWi--fiKL2i-2X; SSOLoginState=1657262576; SUB=_2A25Pw6GgDeRhGeFM7VcW8ibKyDyIHXVsuJRorDV8PUNbmtAKLUTckW9NQNYH6iWdr1JwBEvZdTe84ORoGdWLbvsu; ALF=1688798575', 'pragma': 'no-cache', 'referer': 'https://weibo.com/', 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'same-site', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', 'user-agent': UserAgent().random, } def get_id_uid(name): """传入要搜索的内容,返回用户id和文章id""" name = parse.quote(name) info = [] # 这里面存放uid和mid形成的元组 table = PrettyTable(["序号", "发布人", "发布时间", "发布主题"]) headers.update({ 'path': f'/weibo?q={name}', "user-agent": UserAgent().random }) # 防止反爬 resp = requests.get(search_url % name, headers=headers) # 发送请求 resp.encoding = resp.apparent_encoding # 设置编码 html = etree.HTML(resp.text) # 提交给xpath解析 divs = html.xpath('//*[@id="pl_feedlist_index"]/div[2]/div') # 获取到存储内容的div info = [] # 这里面存放uid和mid形成的元组 table = PrettyTable(["序号", "发布时间", "作者", "主题"]) # 进行美化输出 index = 0 for index, div in enumerate(divs): try: mid = div.xpath("./@mid")[0] # 获取mid # print(mid) u_url = div.xpath("./div[@class='card']/div[1]/div/a/@href")[0] # 先获取链接,再解析数据 uid = re.search("weibo.com/(?P<uid>\d+)\?refer", u_url).group("uid") # 解析出uid # print(uid) info.append((mid, uid)) # 添加到列表中 time_ = div.xpath("./div[@class='card']/div[1]/div[2]/p[1]/a/text()")[0] # 发布时间 time_ = time_.strip() time_ = time_.split()[0] # print(time_) author = div.xpath("./div[@class='card']/div[1]/div[2]/p[2]/@nick-name")[0] # 发布人 author = author.strip() # print(author) title = div.xpath("./div[@class='card']/div[1]/div[2]/p[2]/a/text()")[0] # 发布主题 title = title.strip() # print(title) if not (title.startswith("#") and title.endswith("#")): # 一般来说,微博里面的主题都是以#开头结尾的,如果不是,说明有点问题,直接抛弃数据 raise AttributeError if not title or not author: # 如果数据缺失,直接抛弃数据 raise AttributeError table.add_row([index + 1, time_, author, title]) except IndexError: continue except AttributeError: index -= 1 # 这步中断,但是索引还是加一了,所以我们应该使得索引减一 continue print(table) try: i = int(input("请输入序号:")) if not (0 < i <= index + 1): # 如果输入的数据不符合要求,报错 raise AttributeError return info[i - 1] # 返回对应的mid和uid except Exception as e: print("请按照要求输入哦!") return None print(get_id_uid("人民日报安倍晋三")) # 这个搜索尽量具体一点,下面,我们就使用人民日报的评论来测试
2、 获取第一个评论
commit_data = [] arg = ('4788943477806466', '2803301701') base_url = "https://weibo.com/ajax/statuses/buildComments" index = 0 def get_first_commit(arg): # 传入文章id和作者id所组成的元组 global index params_ = { 'is_reload': 1, # 是否重新加载数据到页面 'id': arg[0], # 微博文章的id,可以在搜索页面中获得 'is_show_bulletin': 2, 'is_mix': 0, 'count': 10, # 推测是获取每页评论条数 'uid': arg[1], # 发布这篇微博的用户id } # print(params_) resp = requests.get(url=base_url, params=params_, headers=headers) data = resp.json() max_id = data["max_id"] for i in data["data"]: text = i["text"] text = re.sub("<.*?>", "", text) text = text.strip() if text: commit_data.append(text) print(text) print("-----------------------------------------------------") print("max_id", max_id) print(f"爬取完{index}第页评论,休息4秒钟") print("------------------------------------------------------") index += 1 time.sleep(4) return max_id # 返回max_id max_id = get_first_commit(arg) print(type(max_id))
3、 使用递归获取每页信息
def get_other_commit(arg, max_id): global index if max_id == 0: return "大部分内容获取完成!" params = { 'flow': 0, # 根据什么获取,0为热度,1为发布时间 'is_reload': 1, # 是否重新加载数据到页面 'id': arg[0], # 微博文章的id 'is_show_bulletin': 2, 'is_mix': 0, 'max_id': max_id, # 用来控制页数的,这个可以在上一个数据包的响应的max_id 'count': 20, # 推测是获取每页评论条数 'uid': arg[1], # 发布这篇微博的用户id } resp = requests.get(url=base_url, params=params, headers=headers) data = resp.json() max_id = data["max_id"] commit = data["data"] if commit: for i in commit: text = i["text"] text = re.sub("<.*?>", "", text) text = text.strip() if text: commit_data.append(text) print(text) print("-----------------------------------------------------") print("max_id", max_id) print(f"爬取完{index}第页评论,休息4秒钟") print("------------------------------------------------------") index += 1 time.sleep(4) return get_other_commit(arg, max_id) return "大部分内容获取完成!" print(get_other_commit(arg, max_id))
4、 写入文件
with open("./commit.txt", "a+", encoding="utf-8") as f: string = "\n".join(commit_data) # 列表数据的拼接 f.write(string) # 写入文件中,先进行存储,防止出现意外
5、 处理数据
import jieba data = open("./commit.txt", "r", encoding="utf-8").read() # 获取我们刚才获取到的数据 # print(jieba.cut(data)) # 对数据进行分词,得到一个生成器 stop_word = open("./stoplist.txt", encoding='utf8').read().split() # 导入停词表,进行数据的清洗 # print(stop_word) word_list = [w for w in jieba.cut(data)] # print(word_list)
6、 生成词云
from wordcloud import WordCloud font = "./font.ttf" # 导入字体 wc = WordCloud(font_path=font, width = 1000, height = 700, background_color='white', max_words=100, stopwords=stop_word # 加载停用词 ) wc.generate(" ".join(word_list)) # 加载词云文本 img = wc.to_image() img.show()
三、 总代码
import requests, time, re # 发送请求,接收JSON数据,正则解析 from prettytable import PrettyTable # 美化展示 from fake_useragent import UserAgent # 随机请求头 from lxml import etree # 进行xpath解析 from urllib import parse # 将中文转换为url编码 from wordcloud import WordCloud # 导入词云库生成词云 import jieba # 导入jieba库分词 search_url = "https://s.weibo.com/weibo?q=%s" # 搜索要使用的url base_url = "https://weibo.com/ajax/statuses/buildComments" # 获取评论需要使用的url # 微博有cookie反爬,如果要使用其搜索功能的话,最好添加cookie headers = { 'authority': 's.weibo.com', 'method': 'GET', 'scheme': 'https', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', 'cache-control': 'no-cache', 'cookie': 'SINAGLOBAL=6849256018210.763.1640318151683; UOR=,,tophub.today; SCF=ArxHEBnS4gWA2vpCEdEt3WZ41VwutzJfy5_Y0G5csLa_ffzUixMCM9VGFLlHI_NYhIVN-KhWG3pM1VgAOK8.; login_sid_t=f238365342825b4ed0ec7e03a108002a; cross_origin_proto=SSL; _s_tentry=weibo.com; Apache=9854327619564.12.1657262555880; ULV=1657262555884:5:1:1:9854327619564.12.1657262555880:1654528223100; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WFcW9qsrf3VzYncb2aZUJFP5JpX5o275NHD95QNeoqfS0zRSoe7Ws4Dqcjdi--fiKnRiK.Ri--4iK.Xi-iWi--fiKL2i-2X; SSOLoginState=1657262576; SUB=_2A25Pw6GgDeRhGeFM7VcW8ibKyDyIHXVsuJRorDV8PUNbmtAKLUTckW9NQNYH6iWdr1JwBEvZdTe84ORoGdWLbvsu; ALF=1688798575', 'pragma': 'no-cache', 'referer': 'https://weibo.com/', 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'same-site', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', 'user-agent': UserAgent().random, } commit_data = [] # 存储获取到的数据 index = 0 # 记录爬取评论页数,打印日志 def get_id_uid(name): """传入要搜索的内容""" name = parse.quote(name) info = [] # 这里面存放uid和mid形成的元组 table = PrettyTable(["序号", "发布人", "发布时间", "发布主题"]) headers.update({ 'path': f'/weibo?q={name}', "user-agent": UserAgent().random }) # 防止反爬 resp = requests.get(search_url % name, headers=headers) # 发送请求 resp.encoding = resp.apparent_encoding # 设置编码 html = etree.HTML(resp.text) # 提交给xpath解析 divs = html.xpath('//*[@id="pl_feedlist_index"]/div[2]/div') # 获取到存储内容的div info = [] # 这里面存放uid和mid形成的元组 table = PrettyTable(["序号", "发布时间", "作者", "主题"]) # 进行美化输出 index = 0 for index, div in enumerate(divs): try: mid = div.xpath("./@mid")[0] # 获取mid # print(mid) u_url = div.xpath("./div[@class='card']/div[1]/div/a/@href")[0] # 先获取链接,再解析数据 uid = re.search("weibo.com/(?P<uid>\d+)\?refer", u_url).group("uid") # 解析出uid # print(uid) info.append((mid, uid)) # 添加到列表中 time_ = div.xpath("./div[@class='card']/div[1]/div[2]/p[1]/a/text()")[0] # 发布时间 time_ = time_.strip() time_ = time_.split()[0] # print(time_) author = div.xpath("./div[@class='card']/div[1]/div[2]/p[2]/@nick-name")[0] # 发布人 author = author.strip() # print(author) title = div.xpath("./div[@class='card']/div[1]/div[2]/p[2]/a/text()")[0] # 发布主题 title = title.strip() # print(title) if not (title.startswith("#") and title.endswith("#")): # 一般来说,微博里面的主题都是以#开头结尾的,如果不是,说明有点问题,直接抛弃数据 raise AttributeError if not title or not author: # 如果数据缺失,直接抛弃数据 raise AttributeError table.add_row([index + 1, time_, author, title]) except IndexError: continue except AttributeError: index -= 1 continue print(table) try: i = int(input("请输入序号:")) if not (0 < i <= index + 1): # 如果输入的数据不符合要求,报错 raise AttributeError return info[i - 1] # 返回对应的mid和uid except Exception as e: print("请按照要求输入哦!") return None def get_first_commit(arg): # 传入文章id和作者id所组成的元组 global index params_ = { 'is_reload': 1, # 是否重新加载数据到页面 'id': arg[0], # 微博文章的id,可以在搜索页面中获得 'is_show_bulletin': 2, 'is_mix': 0, 'count': 10, # 推测是获取每页评论条数 'uid': arg[1], # 发布这篇微博的用户id } # print(params_) resp = requests.get(url=base_url, params=params_, headers=headers) data = resp.json() max_id = data["max_id"] for i in data["data"]: text = i["text"] text = re.sub("<.*?>", "", text) text = text.strip() if text: commit_data.append(text) print(text) print("-----------------------------------------------------") print("max_id", max_id) print(f"爬取完{index}第页评论,休息4秒钟") print("------------------------------------------------------") index += 1 time.sleep(4) return max_id # 返回max_id def get_other_commit(arg, max_id): global index if max_id == 0: return "大部分内容获取完成!" params = { 'flow': 0, # 根据什么获取,0为热度,1为发布时间 'is_reload': 1, # 是否重新加载数据到页面 'id': arg[0], # 微博文章的id 'is_show_bulletin': 2, 'is_mix': 0, 'max_id': max_id, # 用来控制页数的,这个可以在上一个数据包的响应的max_id 'count': 20, # 推测是获取每页评论条数 'uid': arg[1], # 发布这篇微博的用户id } resp = requests.get(url=base_url, params=params, headers=headers) data = resp.json() max_id = data["max_id"] commit = data["data"] if commit: for i in commit: text = i["text"] text = re.sub("<.*?>", "", text) text = text.strip() if text: commit_data.append(text) print(text) print("-----------------------------------------------------") print("max_id", max_id) print(f"爬取完{index}第页评论,休息4秒钟") print("------------------------------------------------------") index += 1 time.sleep(4) return get_other_commit(arg, max_id) return "大部分内容获取完成!" def get_img(): data = open("./commit.txt", "r", encoding="utf-8").read() # 获取我们刚才获取到的数据 # print(jieba.cut(data)) # 对数据进行分词,得到一个生成器 stop_word = set(open("./stoplist.txt", encoding='utf-8').read().split()) # 导入停词表,进行数据的清洗,这个可以直接在百度上搜索下载 # print(stop_word) word_list = [w for w in jieba.cut(data)] # print(word_list) font = "./font.ttf" # 导入字体 wc = WordCloud(font_path=font, width=1000, height=700, background_color='white', max_words=100, stopwords=stop_word, # 加载停用词 ).generate(" ".join(word_list)) # 加载词云文本 wc.to_file("./ret.png") def main(): name = input("请输入内容:") arg = get_id_uid(name) if arg: print(get_other_commit(arg, get_first_commit(arg))) with open("./commit.txt", "a+", encoding="utf-8") as f: string = "\n".join(commit_data) f.write(string) choice = input("是否生成词云:[yes/no]") if choice == "yes": get_img() if __name__ == '__main__': main() # get_img()
所有源代码及其文件,都在这里:https://github.com/liuzhongkun1/spider_/tree/master/%E7%88%AC%E8%99%AB/%E5%BE%AE%E5%8D%9A%E8%AF%84%E8%AE%BA
本文来自博客园,作者:Kenny_LZK,转载请注明原文链接:https://www.cnblogs.com/liuzhongkun/p/16459782.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器