从零开始免费搭建自己的博客(六)——三个站点一键发布博客

​ 本文是博客搭建系列文章第六篇,其他文章链接:

  1. 从零开始免费搭建自己的博客(一)——本地搭建 Hexo 框架
  2. 从零开始免费搭建自己的博客(二)——基于 GitHub pages 建站
  3. 从零开始免费搭建自己的博客(三)——基于 Gitee pages 建站
  4. 从零开始免费搭建自己的博客(四)——编写Markdown文章利器 Typora
  5. 从零开始免费搭建自己的博客(五)——Typora + PicGo + GitHub/Gitee图床
  6. 从零开始免费搭建自己的博客(六)——三个站点一键发布博客
  7. 从零开始免费搭建自己的博客(七)——迁移 CSDN 博客到个人博客站点
  8. 从零开始免费搭建自己的博客(八)——博客网站个性化设置及优化

前言

本文目标:本地写好文章后,双击执行一个脚本,文章可以自动发布到自己的 github.io 、gitee.io 和 CSDN。

实现原理:

  1. 写文章时自己在开头添加好title,date,tags等字段,用python实现文章的新旧版本替换。
  2. 配置好 Hexo 的配置文件,用shell脚本实现 hexo ghexo d等操作,Python脚本实现Gitee Pages更新。
  3. 利用puppeteer模拟浏览器登录CSDN发布博客。
  4. 将前面三步的代码整合,使用bat脚本双击运行。

文件比较

我平时在本地写文章保存在E:\Markdown目录,而 Hexo 保存在D:\MyBlog目录。发布 Hexo 之前需要先把文章拷贝到D:\MyBlog\source\_posts目录,有时候文章有修改还要重新拷贝覆盖。所以考虑用Python脚本实现拷贝文件,判断文件最后修改时间决定是否需要覆盖旧文章。

因为我们省略了hexo create "title"这一步,直接把文件拷贝到了_posts目录,所以写文章时需要确保在开头加上 title、date、tags、category信息,不然发布的文章会没有没有标题、发布时间、标签、分类信息,其实hexo create命令做的就是这件事。注意最后的空行一定要有

1
2
3
4
5
6
---
title: 我的第一篇博客
date: 2021-01-11 19:50:43
tags: [博客搭建]
---

如果是在 Typora 中编辑,直接在第一行输入---回车就行了。

image-20210121222437143

Python 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# copy_to_hexo.py
import os
import shutil
import time


def copy_to_hexo():
local_list = os.listdir(LOCAL_ARTICLE_PATH)
hexo_list = os.listdir(HEXO_ARTICLE_PATH)
flag = True
for file in local_list:
if file in IGNORE_LIST:
continue
if file.endswith('.md'):
local_version = os.path.join(LOCAL_ARTICLE_PATH, file)
hexo_version = os.path.join(HEXO_ARTICLE_PATH, file)
if file not in hexo_list:
flag = False
print("新增文章: %s..." % file,
"最后修改时间:%s" % TimeStampFormat(os.path.getmtime(local_version)))
shutil.copy(local_version, hexo_version)
elif os.path.getmtime(local_version) > os.path.getmtime(hexo_version):
flag = False
print("更新文章: %s..." % file,
"上次修改时间:%s" % TimeStampFormat(os.path.getmtime(hexo_version)),
"最后修改时间:%s" % TimeStampFormat(os.path.getmtime(local_version)))
shutil.copy(local_version, hexo_version)
print('文章无变化' if flag else '更新完毕')


# 时间格式标准化
def TimeStampFormat(timestamp):
return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))


IGNORE_LIST = ['欢迎使用Markdown编辑器.md']
HEXO_ARTICLE_PATH = 'D:/MyBlog/source/_posts'
LOCAL_ARTICLE_PATH = 'E:/Markdown'

copy_to_hexo()

运行效果:

image-20210124222133145

发布Hexo到GitHub/Gitee

本来以为需要把配置文件deploy参数改成 GitHub 地址 hexo d一次,再改成 Gitee 地址hexo d一次,后来看文档发现 Hexo 支持一次发布到多个仓库,只需要修改一下配置文件,然后hexo g && hexo d就可以了。

1修改Hexo配置文件

1
2
3
4
5
deploy:
type: git
repo:
github: git@github.com:用户名/用户名.github.io.git,main
gitee: git@gitee.com:用户名/仓库名.git,master

shell发布脚本

Git Bash支持直接运行 shell 脚本,只需要把下面代码保存为 .sh后缀的文件即可。

1
2
3
4
5
6
7
8
9
10
11
# deploy_hexo.sh
cd /d/MyBlog
pwd
# 白底黑字效果
echo -e "\033[47;30m>>>>>>>>>>>>>>>>>>>>hexo g<<<<<<<<<<<<<<<<<<<<\033[0m"
hexo g
echo -e "\033[47;30m>>>>>>>>>>>>>>>>>>>>hexo d<<<<<<<<<<<<<<<<<<<<\033[0m"
hexo d
sleep 5
# 执行完毕不退出
# exec /bin/bash

运行效果:

image-20210124214628069

Gitee Pages更新脚本

上一篇博客已经实现了用 Python 脚本自动更新 Gitee pages,这里直接拿来用就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# update_gitee_pages.py
# 注意: 更改25、36、52行的用户名密码为自己的Gitee的用户名密码,第45行的仓库名为图床仓库的名字
# 每处延时都有用,是我花了好长时间调试过的

import asyncio
import os

from pyppeteer import launch


async def _update_gitee_pages(usr_name, repo_name):
browser = await launch(devtools=False, dumpio=True, autoClose=True,
args=['--start-maximized', # 设置浏览器全屏
'--no-sandbox', # 取消沙盒模式,沙盒模式下权限太小
'--disable-infobars', # 关闭受控制提示
# 设置ua
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3542.0 Safari/537.36'
],
userDataDir=os.path.abspath('./cookies'))
page = await browser.newPage()
# 登录
await page.goto('https://gitee.com/login')
await page.waitFor(2000)
if '登录' in await page.title():
await page.type('#user_login', '用户名')
await page.type('#user_password', '密码')
await page.keyboard.press('Enter')
print('使用账号密码登录成功...')
await page.waitFor(2000)
else:
print('使用cookies缓存登录成功...')
# 更新
await page.goto('https://gitee.com/%s/%s/pages' % (usr_name, repo_name))
await page.waitFor(2000)
page.on('dialog', lambda dialog: asyncio.ensure_future(_handle_dialog(page, dialog)))
await page.click('#pages-branch > div.button.orange.redeploy-button.ui.update_deploy')
await page.waitFor(20000)
print('更新 Gitee Pages %s 成功...' % repo_name)


async def _handle_dialog(page, dialog):
await page.waitFor(2000)
print('点击确定更新')
await dialog.accept()


def update_gitee_pages(usr_name, repo_name):
asyncio.get_event_loop().run_until_complete(_update_gitee_pages(usr_name, repo_name))


if __name__ == '__main__':
update_gitee_pages('用户名', '仓库名')

运行效果:

image-20210124214209633

发布到CSDN

最不好处理的是自动发布文章到 CSDN ,据说 CSDN 官方之前是提供了发布文章的接口的,后来关闭了。一文多发这个功能对于做自媒体的人来说需求很大,于是有了 OpenWrite这种一文多发平台,还有开源的ArtiPub。各大平台都没有提供发布文章的官方接口,这种一分多发平台想必也是通过模拟 http 请求来实现自动发文。

本文利用 Python 的第三方库 puppeteer 操作无头浏览器模拟登陆 CSDN 发布文章,类似 Selenium,更加接近真实操作,防止被网站检测到机器操作导致封号。最近发现 Go 语言的 go-rod 库对于模拟浏览器操作更加好用,官方文档写的很详细,两者都不用另外下载 driver,相比于 Selenium 方便了很多,后面打算用 Golang 重新实现一版,今天先看下 Python 代码。注意这只是个 Demo,实现了自动发布一篇文章的功能,我们的需求是发布多篇文章,实际使用要稍作修改,实际使用的代码是这个 post_to_csdn.py)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# post_to_csdn_demo.py 
import asyncio
import base64
import os

from PIL import Image
from pyppeteer import launch


async def main(blog_name, title, content, tags, category):
"""
:param blog_name: 博客名字,自己博客主页url的最后部分
:param title: 文章标题
:param content: 文章内容
:param tags: 标签,多个用英文","隔开
:param category: 分类
:return:
"""
browser = await launch(devtools=False, dumpio=True, autoClose=True,
args=['--start-maximized', # 设置浏览器全屏
'--no-sandbox', # 取消沙盒模式,沙盒模式下权限太小
'--disable-infobars', # 关闭受控制提示
# 设置ua
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3542.0 Safari/537.36'
],
userDataDir=os.path.abspath('./cookies'))
print(await browser.userAgent())
pages_list = await browser.pages()
page = pages_list[0]
# await page.setViewport(viewport={'width': 1920, 'height': 1080})
# 打开博客主页
await page.goto('https://blog.csdn.net/%s' % blog_name)
await page.waitFor(1000)
# 先找到已有文章
elements = await page.querySelectorAll('#articleMeList-blog > div.article-list > div > h4')
article_list = []
for article in elements:
article = await (await article.getProperty('textContent')).jsonValue()
article_list.append(str(article).strip('\n').split('\n')[2].strip())
print('已有文章%d篇: ' % len(article_list), article_list)
# 点击 创作中心
await page.click(
'#csdn-toolbar > div > div > div.toolbar-container-right > div > div.toolbar-btn.toolbar-btn-write.csdn-toolbar-fl > a')
await page.waitFor(4000)
if '登录' in await page.title():
print('正在登录...')
# 点击 CSDN App扫码
await page.click('#app > div > div > div.main > div.main-login > div.main-select > ul > li:nth-child(1) > a')
# 获取登录二维码
img_element = await page.querySelector('#appqr > span.app-code-wrap > img')
img_src = await (await img_element.getProperty('src')).jsonValue()
img_src = str(img_src).split(',')[1]
img_data = base64.b64decode(img_src)
img_name = 'login.png'
if os.path.exists(img_name):
os.remove(img_name)
with open(img_name, 'wb') as f:
f.write(img_data)
img = Image.open(img_name)
img.show()
# 等待登录成功,最多等5分钟
await page.waitForSelector(
'#view-containe > div.left_box > div.left_box_top > a.routerlink-bt.routerlink-bt-md > span',
timeout=300 * 1000)
print('已登录')
# 点击 Markdown 编辑器
await page.click('#view-containe > div.left_box > div.left_box_top > a.routerlink-bt.routerlink-bt-md > span')
await page.waitFor(3000)
# 切换到写文章页签
pages_list = await browser.pages()
page = pages_list[-1]
# 选中标题输入框
print('输入标题: %s' % title)
title_input = await page.querySelector(
'body > div.app.app--light > div.layout > div.layout__panel.layout__panel--articletitle-bar > div > div.article-bar__input-box > input')
# 清空原有内容
await title_input.focus()
await page.keyboard.down('Control')
await page.keyboard.press('KeyA')
await page.keyboard.up('Control')
await page.keyboard.up('Backspace')
# 输入标题内容
await title_input.type(title)
# 选中文章输入框
print('输入文章内容: %s' % content)
content_input = await page.querySelector(
'body > div.app.app--light > div.layout > div.layout__panel.flex.flex--row > div > div.layout__panel.flex.flex--row > div.layout__panel.layout__panel--editor > div.editor > pre')
# 清空原有内容
await content_input.focus()
await page.keyboard.down('Control')
await page.keyboard.press('KeyA')
await page.keyboard.up('Control')
await page.keyboard.up('Backspace')
# 输入文章内容
await content_input.type(content)
await page.waitFor(2000)
# 点击 发布文章
await page.click(
'body > div.app.app--light > div.layout > div.layout__panel.layout__panel--articletitle-bar > div > div.article-bar__user-box.flex.flex--row > button.btn.btn-publish')
# 删除原来的标签,如果有的话
exist_tags = await page.querySelectorAll(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div:nth-child(3) > div > div > div > span > span > i')
for i in exist_tags:
# 每删一个,页面会有变化
tag = await page.querySelector(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div:nth-child(3) > div > div > div > span > span > i')
await tag.click()
await page.waitFor(500)
# 点击 添加文章标签
print('添加标签: %s' % tags)
add_tag = await page.querySelector(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div:nth-child(3) > div > div > div > button')
await add_tag.click()
# 添加标签
tag_input = await page.querySelector(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div:nth-child(3) > div > div > div.mark_selection_box > div.mark_selection_box_header > div > div.el-input.el-input--suffix > input')
tag_list = tags.split(',')
for tag in tag_list:
await tag_input.type(tag.strip())
await page.waitFor(500)
await page.keyboard.press('Enter')
await page.waitFor(500)
# 收起 添加文章标签
await add_tag.click()
# 添加分类
print('添加分类: %s' % category)
add_category = await page.querySelector('#tagList > button')
await add_category.click()
category_input = await page.querySelector(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div:nth-child(4) > div > div > input')
await category_input.type(category)
await page.waitFor(500)
await page.keyboard.press('Enter')
await page.waitFor(500)
# 选择文章类型:原创
print('文章类型: 原创')
select_box = await page.querySelector(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div.inline-box > div > div')
await select_box.click()
await page.waitFor(500)
# 下拉框的 id 是个随机值,每次都不一样,通过按键曲线救国
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
# 选择发布形式:公开 2, 私密 4, 粉丝可见 6, VIP可见 8
print('发布形式: 公开')
flag = 2
await page.click(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__content > div.form-entry.flex.form-entry__field-switch-box.overflow-unset.form-entry-marginBottom > div > div > label:nth-child(%d)' % flag)
# 发布文章:保存为草稿 btn-c-blue, 发布文章 btn-b-red
print('点击发布...')
flag = 'btn-b-red'
await page.click(
'body > div.app.app--light > div.modal > div > div.modal__inner-2 > div.modal__button-bar > button.button.%s' % flag)
await page.waitFor(3000)
url_element = await page.querySelector('#alertSuccess > div > div.pos-top > div:nth-child(4) > a')
url = await (await url_element.getProperty('href')).jsonValue()
await browser.close()
print('发布成功, 文章地址: %s' % url)


blog_name = 'yushuaigee'
title = '自动发布的第一篇文章'
content = '自动发布的第一篇文章自动发布的第一篇文章自动发布的第一篇文章'
tags = '博客搭建,自动发布文章'
category = '博客搭建'
asyncio.get_event_loop().run_until_complete(main(blog_name, title, content, tags, category))

运行效果:

image-20210124213855158

一键发布脚本!

上面写的4个脚本一步一步实现了我们的每个小目标,对于 Windows 系统来说,可以使用 bat 脚本把它们整合在一起,完成“一键”发布的需求。

1
2
3
4
5
6
:: post_my_blog.bat
python copy_to_hexo.py
D:\Git\git-bash.exe deploy_hexo.sh
python update_gitee_pages.py
python post_to_csdn.py
pause

写好文章后,直接双击post_my_blog.bat就可以发布到三个地方了。

最终运行效果:

image-20210124223519915

后续

本文解决了一文多发的问题,以前发布在 CSDN 上的博客就不用一篇一篇复制到自己的博客站点了,但是要想一键发布,需要先将 CSDN 上的文章先下载到本地,而且原来的文章是使用富文本编辑器写的,怎么转化成 Markdown 格式呢?下一篇文章将介绍这两个问题的解决方法。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2021 杰克小麻雀
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信