前言

在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格式,同时保存博客的图片到本地相关推荐

  1. 阮一峰老师博客爬取与博客文章存储持久化方式的思考

    阮一峰老师博客爬取与博客文章存储持久化方式的思考 前言 博客文章存储持久化思考 文本形式存储 html形式存储 pdf形式存储 博客爬取思路 爬取思路一 爬取思路二 个人选择 pdf存储 结尾 前言 ...

  2. 16、爬取知乎大v张佳玮的文章“标题”、“摘要”、“链接”,并存储到本地文件...

    爬取知乎大v张佳玮的文章"标题"."摘要"."链接",并存储到本地文件 1 # 爬取知乎大v张佳玮的文章"标题".&qu ...

  3. Python3从零开始爬取今日头条的新闻【五、解析头条视频真实播放地址并自动下载】

    Python3从零开始爬取今日头条的新闻[一.开发环境搭建] Python3从零开始爬取今日头条的新闻[二.首页热点新闻抓取] Python3从零开始爬取今日头条的新闻[三.滚动到底自动加载] Pyt ...

  4. 使用集搜客爬取酷狗排行歌曲信息

    最近项目中遇到需要大量爬取歌曲数据的需求,且需要爬取歌曲的网站比较多,自己写爬虫显然开发成本很高,所以找了个集搜客来用. 1.安装教程可以自行百度,基本都是直接下一步,不过这个软件需要.net4.0的 ...

  5. python微博爬虫分析_python爬取和分析新浪微博(一):scrapy构建新浪微博榜单、博主及微博信息爬虫...

    1. 爬虫项目介绍 爬虫首先基于python scrapy 框架,使用了随机用户代理和IP代理等反爬技巧,将爬取到的微博领域数据,各领域榜单博主信息和博主的微博信息分别存入的mysql数据库对应的表格 ...

  6. xml文件拆分 python_用Python提取合并由集搜客爬取的多个xml文件中的数据 | 向死而生...

    为了爬点小数据同时试用了八爪鱼和集搜客.两者都有免费版本,但八爪鱼数据导出需要积分,集搜客可以不用积分.不过八爪鱼导出的数据有多种格式可选,而集搜客如果不用积分就只能得到一堆xml文件.本着能省则省的 ...

  7. (3)分布式下的爬虫Scrapy应该如何做-递归爬取方式,数据输出方式以及数据库链接...

    放假这段时间好好的思考了一下关于Scrapy的一些常用操作,主要解决了三个问题: 1.如何连续爬取 2.数据输出方式 3.数据库链接 一,如何连续爬取: 思考:要达到连续爬取,逻辑上无非从以下的方向着 ...

  8. scrapy 解析css,Scrapy基础(六)————Scrapy爬取伯乐在线一通过css和xpath解析文章字段...

    上次我们介绍了scrapy的安装和加入debug的main文件,这次重要介绍创建的爬虫的基本爬取有用信息 通过命令(这篇博文)创建了jobbole这个爬虫,并且生成了jobbole.py这个文件,又写 ...

  9. scrapy爬取网站在线播放TS视频流片段并整合为MP4格式

    目标:爬取网站在线播放的视频 分析: 1.网站:天一影视 视频:天地争霸美猴王第一集 2.request请求 :'https://youku.comyouku.com/20190524/23996_8 ...

最新文章

  1. 电力系统稳定与控制_基于数据驱动的电力系统稳定性分析
  2. 轻松记账工程冲刺第二阶段10
  3. redhat 服务器 安装mysql_Linux服务器安装mysql
  4. 未定义标识符 stringc/c++(20)_到 2024 年,阿斯顿·马丁汽车销量的 20%以上将是电动汽车...
  5. Hadoop3 Hive Spark完整安装与实践
  6. 泛化性的危机!LeCun发文质疑:测试集和训练集永远没关系...
  7. 递归+分治+贪心+动态规划
  8. android软件游戏显示fps测试工具,fpsviewer—实时显示fps,监控Android卡顿的可视化工具...
  9. 学生信息管理系统(SSM+JSP)
  10. 红米7pro支持html不,可以畅快的刷机了,红米Note 7 Pro等机型适配 TWRP
  11. Axure原型分享:可视化统计图表
  12. “花瓣网”首页静态页面(仅供参考)
  13. Entegris EUV 1010光罩盒展现极低的缺陷率,已获ASML认证
  14. 前端vue-高德地图操作
  15. 【ROM制作工具】华为UPDATE.APP固件一键解包打包教程
  16. 通用Mmapper配置
  17. [Android]-SDK QQ微信登入
  18. 用python爬虫来登录深信服ac行为控制器,涉及到js加密部分,更新url分类库(针对企业微信更新)
  19. SimpleRNN实现股票预测
  20. 亚洲领军汽车产业展会Automechanika Shanghai开幕丨Xtecher 前线

热门文章

  1. 关于逆向思维在程序设计中的应用
  2. RocketMQ 为什么延迟消息不能自定义延迟时间
  3. MySQL,3306端口,内部敏感服务对外开放存在风险解决办法
  4. 操作系统 课堂笔记 第二章 进程管理
  5. php网站实战,PHP实战之WEB网站常见受攻击方式及解决办法
  6. 爬虫Spider 09 - scrapy框架 | 日志级别 | 保存为csv、json文件
  7. [docker] 启动docker的container(或者说image的实例)
  8. 一个SpringBoot小案例
  9. 智能隐私:如何保障我们的个人信息
  10. 如何搭建自己的gitlab服务