1. 程式人生 > >部落格搬家系列(四)-爬取簡書文章

部落格搬家系列(四)-爬取簡書文章

部落格搬家系列(四)-爬取簡書文章

一.前情回顧 

 部落格搬家系列(一)-簡介:https://blog.csdn.net/rico_zhou/article/details/83619152

 部落格搬家系列(二)-爬取CSDN部落格:https://blog.csdn.net/rico_zhou/article/details/83619509

 部落格搬家系列(三)-爬取部落格園部落格:https://blog.csdn.net/rico_zhou/article/details/83619525

 部落格搬家系列(五)-爬取開源中國部落格:https://blog.csdn.net/rico_zhou/article/details/83619561

 部落格搬家系列(六)-爬取今日頭條文章:https://blog.csdn.net/rico_zhou/article/details/83619564

 部落格搬家系列(七)-本地WORD文件轉HTML:https://blog.csdn.net/rico_zhou/article/details/83619573

 部落格搬家系列(八)-總結:https://blog.csdn.net/rico_zhou/article/details/83619599
 

二.開幹(獲取文章URL集合)

爬取簡書的文章思路跟CSDN一樣,且下載圖片那一步更為簡單,任何header都不需要設定,同樣,我們找一個文章比較多的主頁為例分析原始碼,如

https://www.jianshu.com/u/b52ff888fd17  u後面的字串即為博主id,經我們下拉發現,簡書的文章列表載入方式是下拉自動載入,即滾動條到達一定程度時則js去請求後臺,那麼我們按下F12或者右擊審查元素,點選network檢視一下詳情

我們點選XHR(XMLHttpRequest)檢視請求如下

暫時沒啥有用資訊,此時我們緩慢滾動滑鼠讓其繼續載入文章列表,我們發現多了一條請求:?order_by=shared_at&page=2

猜測page=2即表示文章的頁數,將url複製到瀏覽器開啟,可以看到正是下一頁的文章,此時文章url的規律找到,接下來右擊檢視原始碼尋找包含文章url的標籤,可以發現,文章的url在class為note-list的ul標籤下的子標籤,class為content的div內,這就好辦了,程式碼如下:注意url不完整,需要補充拼接一下

/**
	 * @date Oct 17, 2018 12:30:46 PM
	 * @Desc
	 * @param blogMove
	 * @param oneUrl
	 * @return
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws FailingHttpStatusCodeException
	 */
	public void getJianShuArticleUrlList(Blogmove blogMove, String oneUrl, List<String> urlList)
			throws FailingHttpStatusCodeException, MalformedURLException, IOException {
		// 模擬瀏覽器操作
		// 建立WebClient
		WebClient webClient = new WebClient(BrowserVersion.CHROME);
		// 關閉css程式碼功能
		webClient.getOptions().setThrowExceptionOnScriptError(false);
		webClient.getOptions().setCssEnabled(false);
		// 如若有可能找不到檔案js則加上這句程式碼
		webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
		// 獲取第一級網頁html
		HtmlPage page = webClient.getPage(oneUrl);
		// System.out.println(page.asXml());
		Document doc = Jsoup.parse(page.asXml());
		Element pageMsg22 = doc.select("ul.note-list").first();
		if (pageMsg22 == null) {
			return;
		}
		Elements pageMsg = pageMsg22.select("div.content");
		Element linkNode;
		for (Element e : pageMsg) {
			linkNode = e.select("a.title").first();
			if (linkNode == null) {
				continue;
			}
			if (urlList.size() < blogMove.getMoveNum()) {
				urlList.add(BlogConstant.BLOG_BLOGMOVE_WEBSITE_BASEURL_JIANSHU + linkNode.attr("href"));
			} else {
				break;
			}
		}
		return;
	}

獲取url集合如下注意url不完整,需要補充拼接一下

三.開幹(獲取文章具體資訊)

同樣,我們還是開啟一篇博文,以爬蟲框架htmlunit整合springboot不相容的問題為例,使用Chrome開啟,我們可以看到一些基本資訊,如文章的型別為原創,標題,時間,作者,閱讀數,文章文字資訊,圖片資訊等

這裡需要特別注意一下的就是時間的獲取,簡書文章時間顯示並不是唯一,如他會將時間進行一些改變顯示,這裡需要注意一下,將獲取的時間反向解析一下,這裡不再過多講述。

同樣,右擊檢視原始碼找到對應的元素,然後獲取內容

程式碼如下:

/**
	 * @date Oct 17, 2018 12:46:52 PM
	 * @Desc 獲取詳細資訊
	 * @param blogMove
	 * @param url
	 * @return
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws FailingHttpStatusCodeException
	 */
	public Blogcontent getJianShuArticleMsg(Blogmove blogMove, String url, List<Blogcontent> bList)
			throws FailingHttpStatusCodeException, MalformedURLException, IOException {
		Blogcontent blogcontent = new Blogcontent();
		blogcontent.setArticleSource(blogMove.getMoveWebsiteId());
		// 模擬瀏覽器操作
		// 建立WebClient
		WebClient webClient = new WebClient(BrowserVersion.CHROME);
		// 關閉css程式碼功能
		webClient.getOptions().setThrowExceptionOnScriptError(false);
		webClient.getOptions().setCssEnabled(false);
		// 如若有可能找不到檔案js則加上這句程式碼
		webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
		// 獲取第一級網頁html
		HtmlPage page = webClient.getPage(url);

		Document doc = Jsoup.parse(page.asXml());
		// 獲取標題
		String title = BlogMoveJianShuUtils.getJianShuArticleTitle(doc);
		// 是否重複去掉
		if (blogMove.getMoveRemoveRepeat() == 0) {
			// 判斷是否重複
			if (BlogMoveCommonUtils.articleRepeat(bList, title)) {
				return null;
			}
		}
		blogcontent.setTitle(title);
		// 獲取作者
		blogcontent.setAuthor(BlogMoveJianShuUtils.getJianShuArticleAuthor(doc));
		// 獲取時間
		if (blogMove.getMoveUseOriginalTime() == 0) {
			blogcontent.setGtmCreate(BlogMoveJianShuUtils.getJianShuArticleTime(doc));
		} else {
			blogcontent.setGtmCreate(new Date());
		}
		blogcontent.setGtmModified(new Date());
		// 獲取型別
		blogcontent.setType(BlogMoveJianShuUtils.getJianShuArticleType(doc));
		// 獲取正文
		blogcontent.setContent(BlogMoveJianShuUtils.getJianShuArticleContent(doc, blogMove, blogcontent));

		// 設定其他
		blogcontent.setStatus(blogMove.getMoveBlogStatus());
		blogcontent.setBlogColumnName(blogMove.getMoveColumn());
		// 特殊處理
		blogcontent.setArticleEditor(blogMove.getMoveArticleEditor());
		blogcontent.setShowId(DateUtils.format(new Date(), DateUtils.YYYYMMDDHHMMSSSSS));
		blogcontent.setAllowComment(0);
		blogcontent.setAllowPing(0);
		blogcontent.setAllowDownload(0);
		blogcontent.setShowIntroduction(1);
		blogcontent.setIntroduction("");
		blogcontent.setPrivateArticle(1);

		return blogcontent;
	}

詳細資訊

	/**
	 * @date Oct 17, 2018 1:10:19 PM
	 * @Desc 獲取標題
	 * @param doc
	 * @return
	 */
	public static String getJianShuArticleTitle(Document doc) {
		// 標題
		Element pageMsg2 = doc.select("div.note").first().select("h1.title").first();
		return pageMsg2.html();
	}

	/**
	 * @date Oct 17, 2018 1:10:28 PM
	 * @Desc 獲取作者
	 * @param doc
	 * @return
	 */
	public static String getJianShuArticleAuthor(Document doc) {
		Element pageMsg2 = doc.select("div.note").first().select("span.name").first();
		return pageMsg2.text();
	}

	/**
	 * @date Oct 17, 2018 1:10:33 PM
	 * @Desc 獲取時間
	 * @param doc
	 * @return
	 */
	public static Date getJianShuArticleTime(Document doc) {
		Element pageMsg2 = doc.select("div.note").first().select("span.publish-time").first();
		String date = pageMsg2.html();
		// 注意有些格式不正確
		return DateUtils.formatStringDate(date, DateUtils.YYYY_MM_DD_HH_MM_SS2);
	}

	/**
	 * @date Oct 17, 2018 1:10:37 PM
	 * @Desc 獲取型別
	 * @param doc
	 * @return
	 */
	public static String getJianShuArticleType(Document doc) {
		// Element pageMsg2 =
		// doc.select("div.article-title-box").first().select("span.article-type").first();
		// if ("原".equals(pageMsg2.html())) {
		// return "原創";
		// } else if ("轉".equals(pageMsg2.html())) {
		// return "轉載";
		// } else if ("譯".equals(pageMsg2.html())) {
		// return "翻譯";
		// }
		return "原創";
	}

	/**
	 * @date Oct 17, 2018 1:10:41 PM
	 * @Desc 獲取正文
	 * @param doc
	 * @param object
	 * @param blogcontent
	 * @return
	 */
	public static String getJianShuArticleContent(Document doc, Blogmove blogMove, Blogcontent blogcontent) {
		Element pageMsg2 = doc.select("div.note").first().select("div.show-content").first();
		// 為了圖片顯示正常去掉一個元素
		pageMsg2.select("div.image-container-fill").remove();
		String content = pageMsg2.toString();
		String images;
		// 注意是否需要替換圖片
		if (blogMove.getMoveSaveImg() == 0) {
			// 儲存圖片到本地
			// 先獲取所有圖片連線,再按照每個連結下載圖片,最後替換原有連結
			// 先建立一個資料夾
			// 先建立一個臨時資料夾
			String blogFileName = String.valueOf(UUID.randomUUID());
			FileUtils.createFolder(FilePathConfig.getUploadBlogPath() + File.separator + blogFileName);
			blogcontent.setBlogFileName(blogFileName);
			// 匹配出所有連結
			List<String> imgList = BlogMoveCommonUtils.getArticleImgList2(content);
			// 下載並返回重新生成的imgurllist
			List<String> newImgList = BlogMoveCommonUtils.getArticleNewImgList(blogMove, imgList, blogFileName);
			// 拼接文章所有連結
			images = BlogMoveCommonUtils.getArticleImages(newImgList);
			blogcontent.setImages(images);
			// 替換所有連結按順序
			content = getJianShuNewArticleContent(content, imgList, newImgList);

		}

		return content;
	}

這裡的下載圖片也需要注意,當我測試文章時,發現有些圖片下載了有些則沒有,注意觀察一下原始碼發現,img標籤中的src並不是同步載入,即當我獲取文章正文時,可能圖片連結尚未載入到原始碼中,但是此img標籤中有另一個屬性可用,即data-original-src,顯然這是圖片的原路徑,那麼我們就根據這個路徑去下載圖片,然後將自己的圖片路徑更改到src屬性中,這樣就可全部下載顯示了

相關程式碼更改如下,獲取連結

/**
	 * @date Oct 17, 2018 1:10:41 PM
	 * @Desc 獲取連結
	 * @param doc
	 * @param object
	 * @param blogcontent
	 * @return
	 */
	public static List<String> getArticleImgList2(String content) {
		// 注意,當爬取簡書文章時,圖片非同步載入,有些並沒有完全加載出img,所以只能換方法
		if (content == null) {
			return null;
		}
		List<String> imgList = new ArrayList<String>();
		Document doc = Jsoup.parse(content);
		Elements imgTags = doc.select("img[data-original-src]");
		for (Element element : imgTags) {
			imgList.add("https:" + element.attr("data-original-src"));
		}
		return imgList;
	}

替換img連結程式碼

/**
	 * @date Oct 22, 2018 3:31:40 PM
	 * @Desc
	 * @param content
	 * @param imgList
	 * @param newImgList
	 * @return
	 */
	private static String getJianShuNewArticleContent(String content, List<String> imgList, List<String> newImgList) {
		Document doc = Jsoup.parse(content);
		Elements imgTags = doc.select("img[data-original-src]");
		if (imgList == null || imgList.size() < 1 || newImgList == null || newImgList.size() < 1 || imgTags == null
				|| "".equals(imgTags)) {
			return content;
		}
		for (int i = 0; i < imgTags.size(); i++) {
			imgTags.get(i).attr("src", newImgList.get(i));
		}
		return doc.body().toString();
	}

最後獲取的正文如下:

本人網站效果圖:

歡迎交流學習!

完整原始碼請見github: