用Jsoup爬取CSDN博客的一些信息

36 篇文章 4 订阅
6 篇文章 0 订阅


简单介绍

jsoup 爬取csdn博客的一些信息。项目已上传到github,有兴趣可以去看看:https://github.com/yansheng836/jsoup-crawl-csdn.

初衷

逛CSDN时,总会发现有些博客因为年代比较久远,而造成博客图片的丢失,阅读博客的体验极差;因为嵌入的图片中可能包含一些和内容联系比较紧密的信息,图片丢失会使博客断断续续、不易理解。

于是我突然想将csdn博客中的图片下载下来,然后进行备份:保存到本地、上传百度云、上传图床等,然后这个项目就诞生了。

主要内容

  1. 获得博客数量。
  2. 获得博客列表页数。
  3. 获得博客列表信息(网址,创建时间,标题),如:
Blog [url=https://blog.csdn.net/weixin_41287260/article/details/92185040, createTime=2019-06-15 20:45:43, title=阿里巴巴主导的“华山版《Java 开发手册》”简介]
  1. 获得博客的图片链接。
  2. 下载博客的所有图片,进行备份,防止博客中的图片丢失。

构建环境

jdk1.8+,maven3.0+,myeclipse2017

<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
		<dependency>
			<groupId>org.jsoup</groupId>
			<artifactId>jsoup</artifactId>
			<version>1.12.1</version>
		</dependency>

注:如果不使用maven框架进行构建,直接下载项目中的jsoup-1.12.1.jar或者到 https://mvnrepository.com/artifact/org.jsoup/jsoup 下载jar包,然后加到类路径中。

主要代码

0.说明:

  1. 以下代码是主要代码,不可独立运行,仅供参考。如需要全部代码可到github上查看:github仓库jsoup-crawl-csdn
  2. 如果你想要使用该项目备份自己的博客图片,你需要进行以下操作:
    2.1 按照github的README.md内容将项目导入到IDE。
    2.2 修改 /jsoup-crawl-csdn/src/main/java/xyz/yansheng/utility/BlogUtil.java 文件中的三个常量:
字符串常量名注释举例
PERSONAL_HOME_PAGE个人主页网址https://me.csdn.net/username
BLOG_HOME个人博客主页https://blog.csdn.net/username
PARENT_PATH保存博客图片的根路径E://2CSDN//

2.3 运行 /jsoup-crawl-csdn/src/main/java/xyz/yansheng/main/DownloadBlogsPictures.java 即可下载你的博客中的所有图片。

1.主要的工具函数

/**
 * @Title BlogUtil.java
 * @Package xyz.yansheng.utility
 * @Description TODO
 * @author yansheng
 * @date 2019-08-10 18:37:58
 * @version v1.0
 */
package xyz.yansheng.utility;

import java.io.IOException;
import java.util.ArrayList;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import xyz.yansheng.entity.Blog;

/**
 * <p>Title: 爬取博客的工具类</p>
 * <p>Description: </p>
 * <p>Company: </p>
 * @author yansheng
 * @date 2019-08-10 18:37:58
 * @version v1.0 
 */
public class BlogUtil {

	// 定义常量字符串
	/**  
	 * @Fields PERSONAL_HOME_PAGE : 个人主页网址:personal-home-page,如https://me.csdn.net/username
	 */
	public static final String PERSONAL_HOME_PAGE = "https://me.csdn.net/weixin_41287260";

	/**  
	 * @Fields BLOG_HOME : 个人博客主页:personal-blog-page,如https://blog.csdn.net/username
	 */
	public static final String BLOG_HOME = "https://blog.csdn.net/weixin_41287260";

	/**  
	 * @Fields PARENT_PATH : 保存博客图片的根路径
	 */
	public static final String PARENT_PATH = "E://2CSDN//";

	/**
	 * @Title getBlogCounter
	 * @author yansheng
	 * @version v1.0
	 * @date 2019-08-10 18:54:17
	 * @Description 获取某博主的csdn博客的数量
	 * @param personalHomePage 个人主页网址:personal-home-page,如https://me.csdn.net/username
	 * @return   
	 * int 博客数量
	 */
	public static int getBlogCounter(String personalHomePage) {

		// 1.获取文档对象
		Document doc = null;
		try {
			doc = Jsoup.connect(personalHomePage).get();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// 2.查找包含博客数量的元素
		Element countElement = doc.select("span.count").first();

		// 3.取出元素包含的文本(字符串),这里为博客数量
		String blogCount = countElement.text();

		return Integer.parseInt(blogCount);
	}

	/**
	 * @Title getBlogListPage
	 * @author yansheng
	 * @version v1.0
	 * @date 2019-08-10 18:58:07
	 * @Description 获取个人博客的页数,一页有20个博客;如果除不尽、有余数,整除博客页面+1
	 * @param blogCount 博客数量
	 * @return   
	 * int 博客页数
	 */
	public static int getBlogListPage(int blogCount) {

		// 向上取整,注意20.0,如果是20就是整数除法了,而不是浮点数除法
		int blogListPage = (int) Math.ceil(blogCount / 20.0);

		return blogListPage;
	}

	/**
	 * @Title getBlogs
	 * @author yansheng
	 * @version v1.1
	 * @date 2019-08-10 19:05:04
	 * @Description 返回博客列表
	 * @param blogListPage 博客列表页数
	 * @param blogHome 博客主页网址
	 * @return   
	 * ArrayList<Blog> 博客列表
	 */
	public static ArrayList<Blog> getBlogs(int blogListPage, String blogHome) {

		// 拼接博客列表页网址 
		// 如列表第二页为:https://blog.csdn.net/weixin_41287260/article/list/2
		final String articleListString = "/article/list/";

		// 用于存放博客列表,设置初始容量为:页面*20
		ArrayList<Blog> blogs = new ArrayList<Blog>(blogListPage * 20);

		// 定义变量:博客列表页的网址
		String blogListUrl = null;
		for (int i = 1, pageNum = blogListPage + 1; i < pageNum; i++) {
			// 拼接网址
			blogListUrl = blogHome + articleListString + Integer.toString(i);

			// 1. 获取文档对象
			Document doc = null;
			try {
				doc = Jsoup.connect(blogListUrl).get();
			} catch (IOException e) {
				e.printStackTrace();
			}
			//System.out.println(doc);

			// 2. 查找包含博客列表的元素
			Element articleList = doc.select("div.article-list").first();

			// 3. 查找每篇博客的元素
			// 注意这里因为class里面有空格,class="article-item-box csdn-tracking-statistics",
			// 相当于是两个class
			Elements blogInfo = articleList.select("div.article-item-box.csdn-tracking-statistics");

			for (int j = 0, size = blogInfo.size(); j < size; j++) {
				// 如果不含blogHome(https://blog.csdn.net/weixin_41287260),则移除(或者直接跳出该重循环)
				// 排除乱入的第一个网址 :https://blog.csdn.net/yoyo_liyy/article/details/82762601
				if (j == 0) {
					// blogInfo.remove(j);
					continue;
				}
				// 取出博客信息
				Element h4 = blogInfo.get(j).select("h4>a").first();
				String blogUrl = h4.attr("href");

				// 裁剪博客标题,前面有:原,空格
				String blogTitle = h4.text().substring(2);

				Element date = blogInfo.get(j).select("span.date").first();
				String blogDate = date.text();

				Blog blog = new Blog();
				blog.setUrl(blogUrl);
				blog.setCreateTime(blogDate);
				blog.setTitle(blogTitle);

				blogs.add(blog);
			}
		}

		return blogs;
	}

	/**
	 * @Title getBlogPictures
	 * @author yansheng
	 * @version v1.0
	 * @date 2019-08-10 22:22:34
	 * @Description 获取博客中的图片的链接
	 * @param blogUrl 博客的网址
	 * @return   
	 * ArrayList<String> 博客中的图片的链接
	 */
	public static ArrayList<String> getBlogPictures(String blogUrl) {

		ArrayList<String> picUrls = new ArrayList<String>();

		// 1. 获取文档对象
		Document doc = null;
		try {
			doc = Jsoup.connect(blogUrl).get();
		} catch (IOException e) {
			e.printStackTrace();
		}

		/*2. 先判断该文章是用富文本写的,还是用markdown写的,因为他们的图片的标签有些差别
		 * <article class="baidu_pl">
		 * 	 <!-- 富文本写的博客,注意不同之处在这里,类不同 -->
		 * 	<div id="content_views" class="htmledit_views" >
		 * 
		 * 	<!-- markdown写的博客,注意不同之处在这里 -->
		 * 	<div id="content_views" class="markdown_views prism-kimbie-light">
		 * 
		 */
		Element article = doc.selectFirst("article.baidu_pl");
		Element contentViews = article.selectFirst("div#content_views");
		String contentViewsClass = contentViews.attr("class");
		
		// 进行判断:如果类包含htmledit_views,说明是富文本编辑器写的;否则是markdown写的
		// 3.查找该篇博客中的所有图片
		String htmleditViews = "htmledit_views";
		Elements images = null;
		if (contentViewsClass.contains(htmleditViews)) {
			images = article.select("img.has");
		}else {
			images = article.select("img");
		}
		
		for (Element element : images) {
			String picUrl = element.attr("src");
			// 对特殊字符串(如下)进行裁剪
			picUrl = StringUtil.subPicUrl(picUrl);
			picUrls.add(picUrl);
		}
		return picUrls;
	}
}

2. 要特别注意下面这段代码

这段代码是查找博客中的图片的,这里是要区分博客是用富文本写的,还是用markdown写的,因为他们的图片的标签有些差别。(这个问题一开始我也没有注意到,是在后面备份博客图片时才发现的,已修复)

类型文章标识图片标签选择器
富文本<div id="content_views" class="htmledit_views" ><img class="has">select("img.has")
markdown<div id="content_views" class="markdown_views prism-kimbie-light"><img>select("img")

我们可以发现这两个标签虽然id一样,但是class不同,因此我们可以以此进行判断,如下:

		/*2. 先判断该文章是用富文本写的,还是用markdown写的,因为他们的图片的标签有些差别
		 * <article class="baidu_pl">
		 * 	 <!-- 富文本写的博客,注意不同之处在这里,类不同 -->
		 * 	<div id="content_views" class="htmledit_views" >
		 * 
		 * 	<!-- markdown写的博客,注意不同之处在这里 -->
		 * 	<div id="content_views" class="markdown_views prism-kimbie-light">
		 * 
		 */
		Element article = doc.selectFirst("article.baidu_pl");
		Element contentViews = article.selectFirst("div#content_views");
		String contentViewsClass = contentViews.attr("class");
		
		// 进行判断:如果类包含htmledit_views,说明是富文本编辑器写的;否则是markdown写的
		// 3.查找该篇博客中的所有图片
		String htmleditViews = "htmledit_views";
		Elements images = null;
		if (contentViewsClass.contains(htmleditViews)) {
			images = article.select("img.has");
		}else {
			images = article.select("img");
		}

3. 下载博客图片的主要代码

/**
 * @Title DownloadBlogsPictures.java
 * @Package xyz.yansheng.main
 * @Description TODO
 * @author yansheng
 * @date 2019-08-11 02:05:34
 * @version v1.0
 */
package xyz.yansheng.main;

import java.io.File;
import java.util.ArrayList;

import xyz.yansheng.entity.Blog;
import xyz.yansheng.utility.BlogUtil;
import xyz.yansheng.utility.FileUtil;
import xyz.yansheng.utility.StringUtil;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Company: </p>
 * @author yansheng
 * @date 2019-08-11 02:05:34
 * @version v1.0 
 */
public class DownloadBlogsPictures {

	/**
	 * @Title main
	 * @author yansheng
	 * @version v1.2
	 * @date 2019-08-11 02:05:34
	 * @Description 备份博客的图片
	 */
	public static void main(String[] args) {
		// 1.获取博客数量
		int blogCount = BlogUtil.getBlogCounter(BlogUtil.PERSONAL_HOME_PAGE);

		// 2.获取博客列表页数
		int blogListPage = BlogUtil.getBlogListPage(blogCount);

		// 3.获取博客列表
		ArrayList<Blog> blogs = BlogUtil.getBlogs(blogListPage, BlogUtil.BLOG_HOME);

		ArrayList<String> picUrls = new ArrayList<String>();

		// 首先保证保存图片的父目录是存在的,如果不存在就创建
		File parentPath = new File(BlogUtil.PARENT_PATH);
		if (!parentPath.exists()) {
			if (parentPath.mkdirs()) {
				System.out.println("创建保存图片的跟目录:'" + parentPath + "' 成功。");
			}else {
				System.err.println("创建保存图片的跟目录:'" + parentPath + "' 失败。");
				return ;
			}
		} else {
			System.out.println("保存图片的跟目录:'" + parentPath + "' 已存在。");
		}

		// 下载所有博客的图片
		for (int i = 0, blogNo = blogs.size(); i < blogNo; i++) {
			Blog blog = blogs.get(i);
			System.out.print("第" + Integer.toString(i + 1) + "篇博客-图片");

			// 创建文件夹,以博客的创建时间+博客标题为文件夹名,如:2018-10-15-html表单提交问题
			String blogTitle = blog.getTitle();
			// 替换文件名中的特殊字符,使能够成功创建该文件夹
			blogTitle = StringUtil.replaceSpecialCharacters(blogTitle);

			String dirPath = BlogUtil.PARENT_PATH + blog.getCreateTime().substring(0, 10) + "-" + blogTitle
					+ "//";

			// 判断创建文件夹的返回值,如果是0,即已存在,则认为已下载该博客的图片,跳出该循环;
			// 如果是-1,则文件夹创建失败,故路径错误,不可能成功保存图片,也直接跳出循环。
			int result = FileUtil.mkdir(dirPath);
			if (result == 0 || result == -1) {
				// continue;
			}

			// 获取该博客的所有图片的url列表
			picUrls = BlogUtil.getBlogPictures(blog.getUrl());

			// 下载图片
			for (int j = 0, picNo = picUrls.size(); j < picNo; j++) {
				String picUrl = picUrls.get(j);
				// 暂时注释,用于测试图片保存的文件名
				FileUtil.downloadPic(picUrl, dirPath);
				if (j == picNo - 1) {
					System.out.println(" -已下载" + picNo + "张图片\n");
				}
				// System.out.println("图片文件名:" + picUrl);
			}
		}
		// 结束语
		System.out.println("\n****已成功下载博主:" + BlogUtil.BLOG_HOME + "所有博客中的图片!****");
	}
}

4.实现效果

  • 控制台输出:
    (1)下图是用富文本编辑器写的博客,因为之前我已经下载过来,使用显示已存在。
    在这里插入图片描述
    (2)下图是用markdown编辑器写的博客,因为之前我没有下载,所以会下载到本地。
    在这里插入图片描述
  • 本地根目录(部分截图)
    在这里插入图片描述
  • 次级目录(部分截图)
    在这里插入图片描述

资源获取

  1. github:地址:https://github.com/yansheng836/jsoup-crawl-csdn ,下面方法2选1
  1. 百度云

链接:https://pan.baidu.com/s/1Fe4Z6qoFGwUWtag9-z-RSQ
提取码:vmt2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值