博客搬家系列(四)-爬取简书文章

梦里梦外; 2022-04-22 01:32 327阅读 0赞

博客搬家系列(四)-爬取简书文章

一.前情回顾

博客搬家系列(一)-简介: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查看一下详情

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70

我们点击XHR(XMLHttpRequest)查看请求如下

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70 1

暂时没啥有用信息,此时我们缓慢滚动鼠标让其继续加载文章列表,我们发现多了一条请求:?order_by=shared_at&page=2

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70 2

猜测page=2即表示文章的页数,将url复制到浏览器打开,可以看到正是下一页的文章,此时文章url的规律找到,接下来右击查看源码寻找包含文章url的标签,可以发现,文章的url在class为note-list的ul标签下的子标签,class为content的div内,这就好办了,代码如下:注意url不完整,需要补充拼接一下

  1. /**
  2. * @date Oct 17, 2018 12:30:46 PM
  3. * @Desc
  4. * @param blogMove
  5. * @param oneUrl
  6. * @return
  7. * @throws IOException
  8. * @throws MalformedURLException
  9. * @throws FailingHttpStatusCodeException
  10. */
  11. public void getJianShuArticleUrlList(Blogmove blogMove, String oneUrl, List<String> urlList)
  12. throws FailingHttpStatusCodeException, MalformedURLException, IOException {
  13. // 模拟浏览器操作
  14. // 创建WebClient
  15. WebClient webClient = new WebClient(BrowserVersion.CHROME);
  16. // 关闭css代码功能
  17. webClient.getOptions().setThrowExceptionOnScriptError(false);
  18. webClient.getOptions().setCssEnabled(false);
  19. // 如若有可能找不到文件js则加上这句代码
  20. webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
  21. // 获取第一级网页html
  22. HtmlPage page = webClient.getPage(oneUrl);
  23. // System.out.println(page.asXml());
  24. Document doc = Jsoup.parse(page.asXml());
  25. Element pageMsg22 = doc.select("ul.note-list").first();
  26. if (pageMsg22 == null) {
  27. return;
  28. }
  29. Elements pageMsg = pageMsg22.select("div.content");
  30. Element linkNode;
  31. for (Element e : pageMsg) {
  32. linkNode = e.select("a.title").first();
  33. if (linkNode == null) {
  34. continue;
  35. }
  36. if (urlList.size() < blogMove.getMoveNum()) {
  37. urlList.add(BlogConstant.BLOG_BLOGMOVE_WEBSITE_BASEURL_JIANSHU + linkNode.attr("href"));
  38. } else {
  39. break;
  40. }
  41. }
  42. return;
  43. }

获取url集合如下20181101162740936.png注意url不完整,需要补充拼接一下

三.开干(获取文章具体信息)

同样,我们还是打开一篇博文,以爬虫框架htmlunit整合springboot不兼容的问题为例,使用Chrome打开,我们可以看到一些基本信息,如文章的类型为原创,标题,时间,作者,阅读数,文章文字信息,图片信息等

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70 3

这里需要特别注意一下的就是时间的获取,简书文章时间显示并不是唯一,如20181101163033595.png他会将时间进行一些改变显示,这里需要注意一下,将获取的时间反向解析一下,这里不再过多讲述。

同样,右击查看源码找到对应的元素,然后获取内容

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70 4

代码如下:

  1. /**
  2. * @date Oct 17, 2018 12:46:52 PM
  3. * @Desc 获取详细信息
  4. * @param blogMove
  5. * @param url
  6. * @return
  7. * @throws IOException
  8. * @throws MalformedURLException
  9. * @throws FailingHttpStatusCodeException
  10. */
  11. public Blogcontent getJianShuArticleMsg(Blogmove blogMove, String url, List<Blogcontent> bList)
  12. throws FailingHttpStatusCodeException, MalformedURLException, IOException {
  13. Blogcontent blogcontent = new Blogcontent();
  14. blogcontent.setArticleSource(blogMove.getMoveWebsiteId());
  15. // 模拟浏览器操作
  16. // 创建WebClient
  17. WebClient webClient = new WebClient(BrowserVersion.CHROME);
  18. // 关闭css代码功能
  19. webClient.getOptions().setThrowExceptionOnScriptError(false);
  20. webClient.getOptions().setCssEnabled(false);
  21. // 如若有可能找不到文件js则加上这句代码
  22. webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
  23. // 获取第一级网页html
  24. HtmlPage page = webClient.getPage(url);
  25. Document doc = Jsoup.parse(page.asXml());
  26. // 获取标题
  27. String title = BlogMoveJianShuUtils.getJianShuArticleTitle(doc);
  28. // 是否重复去掉
  29. if (blogMove.getMoveRemoveRepeat() == 0) {
  30. // 判断是否重复
  31. if (BlogMoveCommonUtils.articleRepeat(bList, title)) {
  32. return null;
  33. }
  34. }
  35. blogcontent.setTitle(title);
  36. // 获取作者
  37. blogcontent.setAuthor(BlogMoveJianShuUtils.getJianShuArticleAuthor(doc));
  38. // 获取时间
  39. if (blogMove.getMoveUseOriginalTime() == 0) {
  40. blogcontent.setGtmCreate(BlogMoveJianShuUtils.getJianShuArticleTime(doc));
  41. } else {
  42. blogcontent.setGtmCreate(new Date());
  43. }
  44. blogcontent.setGtmModified(new Date());
  45. // 获取类型
  46. blogcontent.setType(BlogMoveJianShuUtils.getJianShuArticleType(doc));
  47. // 获取正文
  48. blogcontent.setContent(BlogMoveJianShuUtils.getJianShuArticleContent(doc, blogMove, blogcontent));
  49. // 设置其他
  50. blogcontent.setStatus(blogMove.getMoveBlogStatus());
  51. blogcontent.setBlogColumnName(blogMove.getMoveColumn());
  52. // 特殊处理
  53. blogcontent.setArticleEditor(blogMove.getMoveArticleEditor());
  54. blogcontent.setShowId(DateUtils.format(new Date(), DateUtils.YYYYMMDDHHMMSSSSS));
  55. blogcontent.setAllowComment(0);
  56. blogcontent.setAllowPing(0);
  57. blogcontent.setAllowDownload(0);
  58. blogcontent.setShowIntroduction(1);
  59. blogcontent.setIntroduction("");
  60. blogcontent.setPrivateArticle(1);
  61. return blogcontent;
  62. }

详细信息

  1. /**
  2. * @date Oct 17, 2018 1:10:19 PM
  3. * @Desc 获取标题
  4. * @param doc
  5. * @return
  6. */
  7. public static String getJianShuArticleTitle(Document doc) {
  8. // 标题
  9. Element pageMsg2 = doc.select("div.note").first().select("h1.title").first();
  10. return pageMsg2.html();
  11. }
  12. /**
  13. * @date Oct 17, 2018 1:10:28 PM
  14. * @Desc 获取作者
  15. * @param doc
  16. * @return
  17. */
  18. public static String getJianShuArticleAuthor(Document doc) {
  19. Element pageMsg2 = doc.select("div.note").first().select("span.name").first();
  20. return pageMsg2.text();
  21. }
  22. /**
  23. * @date Oct 17, 2018 1:10:33 PM
  24. * @Desc 获取时间
  25. * @param doc
  26. * @return
  27. */
  28. public static Date getJianShuArticleTime(Document doc) {
  29. Element pageMsg2 = doc.select("div.note").first().select("span.publish-time").first();
  30. String date = pageMsg2.html();
  31. // 注意有些格式不正确
  32. return DateUtils.formatStringDate(date, DateUtils.YYYY_MM_DD_HH_MM_SS2);
  33. }
  34. /**
  35. * @date Oct 17, 2018 1:10:37 PM
  36. * @Desc 获取类型
  37. * @param doc
  38. * @return
  39. */
  40. public static String getJianShuArticleType(Document doc) {
  41. // Element pageMsg2 =
  42. // doc.select("div.article-title-box").first().select("span.article-type").first();
  43. // if ("原".equals(pageMsg2.html())) {
  44. // return "原创";
  45. // } else if ("转".equals(pageMsg2.html())) {
  46. // return "转载";
  47. // } else if ("译".equals(pageMsg2.html())) {
  48. // return "翻译";
  49. // }
  50. return "原创";
  51. }
  52. /**
  53. * @date Oct 17, 2018 1:10:41 PM
  54. * @Desc 获取正文
  55. * @param doc
  56. * @param object
  57. * @param blogcontent
  58. * @return
  59. */
  60. public static String getJianShuArticleContent(Document doc, Blogmove blogMove, Blogcontent blogcontent) {
  61. Element pageMsg2 = doc.select("div.note").first().select("div.show-content").first();
  62. // 为了图片显示正常去掉一个元素
  63. pageMsg2.select("div.image-container-fill").remove();
  64. String content = pageMsg2.toString();
  65. String images;
  66. // 注意是否需要替换图片
  67. if (blogMove.getMoveSaveImg() == 0) {
  68. // 保存图片到本地
  69. // 先获取所有图片连接,再按照每个链接下载图片,最后替换原有链接
  70. // 先创建一个文件夹
  71. // 先创建一个临时文件夹
  72. String blogFileName = String.valueOf(UUID.randomUUID());
  73. FileUtils.createFolder(FilePathConfig.getUploadBlogPath() + File.separator + blogFileName);
  74. blogcontent.setBlogFileName(blogFileName);
  75. // 匹配出所有链接
  76. List<String> imgList = BlogMoveCommonUtils.getArticleImgList2(content);
  77. // 下载并返回重新生成的imgurllist
  78. List<String> newImgList = BlogMoveCommonUtils.getArticleNewImgList(blogMove, imgList, blogFileName);
  79. // 拼接文章所有链接
  80. images = BlogMoveCommonUtils.getArticleImages(newImgList);
  81. blogcontent.setImages(images);
  82. // 替换所有链接按顺序
  83. content = getJianShuNewArticleContent(content, imgList, newImgList);
  84. }
  85. return content;
  86. }

这里的下载图片也需要注意,当我测试文章时,发现有些图片下载了有些则没有,注意观察一下源码发现,img标签中的src并不是同步加载,即当我获取文章正文时,可能图片链接尚未加载到源码中,但是此img标签中有另一个属性可用,即data-original-src,显然这是图片的原路径,那么我们就根据这个路径去下载图片,然后将自己的图片路径更改到src属性中,这样就可全部下载显示了

20181101163830231.png

相关代码更改如下,获取链接

  1. /**
  2. * @date Oct 17, 2018 1:10:41 PM
  3. * @Desc 获取链接
  4. * @param doc
  5. * @param object
  6. * @param blogcontent
  7. * @return
  8. */
  9. public static List<String> getArticleImgList2(String content) {
  10. // 注意,当爬取简书文章时,图片异步加载,有些并没有完全加载出img,所以只能换方法
  11. if (content == null) {
  12. return null;
  13. }
  14. List<String> imgList = new ArrayList<String>();
  15. Document doc = Jsoup.parse(content);
  16. Elements imgTags = doc.select("img[data-original-src]");
  17. for (Element element : imgTags) {
  18. imgList.add("https:" + element.attr("data-original-src"));
  19. }
  20. return imgList;
  21. }

替换img链接代码

  1. /**
  2. * @date Oct 22, 2018 3:31:40 PM
  3. * @Desc
  4. * @param content
  5. * @param imgList
  6. * @param newImgList
  7. * @return
  8. */
  9. private static String getJianShuNewArticleContent(String content, List<String> imgList, List<String> newImgList) {
  10. Document doc = Jsoup.parse(content);
  11. Elements imgTags = doc.select("img[data-original-src]");
  12. if (imgList == null || imgList.size() < 1 || newImgList == null || newImgList.size() < 1 || imgTags == null
  13. || "".equals(imgTags)) {
  14. return content;
  15. }
  16. for (int i = 0; i < imgTags.size(); i++) {
  17. imgTags.get(i).attr("src", newImgList.get(i));
  18. }
  19. return doc.body().toString();
  20. }

最后获取的正文如下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70 5

本人网站效果图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3JpY29femhvdQ_size_16_color_FFFFFF_t_70 6

欢迎交流学习!

完整源码请见github:https://github.com/ricozhou/blogmove​​​​​​​

发表评论

表情:
评论列表 (有 0 条评论,327人围观)

还没有评论,来说两句吧...

相关阅读

    相关 搬家系列(一)-简介

    这个功能思来想去想了很久,终于实现了基本功能,自己基于别人的后台权限管理系统写了一个博客系统,其实博客系统只是一小部分,但今天只讲博客部分,其他详见: RZSpider详见: