用python寫(xiě)個(gè)博客遷移工具
前言
最近不少寫(xiě)博客的朋友跟我反饋博客園的一些文章下架了,這讓我聯(lián)想到去年簡(jiǎn)書(shū)一樣,我之前寫(xiě)的博客都被下架不可見(jiàn)了。
我最開(kāi)始接觸的博客網(wǎng)址是 csdn、思否、簡(jiǎn)書(shū)還有博客園等,但是后期發(fā)現(xiàn),單論博客的生態(tài)感覺(jué)做的越來(lái)越不行,干貨雖然很多,但是垃圾、標(biāo)題黨很?chē)?yán)重,我自己也有一些博文被莫名的搬走直接標(biāo)為原創(chuàng)。
雖然搜問(wèn)題在上面還是能搜到很多解決方案,但寫(xiě)作的欲望降低了很多。
綜上我從去年入駐掘金,并以掘金作為博客的主平臺(tái)。個(gè)人感覺(jué)掘金團(tuán)隊(duì)對(duì)個(gè)人原創(chuàng)的保護(hù)是非常好的,同時(shí)也在不斷的聽(tīng)取用戶(hù)的建議而去改進(jìn)。有問(wèn)題與建議能隨時(shí)與掘金的同學(xué)討論、溝通,非常方便。
掘金的成長(zhǎng)
最開(kāi)始的時(shí)候,掘金也是面試、標(biāo)題黨滿(mǎn)天飛,但是掘金的運(yùn)營(yíng)大佬逐步整頓起來(lái)之后,文章的質(zhì)量有了顯著的提高,并且也不斷推出有利于新手作者、高質(zhì)量博文的各種活動(dòng),鼓勵(lì)新人創(chuàng)作、老人分享。
同樣在我入駐掘金之后,作為一個(gè)長(zhǎng)期用戶(hù),新人作者,也是見(jiàn)證了這段時(shí)間以來(lái)掘金為了社區(qū)活躍,博客質(zhì)量而做的種種努力。
而最開(kāi)始使用掘金的 markdown,能吐槽的地方還是很多,但掘金的研發(fā)也非常給力,吸納了用戶(hù)的建議后,最新升級(jí)的 markdown 編輯器也是廣受好評(píng),使用過(guò)你就知道真相定律是什么了。
掘金在使用的時(shí)候,一直有種特殊的感覺(jué),是一種很純粹的 coding 情懷。并不僅僅只是一個(gè)單純的博客平臺(tái),而是一直致力于社區(qū)共建、開(kāi)源項(xiàng)目、掘金翻譯計(jì)劃等等的建設(shè),為技術(shù)社區(qū)打造一片純粹干凈的后花園。
搬家命令行工具
那么作為程序員,手動(dòng)搬文章顯然是略 low 的
所以寫(xiě)了一個(gè)簡(jiǎn)單的 python 腳本,有興趣的同學(xué)可以使用它將 cnblogs 上面已有或者創(chuàng)作中的草稿轉(zhuǎn)移到掘金來(lái)。
如果有興趣可以試試改造的更完美點(diǎn),但不建議泄露自己的隱私信息
環(huán)境配置
腳本跑起來(lái)需要 python3 環(huán)境,所以先安裝一下 python 環(huán)境
請(qǐng)?jiān)?cookie.json 中補(bǔ)充博客園與掘金的 cookie
使用 python3 main.py -h 查看使用說(shuō)明
作為程序員應(yīng)該都了解 cookie 是啥,也知道從哪里撈出來(lái)吧
使用方法

還是上個(gè)獲取 cookie 的圖吧,哈哈
請(qǐng)先在 cookie.json 中替換 cookie_cnblogs 與 cookie_juejin 為自己在對(duì)應(yīng)站點(diǎn)上的 cookie
請(qǐng)自行替換user_name與blog_id
// 下載單篇文章到默認(rèn)目錄'./cnblogs' 并輸出日志到'./log'
python3 main.py -m download -a https://www.cnblogs.com/{{user_name}}/p/{{blog_id}}.html --enable_log
// 下載用戶(hù)所有文章到目錄'/Users/cnblogs_t'
python3 main.py -m download -u https://www.cnblogs.com/{{username}} -p /Users/cnblogs_t
// 上傳單篇文章到掘金草稿箱
python3 main.py -m upload -f ./cnblogs/{{blog_id}}.html
// 上傳'./test_blogs'下所有的html文件到掘金草稿箱
python3 main.py -m upload -d ./test_blogs
main.py
新建 main.py 文件,將下述 python 代碼復(fù)制進(jìn)去
# coding=utf-8
import requests
import os
import argparse
import sys
import json
from lxml import etree
from urllib.parse import urlparse
import logging
reload(sys)
sys.setdefaultencoding('utf-8')
parser = argparse.ArgumentParser()
args_dict = {}
list_url_tpl = 'https://www.cnblogs.com/%s/default.html?page=%d'
draft_url = 'https://api.juejin.cn/content_api/v1/article_draft/create_offline'
jj_draft_url_tpl = 'https://juejin.cn/editor/drafts/%s'
cnblog_headers = {}
log_path = './log'
def myget(d, k, v):
if d.get(k) is None:
return v
return d.get(k)
def init_parser():
parser.description = 'blog move for cnblogs'
parser.add_argument('-m', '--method', type=str, dest='method', help='使用方式: download下載 upload上傳到草稿箱', choices=['upload', 'download'])
parser.add_argument('-p', '--path', type=str, dest='path', help='博客html下載的路徑')
parser.add_argument('-d', '--dir', type=str, dest='rec_dir', help='制定要上傳的博客所在文件夾')
parser.add_argument('-f', '--file', type=str, dest='file', help='指定上傳的博客html')
parser.add_argument('-u', '--url', type=str, dest='url', help='個(gè)人主頁(yè)地址')
parser.add_argument('-a', '--article', type=str, dest='article_url', help='單篇文章地址')
parser.add_argument('--enable_log', dest='enable_log', help='是否輸出日志到./log', action='store_true')
parser.set_defaults(enable_log=False)
def init_log():
root_logger = logging.getLogger()
log_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(pathname)s:%(lineno)s %(message)s')
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_formatter)
root_logger.addHandler(console_handler)
if myget(args_dict, 'enable_log', False):
if not os.path.exists(log_path):
os.mkdir(log_path)
file_handler = logging.FileHandler('./log/debug.log')
file_handler.setFormatter(log_formatter)
root_logger.addHandler(file_handler)
root_logger.setLevel(logging.INFO)
def download():
cookies = json.load(open('cookie.json'))
headers = {'cookie': cookies.get('cookie_cnblogs', '')}
dir_path = myget(args_dict, 'path', './cnblogs')
if dir_path[len(dir_path)-1] == '/':
dir_path = dir_path[:len(dir_path)-1]
if not os.path.exists(dir_path):
os.mkdir(dir_path)
article_url = myget(args_dict, 'article_url', '-1')
if article_url != '-1':
logging.info('article_url=%s', article_url)
try:
resp = requests.get(article_url, headers=headers)
if resp.status_code != 200:
logging.error('fail to get blog \'%s\', resp=%s', article_url, resp)
return
tmp_list = article_url.split('/')
blog_id_str = tmp_list[len(tmp_list)-1]
with open(dir_path+'/'+blog_id_str, 'w') as f:
f.write(resp.text)
logging.info('get blog \'%s\' success.', article_url)
except Exception as e:
logging.error('exception raised, fail to get blog \'%s\', exception=%s.', list_url, e)
finally:
return
raw_url = args_dict.get('url')
rurl = urlparse(raw_url)
username = (rurl.path.split("/", 1))[1]
page_no = 1
while True:
list_url = list_url_tpl%(username, page_no)
logging.info('list_url = %s', list_url)
try:
resp = requests.get(list_url, headers=headers)
if resp.status_code != 200:
break
except Exception as e:
logging.error('exception raised, fail to get list \'%s\', exception=%s.', list_url, e)
return
html = etree.HTML(resp.text)
blog_list = html.xpath('//div[@class=\'postTitle\']/a/@href')
if len(blog_list) == 0:
break
for blog_url in blog_list:
tmp_list = blog_url.split('/')
blog_id_str = tmp_list[len(tmp_list)-1]
blog_resp = requests.get(blog_url, headers=headers)
if resp.status_code != 200:
logging.error('fail to get blog \'%s\', resp=%s, skip.', blog_url, resp)
continue
with open(dir_path+'/'+blog_id_str, 'w') as f:
f.write(blog_resp.text)
logging.info('get blog \'%s\' success.', blog_url)
page_no += 1
def upload_request(headers, content, filename):
body = {
"edit_type": 0,
"origin_type": 2,
"content": content
}
data = json.dumps(body)
try:
resp = requests.post(draft_url, data=data, headers=headers)
if resp.status_code != 200:
logging.error('fail to upload blog, filename=%s, resp=%s', filename, resp)
return
ret = resp.json()
draft_id = ret.get('data', {}).get('draft_id', '-1')
logging.info('upload success, filename=%s, jj_draft_id=%s, jj_draft_url=%s', filename, draft_id, jj_draft_url_tpl%draft_id)
except Exception as e:
logging.error('exception raised, fail to upload blog, filename=%s, exception=%s', filename, e)
return
def upload():
cookies = json.load(open('cookie.json'))
headers = {
'cookie': cookies.get('cookie_juejin', ''),
'content-type': 'application/json'
}
filename = myget(args_dict, 'file', '-1')
if filename != '-1':
logging.info('upload_filename=%s', filename)
try:
with open(filename, 'r') as f:
content = f.read()
upload_request(headers, content, filename)
return
except Exception as e:
logging.error('exception raised, exception=%s', e)
rec_dir = myget(args_dict, 'rec_dir', '-1')
if rec_dir != '-1':
logging.info('upload_dir=%s', filename)
try:
g = os.walk(rec_dir)
for path, dir_list, file_list in g:
for filename in file_list:
if filename.endswith('.html'):
filename = os.path.join(path, filename)
with open(filename, 'r') as f:
content = f.read()
upload_request(headers, content, filename)
except Exception as e:
logging.error('exception raised, exception=%s', e)
return
if __name__ == '__main__':
init_parser()
args = parser.parse_args()
args_dict = args.__dict__
init_log()
empty_flag = True
for k, v in args_dict.items():
if k != 'enable_log' and v is not None:
empty_flag = False
if empty_flag:
parser.print_help()
exit(0)
if args_dict.get('method') == 'upload':
upload()
else:
download()
pass
cookie.json
本地新建 cookie.json 文件,與 main.py 同級(jí)
{
"cookie_cnblogs": "請(qǐng)?zhí)鎿Q為博客園cookie",
"cookie_juejin": "請(qǐng)?zhí)鎿Q為掘金cookie"
}
github 地址
最后附上 github 地址,里面除了 demo 的 源碼之外也有錄制好的一個(gè)視頻,有興趣的同學(xué)可以下載使用或者研究研究,腳本有問(wèn)題或者寫(xiě)的不好改進(jìn)的地方也可以互相探討下。有意見(jiàn)也可以隨時(shí)留言反饋
以上就是用python寫(xiě)個(gè)博客遷移工具的詳細(xì)內(nèi)容,更多關(guān)于python 博客遷移的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 使用python和Django完成博客數(shù)據(jù)庫(kù)的遷移方法
- Python依賴(lài)包遷移到斷網(wǎng)環(huán)境操作
- 如何把外網(wǎng)python虛擬環(huán)境遷移到內(nèi)網(wǎng)
- 如何將你的應(yīng)用遷移到Python3的三個(gè)步驟
- 詳解Python3遷移接口變化采坑記
- python 動(dòng)態(tài)遷移solr數(shù)據(jù)過(guò)程解析
- python django生成遷移文件的實(shí)例
- Python依賴(lài)包整體遷移方法詳解
- pycharm使用正則表達(dá)式批量添加print括號(hào)完美從python2遷移到python3
- python虛擬環(huán)境遷移方法
- python實(shí)現(xiàn)數(shù)據(jù)庫(kù)跨服務(wù)器遷移
相關(guān)文章
Python實(shí)現(xiàn)Word表格轉(zhuǎn)成Excel表格的示例代碼
這篇文章主要介紹了Python實(shí)現(xiàn)Word表格轉(zhuǎn)成Excel表格的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
在python中利用numpy求解多項(xiàng)式以及多項(xiàng)式擬合的方法
今天小編就為大家分享一篇在python中利用numpy求解多項(xiàng)式以及多項(xiàng)式擬合的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07
python實(shí)現(xiàn)月食效果實(shí)例代碼
在本文里小編給大家整理了關(guān)于python實(shí)現(xiàn)月食效果的相關(guān)實(shí)例內(nèi)容以及對(duì)應(yīng)代碼,有興趣的朋友們學(xué)習(xí)下。2019-06-06
詳解pytest實(shí)現(xiàn)mark標(biāo)記功能詳細(xì)介紹
這篇文章主要介紹了詳解pytest實(shí)現(xiàn)mark標(biāo)記功能詳細(xì)介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
python socket多線程實(shí)現(xiàn)客戶(hù)端與服務(wù)器連接
這篇文章主要為大家詳細(xì)介紹了python socket多線程實(shí)現(xiàn)客戶(hù)端與服務(wù)器連接,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
如何給windows設(shè)置定時(shí)任務(wù)并運(yùn)行python腳本
這篇文章主要介紹了如何給windows設(shè)置定時(shí)任務(wù)并運(yùn)行python腳本,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
Python特性之列表推導(dǎo)式和生成器表達(dá)式詳解
這篇文章主要介紹了python語(yǔ)言的兩個(gè)非常有用的特性:列表推導(dǎo)式和生成器表達(dá)式,但是它們之間也有一些重要的區(qū)別,我們一起來(lái)看看吧2023-08-08

