将csdn的博客爬取到本地并输出为jekyll可解析的markdown格式,同时保存博客的图片到本地

本文主要是介绍将csdn的博客爬取到本地并输出为jekyll可解析的markdown格式,同时保存博客的图片到本地,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在Github Pages搭建个人博客时利用 Jekyll 生成站点,Jekyll是一个静态站点生成器,可以根据Markdown文件自动生成静态的html文件。且Github Pages 支持托管jekyll。

因此我只要在本地编写符合Jekyll规范的Markdown文件,上传到Github上,Github Pages就会自动生成并托管整个网站。

我想把之前我CSDN上写的博客备份到本地,同时又可以上传到Github Pages搭建的个人博客,那么一个大问题就来了,如何将自己CSDN的博客批量导出并输出为jekyll可解析的markdown文章格式,并且将博客中的图片也保存到本地。

所以用python写了该脚本用于实现所需要的功能。

代码实现

爬取csdn的博客并批量输出为jekyll可以直接解析的markdown格式,对于每一篇文章,我们重点关注以下信息:

  • 标题(title)
  • 正文
  • 发表时间(date)
  • 所属类别(categories)
  • 对应标签(tags)

括号中的英文就是jekyll下博客所需要的文件头格式标准,我们只需要将csdn中每一篇文章的上述属性爬取下来并以特定的格式写入文件即可。

所需要的包

import os           
import sys
reload(sys)
sys.setdefaultencoding("utf-8")# 若要保存print出来的日志内容则加入下面两行
# mylog="mylog.log"
# sys.stdout = open(mylog,'w')from lxml import etree
import requests
import html2textfrom bs4 import BeautifulSoup
import codecs
import reimport imghdr   # 内置模块imghdr可以用来判断图片的真实类型。
import shutil   # 用于清空指定文件夹下所有文件

request子程序

首先我们应该针对csdn的博客系统写一个通用的request函数(方法):

# request子程序
def request_get(url):session = requests.Session()headers = {'User-Agent': 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'}response = requests.get(url, headers=headers, timeout=5)return response

这里的headers里面的信息是通过查看页面的审查元素信息找到的。

文章地址爬取子程序

该子程序依此爬取CSDN账号下的每一篇文章的地址,地址中包含id,然后再调用爬取单篇博客文章的子程序。

username为csdn的地址中的用户名。注意看每一行的注释。

# 文章地址爬取子程序
def start_spider(username):# 把下面这个base_url换成你csdn的地址base_url = 'https://blog.csdn.net/' + username + '/'second_url = base_url + 'article/list/'# 从第一页开始爬取start_url = second_url + '1'number = 1      # 记录当前是第几个article_listcount = 0       # 记录当前是第几篇文章# 开始爬取第一个article_list,返回信息在html中html = request_get(start_url)# 这个循环是对博客的article_list页面的循环while html.status_code == 200:          # 200说明request_get完成,这是因为http协议里面定义的状态码# 获取下一页的 urlselector = etree.HTML(html.text)# cur_article_list_page[0]就是当前article_list页面中的文章的listcur_article_list_page = selector.xpath('//*[@id="mainBox"]/main/div[2]')d = cur_article_list_page[0].xpath('//*[@id="mainBox"]/main/div[2]/div[2]/h4/a')l = cur_article_list_page[0].findall('data-articleid')# 这个循环是对你每一个article_list中的那些文章的循环for elem in cur_article_list_page[0]:item_content = elem.attrib# 通过对比拿到的数据和网页中的有效数据发现返回每一个article_list中的list都有一两个多余元素,每个多余元素都有style属性,利用这一特点进行过滤if item_content.has_key('style'):continueelse:if item_content.has_key('data-articleid'):# 拿到文章对应的articleidarticleid = item_content['data-articleid']# 用于打印进度count += 1print("\n 找到第" + str(count) + "篇博客,正在处理...")# 爬取单篇文章CrawlingItemBlog(base_url, articleid)# 进行下一article_list的爬取number += 1next_url = second_url + str(number)html = request_get(next_url)

爬取单篇博客文章的子程序

按照拿到的每一个文章地址中的id对单篇文章进行爬取,并输出jekyll可解析的markdown文章格式,同时抓取博客中的图片保存到本地。注意看每一行的注释。

jekyll中markdown文件的头格式如下。

    ---layout:     posttitle:      ""date:       2018-03-08 12:41:47author:     "Nick"header-img: "img/post-bg-2015.jpg"catalog: truetags:- 电脑技巧---
# 爬取单篇博客文章的子程序
def CrawlingItemBlog(base_url, id):second_url = base_url + 'article/details/'url = second_url + id# 发送request请求并接受返回值item_html = request_get(url)if item_html.status_code == 200:    # 200说明request_get完成,这是因为http协议里面定义的状态码'''需要的信息:1:标题2:markdown内容3:发表日期4:标签5:类别jekyll中markdown文件的头格式---layout:     posttitle:      ""date:       2018-03-08 12:41:47author:     "Nick"header-img: "img/post-bg-2015.jpg"catalog: truetags:- 电脑技巧---'''# 利用BeautifulSoup解析返回的htmlsoup = BeautifulSoup(item_html.text, "lxml")# 筛选出博客正文那一部分htmlc = soup.find(id="content_views")# 标题title_article = soup.find(attrs={'class': 'title-article'})# 这里是将标题作为最后存储的文件名file_name = title_article.get_text()print(" 该篇博客标题为:" + file_name)title_article = title_article.prettify()# 设置jekyll格式博客开头的格式(title)jekyll_title = 'title:   ' + file_name + '\n'# 文章的categoriesjekyll_categories = ''# 有可能出现这篇文章没有categories的情况try:jekyll_categories = soup.find(attrs={'class': 'tags-box space'}).find(attrs={'class': 'tag-link'}).get_text()except Exception:passif jekyll_categories == '':passelse:# 去除拿到的str中的'\t'jekyll_categories = jekyll_categories.replace('\t', '')jekyll_categories = 'categories:\n' + '- ' + jekyll_categories + '\n'# 获取文章发表时间time = soup.find(attrs={'class': 'time'}).get_text()s_time1 = time.split('年')year = s_time1[0]s_time2 = s_time1[1].split('月')month = s_time2[0]s_time3 = s_time2[1].split('日')day = s_time3[0]minite = s_time3[1].strip()jekyll_date = 'date:   ' + year + '-' + month + '-' + day + ' ' + minite + '\n'jekyll_tags = ''# 获取tags标签tags = ''try:tags = soup.find(attrs={'class': 'tags-box artic-tag-box'}).get_text()except Exception:passif tags == '':passelse:tags = tags.split('\n')tags = tags[2]tags = tags.replace('\t', ' ')tags = tags.split(' ')jekyll_tags = 'tags:\n'for tag in tags:if tag == '':continueelse:jekyll_tags = jekyll_tags + '- ' + tag + '\n'# 将html转化为markdowntext_maker = html2text.HTML2Text()text_maker.bypass_tables = Falsetext = text_maker.handle(c.prettify())# 通过html2text转换得到的markdwon文本中'img-'后面会有一个换行符,导致图片链接不正确# 更正转换得到的markdown文件中'img-\n'为'img-',即删除'img-'后面的换行符text_utf8 = text.encode('utf-8')text_utf8 = text_utf8.replace('-\n', '-')text_utf8 = text_utf8.replace('\n]', ']')text_utf8 = text_utf8.replace('\n/', '/')text_utf8_right = text_utf8.replace('https', 'http')# text_utf8_right = text_utf8.replace('?x-oss-\n', '?x-oss-')# print(text_utf8_right)text_utf8_right = text_utf8_right.replace('(//img-blog', '(http://img-blog')# 有的文章名字特殊,会新建文件失败try:# 写入文件md_file_name = './_posts/' + year + '-' + month + '-' + day + '-' + file_name + '.markdown'f = codecs.open(md_file_name, 'w', encoding='utf-8')jekyll_str = '---\n' + 'layout:  post\n' + jekyll_title + jekyll_date + 'author:  "唐传林"\nheader-img: "img/post-bg-2015.jpg"\ncatalog:   false\n' + jekyll_categories + jekyll_tags + '\n---\n'f.write(jekyll_str)f.write(text_utf8_right)f.close()except Exception:print(' 写入文件 ' + file_name + ' 出错. ')# 若不爬取图片保存下来,则该CrawlingItemBlog子程序中以下这一小段到return True之前可以删除或者注释,可提高爬取博客的速度# 爬取每篇博客的图片保存下来是为了给博客做备份,万一哪天csdn挂了# 获取当前该篇博客中所有图片的地址all_img = c.find_all('img')# print(all_img)k = 1 # 当前该篇博客的图片序号,从1开始# 遍历每一张图片并保存下来for img in all_img:     # 能进入该循环则说明该篇博客中有图片# 若该篇博客中有图片,则以该篇博客的title创建文件夹,if k == 1:folder_path = './imgs/' + year + '-' + month + '-' + day + '-' + file_namemkdir(folder_path)# <class 'bs4.element.Tag'>转stringimg_str = str(img)'''csdn博客页面中 img 地址有以下6种情况:1、<img alt="在这里插入图片描述" src="https://img-blog.csdn.net/20180403152931142?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RhbmdfQ2h1YW5saW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"/>2、<img alt="在这里插入图片描述" src="https://img-blog.csdnimg.cn/20190208181833408.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RhbmdfQ2h1YW5saW4=,size_16,color_FFFFFF,t_70"/>3、<img alt="在这里插入图片描述" src="https://img-blog.csdnimg.cn/2019011319584747.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RhbmdfQ2h1YW5saW4=,size_16,color_FFFFFF,t_70"/>4、<img alt="在这里插入图片描述" src="https://img-blog.csdnimg.cn/20190214214731928.jpg"/>5、<img alt="在这里插入图片描述" src="https://img-blog.csdnimg.cn/20190113200938989.gif"/>6、<img alt="在这里插入图片描述" src="https://img-blog.csdnimg.cn/20190108161409240.png"/>为了去除csdn图片的水印,图片地址?后面的东西不要,则获得的图片是无水印的'''# re.findall中的参数需要为string,正则表达式搞了好久才搞对,在notepad++里面测试没问题的在python下有问题,img_url = re.findall(r"(?=img-blog).*?(?:\?|jpg|gif|png)", img_str)# img_url = re.findall(r"(?=img-blog).*?(?=\?)", img_str)     # 该正则表达式只能找到第1、2、3种img地址的情况# print('img_str: ' + img_str)# print('img_url: ' + str(img_url))img_url_str = "".join(img_url)      # list转string# print('img_url_str' + img_url_str)try:# 下载图片暂时存盘为jpg格式ir = requests.get('https://' + img_url_str)img_name =  'temp.jpg'  # 临时文件名if ir.status_code == 200:       # 200说明request_get完成,这是因为http协议里面定义的状态码open(img_name , 'wb').write(ir.content)     # 存盘# 判断图片真实格式并重命名改后缀为真实格式img_real_name = folder_path + "/" + year + '-' + month + '-' + day + '-' + file_name + "_图" + str(k) +  "." + imghdr.what(img_name)        # imghdr.what(img_name)用来判断图片真实格式os.rename(img_name, img_real_name)      # os.rename对图片进行重命名k = k + 1       # 当前该篇博客的图片序号递增1except Exception:print(" 获取博客“" + file_name + "”的图片时出现错误...\t\t正则表达式之前图片地址:http://" + img_str)return Trueelse:return False

新建子文件夹程序

该子程序用于新建保存markdown文件和图片文件的文件夹。

# 新建文件夹子程序
def mkdir(path):folder = os.path.exists(path)if not folder:  # 判断是否存在文件夹如果不存在则创建为文件夹os.makedirs(path)  # makedirs 创建文件时如果路径不存在会创建这个路径return Trueelse:return False

主程序

if __name__ == "__main__":username = 'Tang_Chuanlin'        # CSDN 个人主页地址中的用户名,例如https://blog.csdn.net/Tang_Chuanlin中的Tang_Chuanlinif os.path.exists('./imgs/'):      # 判断当前路径下是否存在"imgs"文件夹shutil.rmtree('./imgs/')       # 若存在,则删除该文件夹,目的是删除之前爬取获得的图片mkdir('./imgs/')                   # "imgs"新建文件夹if os.path.exists('./_posts/'):      # 判断当前路径下是否存在"_posts"文件夹shutil.rmtree('./_posts/')       # 若存在,则删除该文件夹,目的是删除之前爬取获得的markdown文件mkdir('./_posts/')                   # "_posts"新建文件夹print(" 开始爬取 " + username + " 的 CSDN 博客... ")start_spider(username)              # 开始爬取print('successful!')                # 因为是死循环一直爬取,所以一般需要手动停止

项目地址:

CsdnBlogToJekyll,?欢迎Fork!欢迎 star !

使用方法

  • 在pycharm下运行,直接将工程下载到本地,将:

if __name__ == "__main__":username = 'Tang_Chuanlin'        # CSDN 个人主页地址中的用户名,例如https://blog.csdn.net/Tang_Chuanlin中的Tang_Chuanlinif os.path.exists('./imgs/'):      # 判断当前路径下是否存在"imgs"文件夹shutil.rmtree('./imgs/')       # 若存在,则删除该文件夹,目的是删除之前爬取获得的图片mkdir('./imgs/')                   # "imgs"新建文件夹if os.path.exists('./_posts/'):      # 判断当前路径下是否存在"_posts"文件夹shutil.rmtree('./_posts/')       # 若存在,则删除该文件夹,目的是删除之前爬取获得的markdown文件mkdir('./_posts/')                   # "_posts"新建文件夹print(" 开始爬取 " + username + " 的 CSDN 博客... ")start_spider(username)              # 开始爬取print('successful!')                # 因为是死循环一直爬取,所以一般需要手动停止

中的username换成自己csdn的用户名,然后在pycharm下运行项目即可,因为是死循环一直爬取,所以一般需要手动停止。

项目运行完毕后markdown格式的文章会在“_posts”文件夹下,每篇博客的图片保存在“imgs”文件夹下。
在这里插入图片描述

  • 在windows command窗口下直接运行时,需要先执行以下两条命令,将windows command窗口的编码改为utf-8。

    chcp 65001
    
    set PYTHONIOENCODING=utf-8
    

    然后再执行以下命令运行python脚本即可。

    python export_csdn_markdown.py
    

在这里插入图片描述

gif动图中抓取过程中有一篇博客“解决You are using pip version 9.0.1, however version 9.0.3 is available. You should consider upgrading”保存图片时发错错误,可能是因为该篇博客标题过长,然后新建出的文件夹文件名过长,超出windows文件夹名长度的限制,所以发生了图片保存时的错误。

结语

至此就完成了将csdn的博客爬取到本地并输出为jekyll可解析的markdown格式,同时保存下了每一篇博客的图片到本地,然后将导出的markdown格式的文章(默认在“_posts”目录下)放在jekyll博客目录的_posts文件夹下即可。

需要注意的问题

1、jekyll中post的markdown文件名格式如下:

2019-02-13-Beautiful Soup 4.2.0 官方中文文档.markdown

2、jekyll中post的markdown文件头格式如下:
---    layout:		post
title: 		错误 Unable to locate package python-pip
date: 		2019-02-14 15:10:56
author:		"唐传林"
header-img: 	"img/post-bg-2015.jpg"
catalog:	 true
tags:
- python
---    

tags等关键字后面的冒号必须为英文冒号,为中文冒号jekyll编译生成的页面中该篇文章会没有文章标题、作者显示不对、日期不对等问题。
tags下的标签不要添加空格或table。
title下的字段不能带有英文或者中文冒号,title字段中带有中文冒号jekyll编译生成的页面中该篇文章会没有文章标题、作者显示不对、日期不对等问题。
title中可带有单引号。

3、jekyll中post的markdown文件需要使用utf-8无BOM的编码,不能使用utf-8 BOM的编码。

爬虫保存的markdown文件默认为utf-8无BOM的编码,不用进行转换。csdn在线markdown编辑器导出的markdown文件默认为utf-8 BOM的编码,需要用notepad++进行转换一下。

4、爬虫保存的markdown文件中的图片链接

有些图片链接会换行,程序中已经对可能换行的图片链接进行了处理,若jekyll编译生成的页面中文章的图片不正常显示,则需要人工检查图片链接是否正常。

5、图片链接需要改为http协议

爬虫保存的markdown文件中的图片链接有些为http协议的,jekyll生成的页面中这种图片有些会显示不正常,所以图片链接最好改为http协议。程序中已经将图片链接中的https替换为了http。

这篇关于将csdn的博客爬取到本地并输出为jekyll可解析的markdown格式,同时保存博客的图片到本地的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/810894

相关文章

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

Python中Markdown库的使用示例详解

《Python中Markdown库的使用示例详解》Markdown库是一个用于处理Markdown文本的Python工具,这篇文章主要为大家详细介绍了Markdown库的具体使用,感兴趣的... 目录一、背景二、什么是 Markdown 库三、如何安装这个库四、库函数使用方法1. markdown.mark

Python利用PIL进行图片压缩

《Python利用PIL进行图片压缩》有时在发送一些文件如PPT、Word时,由于文件中的图片太大,导致文件也太大,无法发送,所以本文为大家介绍了Python中图片压缩的方法,需要的可以参考下... 有时在发送一些文件如PPT、Word时,由于文件中的图片太大,导致文件也太大,无法发送,所有可以对文件中的图

java获取图片的大小、宽度、高度方式

《java获取图片的大小、宽度、高度方式》文章介绍了如何将File对象转换为MultipartFile对象的过程,并分享了个人经验,希望能为读者提供参考... 目China编程录Java获取图片的大小、宽度、高度File对象(该对象里面是图片)MultipartFile对象(该对象里面是图片)总结java获取图片

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

一文教你使用Python实现本地分页

《一文教你使用Python实现本地分页》这篇文章主要为大家详细介绍了Python如何实现本地分页的算法,主要针对二级数据结构,文中的示例代码简洁易懂,有需要的小伙伴可以了解下... 在项目开发的过程中,遇到分页的第一页就展示大量的数据,导致前端列表加载展示的速度慢,所以需要在本地加入分页处理,把所有数据先放

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

本地搭建DeepSeek-R1、WebUI的完整过程及访问

《本地搭建DeepSeek-R1、WebUI的完整过程及访问》:本文主要介绍本地搭建DeepSeek-R1、WebUI的完整过程及访问的相关资料,DeepSeek-R1是一个开源的人工智能平台,主... 目录背景       搭建准备基础概念搭建过程访问对话测试总结背景       最近几年,人工智能技术

如何在本地部署 DeepSeek Janus Pro 文生图大模型

《如何在本地部署DeepSeekJanusPro文生图大模型》DeepSeekJanusPro模型在本地成功部署,支持图片理解和文生图功能,通过Gradio界面进行交互,展示了其强大的多模态处... 目录什么是 Janus Pro1. 安装 conda2. 创建 python 虚拟环境3. 克隆 janus