爬虫背景
最近有这方面的需求,于是就研究了一下通过Java爬取微博数据,由于本人是后端Java开发,因此没有研究其他爬取微博数据的方法,比如通过Python爬取微博数据。大家感兴趣的可以自行查找基于Python爬取微博数据的方法。在爬取微博数据之前,先声明一下,本人爬取的微博数据仅用于测试Java爬取微博数据的可行性,并不会用于其他非正当地方,另外,爬取的数据也都是每个人都可以通过微博客户端正常看到的,不存在爬取隐秘数据的情况。大家在进行爬取数据的操作时也应注意不该爬取非授权数据,防止给自己喜提“非法获取计算机信息系统数据罪”“破坏计算机信息系统罪”等。一切爬虫操作都应在合法合规的情况下进行。
爬虫分析
在进行爬虫操作之前,我们先来看一下微博客户端的页面结构,以及对应的请求链接,数据响应情况等,方便为后续爬取微博数据做准备。比如这里打开一个环球网的微博主页:https://weibo.com/u/1686546714 可以看到
那么我们打开浏览器开发者工具,按F12键,打开开发者工具,选择【网络】或者【network】,然后再次刷新当前页面可以看到如下请求
点击对应的URL,查看URL的响应,最终会找到请求链接 /ajax/statuses/mymblog?uid=1686546714&page=1&feature=0 的响应正是我们需要爬取的数据内容来源
到这里,确定了数据来源URL之后,我们就可以进行后续的爬取数据操作了。
爬取微博主页正文列表数据
引入jar包
整个爬取数据操作我们需要用到两个 jar 包 hutool-all 、 fastjson ,那么我们需要首先在项目 pom.xml 文件中引入这两个 jar 包
<!-- hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version> 5.3.4</version> </dependency> <!-- 阿里JSON解析器 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.80</version> </dependency>
编写代码
jar 包引入之后开始编写代码,编写完成后完整代码如下 DemoWeiBo.java
package com.ruoyi.web.controller.demo.controller; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; public class DemoWeiBo { /** * 主函数入口,用于从微博抓取数据并存储到Excel中。 * * @param args 命令行参数(未使用) * @throws ParseException 当日期解析发生错误时抛出 */ public static void main(String[] args) throws ParseException { // 定义微博数据抓取的URL模板 String url = "https://weibo.com/ajax/statuses/mymblog?uid=1686546714&feature=0&page=%s"; // 初始化日期格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 循环抓取3页数据 for (int i = 1; i <= 1; i++) { try { // 输出开始抓取的提示信息 System.out.println("开始获取第" + i + "页数据"); // 格式化URL并发送HTTP请求获取响应 String urlstr = String.format(url, i); HttpResponse response = HttpUtil.createGet(urlstr) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie","浏览器Cookie") .execute(); // 解析响应体 String body = response.body(); //System.out.println(body); JSONObject jsonObject2 = JSONObject.parseObject(body).getJSONObject("data"); JSONArray list = null; if (Objects.nonNull(jsonObject2)) { // 处理数据列表 list = jsonObject2.getJSONArray("list"); // 遍历并处理每条微博数据 for (Object o : list) { JSONObject data = (JSONObject) o; // 解析并处理微博的其他信息 Date created = new Date(data.getString("created_at")); System.out.println("created:"+dateFormat.format(created)); String regex = "<[^<>]*>"; String text = data.getString("text").replaceAll(regex, ""); System.out.println("text:"+text); String repost = data.getString("reposts_count"); System.out.println("repost:"+repost); String comment = data.getString("comments_count"); System.out.println("comment:"+comment); String like = data.getString("attitudes_count"); System.out.println("like:"+like); } } // 输出完成提示并关闭响应,休眠以避免频繁请求 System.out.println("第" + i + "页数据获取完毕"); response.close(); // 如果列表为空,终止循环 if (list == null || list.size() == 0) { break; } Thread.sleep(700); } catch (Exception e) { // 打印异常信息 e.printStackTrace(); } } } }
代码中请求 URL 中的参数 page 代表当前爬取的是第几页数据,因此代码中进行了字符占位,方便后续的分页数据的替换
关于微博 Cookie
爬取微博数据时,必须要为请求 URL 添加 Header 信息 ,增加请求头 Cookie ,没有请求头 Cookie 的话,无法返回正常的响应数据,而是重定向到访客页面
那么关于请求头 Cookie 的来源,我们可以到浏览器的【网络】中刚才找到的请求 URL ,点击【标头】 下滑看到如下内容,红框部分就是 Cookie 内容
由于没有登录账号,因此这里的 Cookie 就属于访客 Cookie,那么微博对于访客 Cookie 的数据访问权限比较有限,在通过访客 Cookie 获取数据时,你只能获取当前请求 URL 的前两页数据,每页 20 条,整体也就是 40条数据,如果有置顶微博的话,置顶微博不算在这两页内,那么你就可能会获取到 大于 40 条的微博数据。当你获取 第 3 页数据时,请求链接只返回成功状态,但是没有 data 数据返回
而正常情况下 response.body() 应该返回这样的内容
将 response.body() 数据格式化之后 如图
获取到微博数据之后,在代码中打印的数据内容具体属性 text : 文本内容 reposts_count : 转发数 comments_count : 评论数 attitudes_count : 点赞数
最终的打印结果可以看到如下请求链接返回内容,
到这里我们爬取微博数据就完成了,整个代码逻辑比较清晰,后续对于爬取到的微博数据的处理可以根据具体的业务需求。
处理文本的正则
另外,对于代码中的正则表达式 String regex = "<[^<>]*>"; 表示的意义:【用于匹配以"<"开头,紧接着是0个或多个不包括"<"和">"的字符,最后以">"结尾的字符串。这个正则表达式常用于从一段文本中提取标签内容,例如从
<html><body><h1>Hello, World!</h1></body></html>
中提取出
Hello, World!
在线正则表达式匹配结果如图
当然,微博数据并不是只有这些的,你可以直接将我们爬取数据的请求 URL 放在浏览器看到
那么到这里关于 爬取微博主页正文列表数据 的操作就完成了。
微博正文长文本补全
什么是正文长文本
在爬取微博主页正文列表数据的过程中,大家可能不太会注意到这样的微博数据,比如
这样的文本数据有什么特点呢?直观的可以看到 在微博正文结束 出现了【展开】字样,那么这样的微博内容通过Java爬取数据获取到的 text 字段的取值内容是这样的
text:#伊朗将宣布总统莱希等遇难人员葬礼安排#据伊朗国家电视台报道,伊朗政府内阁举行了特别会议,将会宣布伊朗总统莱希等遇难人员的葬礼安排。#伊朗总统莱希等高级官员遇难#据伊朗官方通讯社报道,莱希5月19日在伊朗东阿塞拜疆省出席一个大坝的落成仪式后,其所乘坐的直升机在返回大不里士的途中失事, ...展开
可以看到 text 字段同样返回的内容是有 【展开】 字样的,那么按常理看,微博正文内容肯定时没有获取完整的。那么这个时候就需要补齐长文本了。
获取正文长文本
在微博页面点击【展开】可以看到,触发了ajax 方法 /ajax/statuses/longtext?id=Of8PMwTSJ 获取微博内容详情并补足内容展示
参数 id=Of8PMwTSJ 来自于 爬取微博数据请求链接 /ajax/statuses/mymblog?uid=1686546714&page=1&feature=0 返回的数据
编写代码
下面对于有 【...展开】 字样的微博内容,往往就是需要补足微博长文本内容的,那么可以在代码中增加如下内容
//有一种情况,就是当页面文本内容过多的时候,微博默认不展示全部,而是出现 【...展示】 按钮,此时需要再请求一个 URL 获取展开后的文本内容 if (text.lastIndexOf("...展开") != -1) { //说明存在 展开 需要重新获取 text 内容 String mblogid = data.getString("mblogid"); // 格式化URL并发送HTTP请求获取响应 String unfoldurlstr = String.format(unfoldurl, mblogid); HttpResponse response2 = HttpUtil.createGet(unfoldurlstr) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie",cookie) .execute(); // 如果没有长文本内容会返回 {"ok": 1,"http_code": 200,"data": {}} String body2 = response2.body(); JSONObject jsonObject2 = JSONObject.parseObject(body2).getJSONObject("data"); String longTextContent = jsonObject2.getString("longTextContent"); System.out.println("longTextContent:"+longTextContent); }
其中,cookie和在爬取微博正文内容时用的是同一个cookie,再次执行 main 方法看到如下内容
到这里关于微博主页正文列表中微博正文内容在爬取数据时缺失的 【...展开】里面的文本内容也就补齐了,那么下面我们就可以导出我们爬取到的微博主页正文列表数据到 Excel 中查看了。
导出微博数据到Excel
引入jar包
补全了微博正文内容后,就可以进行下一步操作了,将爬取的微博数据导出到Excel中去,那么首先需要引入 Excel 相关操作 jar 包 ,pom.xml 文件增加
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.0.5</version> </dependency>
编写代码
然后根据所需字段创建导出微博数据的实体对象类 ExcelData.java
package com.ruoyi.web.controller.demo.controller; import com.alibaba.excel.annotation.ExcelProperty; import java.util.Date; /** * dongao * 2024/4/23 * 4月 */ public class ExcelData { "发布日期") ( private Date date; "内容") ( private String content; "点赞") ( private Long like; "评论") ( private Long comment; "转发") ( private Long repost; public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public Long getLike() { return like; } public void setLike(Long like) { this.like = like; } public Long getComment() { return comment; } public void setComment(Long comment) { this.comment = comment; } public Long getRepost() { return repost; } public void setRepost(Long repost) { this.repost = repost; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
最后就是改造 main 方法,增加导出数据操作,改造后的 main 方法结构如下
for 循环内部需要增加写入 对象 ExcelData 并放入导出列表的代码,
那么最后改造后的 main 方法的全部代码 如下
package com.ruoyi.web.controller.demo.controller; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; public class DemoWeiBo { /** * 主函数入口,用于从微博抓取数据并存储到Excel中。 * * @param args 命令行参数(未使用) * @throws ParseException 当日期解析发生错误时抛出 */ public static void main(String[] args) throws ParseException { // 定义微博数据抓取的URL模板 String url = "https://weibo.com/ajax/statuses/mymblog?uid=1686546714&feature=0&page=%s"; String unfoldurl = "https://weibo.com/ajax/statuses/longtext?id=%s"; String cookie = "你的cookie"; // 初始化日期格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //初始化导出Excel数据列表 List<ExcelData> excelDataList = new ArrayList<>(); // 循环抓取前2页数据 for (int i = 1; i <= 2; i++) { try { // 输出开始抓取的提示信息 System.out.println("开始获取第" + i + "页数据"); // 格式化URL并发送HTTP请求获取响应 String urlstr = String.format(url, i); HttpResponse response = HttpUtil.createGet(urlstr) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie",cookie) .execute(); // 解析响应体 String body = response.body(); //System.out.println(body); JSONObject jsonObject = JSONObject.parseObject(body).getJSONObject("data"); JSONArray list = null; if (Objects.nonNull(jsonObject)) { // 处理数据列表 list = jsonObject.getJSONArray("list"); // 遍历并处理每条微博数据 for (Object o : list) { JSONObject data = (JSONObject) o; // 解析并处理微博的其他信息 Date created = new Date(data.getString("created_at")); System.out.println("created:"+dateFormat.format(created)); String regex = "<[^<>]*>"; String text = data.getString("text").replaceAll(regex, ""); System.out.println("text:"+text); String repost = data.getString("reposts_count"); System.out.println("repost:"+repost); String comment = data.getString("comments_count"); System.out.println("comment:"+comment); String like = data.getString("attitudes_count"); System.out.println("like:"+like); //有一种情况,就是当页面文本内容过多的时候,微博默认不展示全部,而是出现 【...展示】 按钮,此时需要再请求一个 URL 获取展开后的文本内容 if (text.lastIndexOf("...展开") != -1) { //说明存在 展开 需要重新获取 text 内容 String mblogid = data.getString("mblogid"); // 格式化URL并发送HTTP请求获取响应 String unfoldurlstr = String.format(unfoldurl, mblogid); HttpResponse response2 = HttpUtil.createGet(unfoldurlstr) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie",cookie) .execute(); // {"ok": 1,"http_code": 200,"data": {}} String body2 = response2.body(); JSONObject jsonObject2 = JSONObject.parseObject(body2).getJSONObject("data"); String longTextContent = jsonObject2.getString("longTextContent"); System.out.println("longTextContent:"+longTextContent); //补全后的内容赋给 text text = longTextContent; } // 创建ExcelData对象并填充数据 ExcelData excelData = new ExcelData(); excelData.setDate(created); excelData.setLike(Long.parseLong(like)); excelData.setComment(Long.parseLong(comment)); excelData.setRepost(Long.parseLong(repost)); excelData.setContent(text); excelDataList.add(excelData); } } // 输出完成提示并关闭响应,休眠以避免频繁请求 System.out.println("第" + i + "页数据获取完毕"); response.close(); // 如果列表为空,终止循环 if (list == null || list.size() == 0) { break; } Thread.sleep(700); } catch (Exception e) { // 打印异常信息 e.printStackTrace(); } } // 输出开始写入Excel的提示 System.out.println("Excel写入数据开始"); // 写入Excel的函数调用 EasyExcel.write("E:/微博.xlsx", ExcelData.class) .sheet("Sheet1") .doWrite(excelDataList); System.out.println("Excel写入数据结束"); } }
执行 main 方法,执行完成之后,看到已经成功导出到Excel 中
打开我们指定目录下的 Excel 文件
这里可以看到我们已经用再次获取的长文本内容替换了原始文本内容,补足内容了。
到这里,基于 Java 爬取微博主页正文列表数据,并补充长文本微博正文内容,导出微博数据到 Excel 表格的操作就完成了。
突来的疑问
那么 基于 Java 爬取微博数据真的就算结束了?真的结束了吗?那么我如果想在爬取微博主页正文列表内容时同时把微博正文内容中的 图片 or 视频 也爬下来,你现在的代码不支持吧!确实,基于以上的 代码 并没有关于获取微博正文内容中 图片 or 视频 的数据分析和爬虫操作,那么下面我们继续讲述如何处理微博正文中的图片/视频等内容。
微博正文内容分析
对于微博正文来说,图片和视频不能同时存在,也就是说你的微博只能选择发9张以内的图片或者发1个视频,那么在爬取微博正文数据时,想要获取微博中的图片/视频该怎么操作呢?我们首先来看一下微博正文中图片或者视频的一些特点,选择任意一篇微博正文,利用浏览器工具选取任意一张图片
这里可以看到图片的链接。这里需要说明的是微博正文中的图片链接是做过防盗处理的,你直接复制图片链接到浏览器是无法打开的,但是你可以通过 Java 代码来转存 图片。
同样的方法,利用浏览器工具的抓取功能获取微博正文视频内容地址,视频内容是可以直接放在浏览器打开的,但是需要注意的是视频内容的链接地址有个有效期参数 Expires ,如果超过有效期再打开会提示 403 错误码
这里获取的视频链接地址通常是这样的
//locallimit.us.sinaimg.cn/o0/RSkMM0NElx08f30vCBPO01041200MgYx0E010.mp4?label=mp4_720p&template=1280x720.25.0&media_id=5036768815153197&tp=8x8A3El:YTkl0eM8&us=0&ori=1&bf=4&ot=h&lp=0000tfMx8&ps=mZ6WB&uid=66f6G2&ab=,8013-g0,3601-g27&Expires=1716360984&ssig=8XIVHnvPwu&KID=unistore,video
没有请求头 http 或者 https ,那么你可以直接将该链接放在浏览器,浏览器会自动适应请求头,视频内容是可以直接播放的。另外微博的默认请求头就是安全请求头 https
到这里关于微博正文中的图片 or 视频的大概情况介绍完了,下面可以转存图片 or 视频
转存 图片 or 视频
如果你需要微博正文中的图片 or 视频的话,那么你可以通过 Java 的方式获取图片 or 视频 内容,并转存到其他地方,这里我转存到本地电脑,整个 main 函数的代码如下 DemoWeiBoDown.java
package com.ruoyi.web.controller.demo.controller; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; /** * dongao * 2024/5/20 * 5月 */ public class DemoWeiBoDown { public static void main(String[] args) { String imageUrl = "https://wx4.sinaimg.cn/orj360/001Q8zQmly1hpy4e694vyj60j60can2r02.jpg"; String mp4Url = "https://locallimit.us.sinaimg.cn/o0/fn1ZOcw6lx08f0fwPoH601041200BRMn0E010.mp4?label=mp4_720p&template=1280x720.25.0&media_id=5036142555496496&tp=8x8A3El:7fHprYw&us=0&ori=1&bf=4&ot=h&lp=0000tfMx8&ps=mZ6WB&uid=66f6G2&ab=,8013-g0,3601-g36,3601-g27,3601-g27&Expires=1716201414&ssig=B05wQvtmef&KID=unistore,video"; String filename = imageUrl.substring(imageUrl.lastIndexOf("/") + 1); String filename2 = mp4Url.substring(mp4Url.lastIndexOf("/") + 1, mp4Url.indexOf("?")); downloadPicture(imageUrl, "E:\\2024weibo\\"+filename); downloadPicture(mp4Url, "E:\\2024weibo\\"+filename2); } /** * 下载图片到指定路径 * * @param imageUrl 图片的URL地址 * @param savePath 图片保存的本地路径 */ public static void downloadPicture(String imageUrl, String savePath){ BufferedInputStream in = null; FileOutputStream out = null; HttpURLConnection connection = null; try { // 创建URL对象并打开连接 URL url = new URL(imageUrl); connection = (HttpURLConnection) url.openConnection(); // 设置请求方法为GET connection.setRequestMethod("GET"); // 建立连接 connection.connect(); // 获取响应码并判断是否下载成功 int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // 创建输入流和输出流,用于读取和保存图片 in = new BufferedInputStream(connection.getInputStream()); out = new FileOutputStream(savePath); // 缓冲区,用于一次读取和写入一定量的数据 byte[] buffer = new byte[1024]; int bytesRead; // 循环读取直到没有数据 while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("图片/视频 下载成功,保存路径:" + savePath); } else { // 响应码不为HTTP_OK,下载失败 System.out.println("无法下载图片/视频,响应码:" + responseCode); } }catch (Exception e) { // 捕获异常并打印堆栈信息 e.printStackTrace(); }finally { // 无论成功或失败,最后都关闭流和连接 // 关闭输入流 if (in != null) { try { in.close(); } catch (IOException e) { // 将IO异常转为运行时异常抛出 throw new RuntimeException(e); } } // 关闭输出流 if (out != null) { try { out.close(); } catch (IOException e) { // 将IO异常转为运行时异常抛出 throw new RuntimeException(e); } } // 关闭连接 if (connection != null) { connection.disconnect(); } } } }
执行当前 main 方法可以看到如下输出情况
这里的视频 响应码 403 就是因为视频链接的有效期已经过了,更换视频链接地址尝试后如下
那么这时去到我们的本地文件夹就可以看到刚才转存成功的图片和视频了
到这里,关于转存微博正文内容中的图片 or 视频的操作就完成了,整个过程比较简单顺畅,当然如果你需要转存到云存储也是可以的,只是需要改造一下获取到图片流 or 视频流后的操作就可以了。
补充微博正文列表图片 or 视频 内容
在通过对微博正文内容中的图片 or 视频内容进行分析后,图片 or 视频 链接是可以直接通过 Java 代码下载或者转存的,那么这样就可以补充我们在 【获取微博主页正文列表数据】 时缺失的图片 or 视频信息了,当然,如果你的需求并不需要转存微博正文列表内容中的图片 or 视频的话,那么你就无需进行下面的操作了。在开始进行微博主页正文列表数据 补充 图片 or 视频内容之前,先来分析一下获取到的微博正文列表数据的内容。
数据分析
同样的,我们先找到获取微博正文列表数据的 ajax 请求 /ajax/statuses/mymblog?uid=1686546714&page=1&feature=0 的响应返回数据
获取到微博正文列表请求响应返回的数据之后,我从中取出一个含图片的完整的微博正文 json 对象 以及 一个含视频的完整的微博正文 json 对象来做一个比较
通过对比工具 Beyond Compare 进行比较这两种情况下返回数据格式的不同,可以看到 含图片的微博正文 返回数据比 含视频的微博正文多了 pic_infos 对象
继续向下比较可以看到 含图片的微博正文 比 含视频的微博正文 少了 page_info对象 而 page_info对象 里面的 media_info 对象正是视频所在对象
到这里,对于微博正文列表内容 含图片微博正文 以及 含视频微博正文 的数据格式基本的分析及对比就结束了,下面开始在 获取微博正文列表内容 DemoWeiBo.java 的 main 方法中补充这一块内容的获取。
首先给导出实体类 ExcelData 增加如下字段
然后在获取微博主页正文列表数据的 main 方法中增加处理图片 or 视频的代码即可,这里需要注意的是 pic_ids 和 pic_infos 是配套出现的, pic_ids 的值 就是 pic_infos 子对象的 key
关于 pic_infos 的子对象包括多种 宽高 尺寸的图片链接地址,你可以根据自己的需要选择不同宽高的图片进行转存
最后补充的转存微博主页正文列表 图片的代码如下
下面再来看获取视频操作,你可以选择 media_info 对象内的以下几种清晰度的视频,
或者也可以选择 media_info 对象内的 playback_list ,里面是 四种清晰度的视频选择,你可以选择其中一种或者多种清晰度的视频链接
最终补充获取微博正文视频内容的代码如下
编写代码
到这里,补充微博主页正文内容列表 获取 图片 or 视频的操作就完成了,改造后的 DemoWeiBo.java 代码完整版如下
package com.ruoyi.web.controller.demo.controller; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; public class DemoWeiBo { /** * 主函数入口,用于从微博抓取数据并存储到Excel中。 * * @param args 命令行参数(未使用) * @throws ParseException 当日期解析发生错误时抛出 */ public static void main(String[] args) throws ParseException { // 定义微博数据抓取的URL模板 String url = "https://weibo.com/ajax/statuses/mymblog?uid=1686546714&feature=0&page=%s"; String unfoldurl = "https://weibo.com/ajax/statuses/longtext?id=%s"; String cookie = "你的 Cookie"; // 初始化日期格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //初始化导出Excel数据列表 List<ExcelData> excelDataList = new ArrayList<>(); // 循环抓取前2页数据 for (int i = 1; i <= 3; i++) { try { // 输出开始抓取的提示信息 System.out.println("开始获取第" + i + "页数据"); // 格式化URL并发送HTTP请求获取响应 String urlstr = String.format(url, i); HttpResponse response = HttpUtil.createGet(urlstr) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie",cookie) .execute(); // 解析响应体 String body = response.body(); //System.out.println(body); JSONObject jsonObject = JSON.parseObject(body).getJSONObject("data"); JSONArray list = null; if (Objects.nonNull(jsonObject)) { // 处理数据列表 list = jsonObject.getJSONArray("list"); // 遍历并处理每条微博数据 for (Object o : list) { JSONObject data = (JSONObject) o; // 解析并处理微博的其他信息 Date created = new Date(data.getString("created_at")); System.out.println("created:"+dateFormat.format(created)); String regex = "<[^<>]*>"; String text = data.getString("text").replaceAll(regex, ""); String repost = data.getString("reposts_count"); String comment = data.getString("comments_count"); String like = data.getString("attitudes_count"); //获取微博正文图片信息 StringBuffer pic_url = new StringBuffer(); Long pic_num = data.getLong("pic_num"); if (pic_num > 0 ) { JSONArray pic_ids = data.getJSONArray("pic_ids"); JSONObject pic_infos = data.getJSONObject("pic_infos"); // 遍历 pic_ids 获取 pic_infos 子对象 key for (Object json : pic_ids) { String key = (String) json; JSONObject pic = pic_infos.getJSONObject(key); JSONObject largest = pic.getJSONObject("largest"); // 提取图片URL并处理 String imageUrl = largest.getString("url"); String filename = imageUrl.substring(imageUrl.lastIndexOf("/") + 1); // 下载图片 String savePath = "E:\\2024weibo\\" + filename; downloadPicture(imageUrl, savePath); pic_url = pic_url.append(savePath).append(","); } } //获取微博正文视频信息 String video_url = ""; JSONObject page_info = data.getJSONObject("page_info"); if (Objects.nonNull(page_info)) { JSONObject media_info = page_info.getJSONObject("media_info"); String mp4_hd_url = media_info.getString("mp4_hd_url"); String filename = mp4_hd_url.substring(mp4_hd_url.lastIndexOf("/") + 1, mp4_hd_url.indexOf("?")); // 下载视频 String savePath = "E:\\2024weibo\\" + filename; downloadPicture(mp4_hd_url, savePath); video_url = savePath; } //有一种情况,就是当页面文本内容过多的时候,微博默认不展示全部,而是出现 【...展示】 按钮,此时需要再请求一个 URL 获取展开后的文本内容 if (text.lastIndexOf("...展开") != -1) { //说明存在 展开 需要重新获取 text 内容 String mblogid = data.getString("mblogid"); // 格式化URL并发送HTTP请求获取响应 String unfoldurlstr = String.format(unfoldurl, mblogid); HttpResponse response2 = HttpUtil.createGet(unfoldurlstr) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie",cookie) .execute(); // {"ok": 1,"http_code": 200,"data": {}} String body2 = response2.body(); JSONObject jsonObject2 = JSONObject.parseObject(body2).getJSONObject("data"); String longTextContent = jsonObject2.getString("longTextContent"); System.out.println("longTextContent:"+longTextContent); //补全后的内容赋给 text text = longTextContent; } // 创建ExcelData对象并填充数据 ExcelData excelData = new ExcelData(); //发布时间 excelData.setDate(created); //点赞数 excelData.setLike(Long.parseLong(like)); //评论数 excelData.setComment(Long.parseLong(comment)); //转发数 excelData.setRepost(Long.parseLong(repost)); //原始内容 excelData.setContent(text); //图片地址 excelData.setImgUrl(pic_url.toString()); //视频地址 excelData.setVideoUrl(video_url); excelDataList.add(excelData); } } // 输出完成提示并关闭响应,休眠以避免频繁请求 System.out.println("第" + i + "页数据获取完毕"); response.close(); // 如果列表为空,终止循环 if (list == null || list.size() == 0) { break; } Thread.sleep(700); } catch (Exception e) { // 打印异常信息 e.printStackTrace(); } } // 输出开始写入Excel的提示 System.out.println("Excel写入数据开始"); // 写入Excel的函数调用 EasyExcel.write("E:/微博.xlsx", ExcelData.class) .sheet("Sheet1") .doWrite(excelDataList); System.out.println("Excel写入数据结束"); } /** * 下载图片到指定路径 * * @param imageUrl 图片的URL地址 * @param savePath 图片保存的本地路径 */ public static void downloadPicture(String imageUrl, String savePath){ BufferedInputStream in = null; FileOutputStream out = null; HttpURLConnection connection = null; try { // 创建URL对象并打开连接 URL url = new URL(imageUrl); connection = (HttpURLConnection) url.openConnection(); // 设置请求方法为GET connection.setRequestMethod("GET"); // 建立连接 connection.connect(); // 获取响应码并判断是否下载成功 int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // 创建输入流和输出流,用于读取和保存图片 in = new BufferedInputStream(connection.getInputStream()); out = new FileOutputStream(savePath); // 缓冲区,用于一次读取和写入一定量的数据 byte[] buffer = new byte[1024]; int bytesRead; // 循环读取直到没有数据 while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("图片/视频 下载成功,保存路径:" + savePath); } else { // 响应码不为HTTP_OK,下载失败 System.out.println("无法下载图片/视频,响应码:" + responseCode); } }catch (Exception e) { // 捕获异常并打印堆栈信息 e.printStackTrace(); }finally { // 无论成功或失败,最后都关闭流和连接 // 关闭输入流 if (in != null) { try { in.close(); } catch (IOException e) { // 将IO异常转为运行时异常抛出 throw new RuntimeException(e); } } // 关闭输出流 if (out != null) { try { out.close(); } catch (IOException e) { // 将IO异常转为运行时异常抛出 throw new RuntimeException(e); } } // 关闭连接 if (connection != null) { connection.disconnect(); } } } }
执行结果
执行main 方法后的输出 Excel 结果如图
图片链接和视频链接都已经转存记录成功。
获取微博主页用户数据
在微博主页,我们可以看到如下的用户数据,包括 粉丝数、关注数、转/评/赞数据、主页描述、全部微博数等内容
数据分析
那么如何获取这些数据信息呢,我先尝试从 微博主页正文列表数据 进行分析,看是否可以从中获取到微博主页用户数据。
首先获取微博主页正文列表数据内容
将获取到的数据取出一个微博内容的完整的 Json 对象,保存为 .json 文件
打开该微博正文内容,可以看到如下微博主页用户数据内容
但是这里看到的并没有包含微博用户主页的 粉丝数、关注数、主页描述、全部微博数等内容。那么既然无法从微博正文列表数据内容的 user 属性中获取,但是页面上可以展示,那么猜测这里应该是跳转到微博用户主页之后通过 ajax 异步加载了微博用户相关信息,那么继续查看 【网络】中相关请求,发现了一个获取 微博用户信息的 ajax 请求 /ajax/profile/info?uid=1686546714
取出请求 /ajax/profile/info?uid=1686546714 浏览器请求中的 响应 内容,可以看到我们需要的微博主页用户信息都有的
到这里,关于如何获取微博主页用户数据的数据分析就结束了,那么下面我们开始来写代码实现获取对应的微博主页用户数据。
微博主页用户数据
这里我们重新创建一个 main 函数来单独的获取微博主页用户数据, DemoWeiBoInfo.java,整个类的代码比较简单,直接可以获取微博主页用户数据内容,最终执行的结果如图
编写代码
DemoWeiBoInfo.java 的源码如下
package com.ruoyi.web.controller.demo.controller; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.utils.StringUtils; import java.text.ParseException; public class DemoWeiBoInfo { /** * 获取微博主页账号信息 * @param args * @throws ParseException */ public static void main(String[] args) throws ParseException { // 获取微博账号主页信息 String url = "https://weibo.com/ajax/profile/info?uid=1686546714"; String cookie = "你的 Cookie"; System.out.println("微博账号信息查询开始"); HttpResponse response = HttpUtil.createGet(url) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36") .header("Cookie",cookie) .execute(); String body = response.body(); //System.out.println(body); if (StringUtils.isNotEmpty(body)) { JSONObject jsonObject = JSON.parseObject(body); //获取数据 data JSONObject data = jsonObject.getJSONObject("data"); // 获取 User 信息 JSONObject user = data.getJSONObject("user"); String id = user.getString("id"); //用户id String idstr = user.getString("idstr"); System.out.println("idstr:" + idstr); //用户名 String screen_name = user.getString("screen_name"); System.out.println("screen_name:" + screen_name); JSONObject status_total_counter = user.getJSONObject("status_total_counter"); // 转、评、赞 数量 String total_cnt_format = status_total_counter.getString("total_cnt_format"); System.out.println("total_cnt_format:" + total_cnt_format); String total_cnt = status_total_counter.getString("total_cnt"); System.out.println("total_cnt:" + total_cnt); //评论数量 String comment_cnt = status_total_counter.getString("comment_cnt"); System.out.println("comment_cnt:" + comment_cnt); // 转发数量 String repost_cnt = status_total_counter.getString("repost_cnt"); System.out.println("repost_cnt:" + repost_cnt); // 获赞数量 String like_cnt = status_total_counter.getString("like_cnt"); System.out.println("like_cnt:" + like_cnt); //用户头像 String avatar_large = user.getString("avatar_large"); System.out.println("avatar_large:" + avatar_large); //描述 String description = user.getString("description"); System.out.println("description:" + description); // 粉丝数量 String followers_count = user.getString("followers_count"); System.out.println("followers_count:" + followers_count); String followers_count_str = user.getString("followers_count_str"); System.out.println("followers_count_str:" + followers_count_str); // 关注数量 String friends_count = user.getString("friends_count"); System.out.println("friends_count:" + friends_count); //微博数量 String statuses_count = user.getString("statuses_count"); System.out.println("statuses_count:" + statuses_count); } System.out.println("微博账号信息查询结束"); } }
那么到这里,基于Java 爬取微博用户主页数据的任务就实现了。
写在最后
首先非常感谢大家认真看这篇 基于 Java 爬取微博数据 的内容,整篇文章内容比较多,感谢大家耐心看完。整体上在爬取数据需要用到的字段以上代码中都已经包含了,大家有需要的可以自行复制代码尝试。再次感谢大家的阅读。
注意点
对于请求 URL 请求头 Cookie 的获取,你可以选择游客 Cookie 或者登录账号后的 Cookie ,这里个人建议使用登录账号后从浏览器拿出的 Cookie,可以获取当前登录账号关注过的用户发布的所有微博数据。关于有效期,百度搜索关于微博登录账号后的 Cookie 有效期有 30天,个人实测了一次,大概十几天后失效,也有可能是本地测试时其他因素影响导致有效期没有30天,这里建议大家有时间的话也可以测一下。不建议使用游客 Cookie 的原因除了获取数据只能获取前 2 页数据外的因素,还有就是不知道具体游客 Cookie 的有效期,有不确定因素在里面,无法保证数据来源稳定了。
到这里可能有人会觉得从浏览器拿出登录后的 Cookie 操作显得不太高级,且比较笨拙,想要通过代码模拟微博登录从而获取 Cookie,这里个人给出的建议是微博目前的登录逻辑安全性较高,需要短信验证且有动态图验证,即使通过扫码登录或者账号登录,验证同样也是多重验证,且不说模拟登录的难度有多大,如果你能成功绕过登录或者模拟登录成功,那么感觉你离“破坏计算机信息系统罪”也不远了。因此个人建议保守一点,手动登录后从浏览器拿出 Cookie 放入爬虫代码中进行数据获取即可,无需费力不讨好的模拟登录。