十分鐘寫個博客遷移工具

前言

最近很多寫博客的朋友跟我反饋博客園的一些文章下架了,這讓我聯想到去年簡書同樣,我以前寫的博客都被下架不可見了。html

我最開始接觸的博客網址是 csdn、思否、簡書還有博客園等,可是後期發現,單論博客的生態感受作的愈來愈不行,乾貨雖然不少,可是垃圾、標題黨很嚴重,我本身也有一些博文被莫名的搬走直接標爲原創。python

雖然搜問題在上面仍是能搜到不少解決方案,但寫做的慾望下降了不少。git

綜上我從去年入駐掘金,並以掘金做爲博客的主平臺。我的感受掘金團隊對我的原創的保護是很是好的,同時也在不斷的聽取用戶的建議而去改進。有問題與建議能隨時與掘金的同窗討論、溝通,很是方便。程序員

掘金的成長

最開始的時候,掘金也是面試、標題黨滿天飛,可是掘金的運營大佬逐步整頓起來以後,文章的質量有了顯著的提升,而且也不斷推出有利於新手做者、高質量博文的各類活動,鼓勵新人創做、老人分享。github

一樣在我入駐掘金以後,做爲一個長期用戶,新人做者,也是見證了這段時間以來掘金爲了社區活躍,博客質量而作的種種努力。web

而最開始使用掘金的 markdown,能吐槽的地方仍是不少,但掘金的研發也很是給力,吸納了用戶的建議後,最新升級的 markdown 編輯器也是廣受好評,使用過你就知道真相定律是什麼了。面試

掘金在使用的時候,一直有種特殊的感受,是一種很純粹的 coding 情懷。並不單單只是一個單純的博客平臺,而是一直致力於社區共建、開源項目、掘金翻譯計劃等等的建設,爲技術社區打造一片純粹乾淨的後花園。shell

搬家命令行工具

那麼做爲程序員,手動搬文章顯然是略 low 的json

因此寫了一個簡單的 python 腳本,有興趣的同窗能夠使用它將 cnblogs 上面已有或者創做中的草稿轉移到掘金來。api

若是有興趣能夠試試改造的更完美點,但不建議泄露本身的隱私信息

環境配置

  1. 腳本跑起來須要 python3 環境,因此先安裝一下 python 環境

  2. 請在 cookie.json 中補充博客園與掘金的 cookie

  3. 使用 python3 main.py -h 查看使用說明

做爲程序員應該都瞭解 cookie 是啥,也知道從哪裏撈出來吧

使用方法

juejincookie.gif

仍是上個獲取 cookie 的圖吧,哈哈

請先在 cookie.json 中替換 cookie_cnblogs 與 cookie_juejin 爲本身在對應站點上的 cookie

請自行替換user_name與blog_id
// 下載單篇文章到默認目錄'./cnblogs' 並輸出日誌到'./log'
python3 main.py -m download -a https://www.cnblogs.com/{{user_name}}/p/{{blog_id}}.html --enable_log 

// 下載用戶全部文章到目錄'/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 代碼複製進去

# 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='我的主頁地址')
    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 同級

{
    "cookie_cnblogs": "請替換爲博客園cookie",
    "cookie_juejin": "請替換爲掘金cookie"
}
複製代碼

github 地址

最後附上 github 地址,裏面除了 demo 的 源碼以外也有錄製好的一個視頻,有興趣的同窗能夠下載使用或者研究研究,腳本有問題或者寫的很差改進的地方也能夠互相探討下。有意見也能夠隨時留言反饋

相關文章
相關標籤/搜索