用python写个博客迁移工具

这篇文章主要介绍了如何用python写个博客迁移工具,帮助大家更好的理解和学习使用python,感兴趣的朋友可以了解下

目录
  • 前言
  • 掘金的成长
  • 搬家命令行工具
    • 环境配置
    • main.py
    • cookie.json
  • github 地址

    前言

    最近不少写博客的朋友跟我反馈博客园的一些文章下架了,这让我联想到去年简书一样,我之前写的博客都被下架不可见了。

    我最开始接触的博客网址是 csdn、思否、简书还有博客园等,但是后期发现,单论博客的生态感觉做的越来越不行,干货虽然很多,但是垃圾、标题党很严重,我自己也有一些博文被莫名的搬走直接标为原创。

    虽然搜问题在上面还是能搜到很多解决方案,但写作的欲望降低了很多。

    综上我从去年入驻掘金,并以掘金作为博客的主平台。个人感觉掘金团队对个人原创的保护是非常好的,同时也在不断的听取用户的建议而去改进。有问题与建议能随时与掘金的同学讨论、沟通,非常方便。

    掘金的成长

    最开始的时候,掘金也是面试、标题党满天飞,但是掘金的运营大佬逐步整顿起来之后,文章的质量有了显著的提高,并且也不断推出有利于新手作者、高质量博文的各种活动,鼓励新人创作、老人分享。

    同样在我入驻掘金之后,作为一个长期用户,新人作者,也是见证了这段时间以来掘金为了社区活跃,博客质量而做的种种努力。

    而最开始使用掘金的 markdown,能吐槽的地方还是很多,但掘金的研发也非常给力,吸纳了用户的建议后,最新升级的 markdown 编辑器也是广受好评,使用过你就知道真相定律是什么了。

    掘金在使用的时候,一直有种特殊的感觉,是一种很纯粹的 coding 情怀。并不仅仅只是一个单纯的博客平台,而是一直致力于社区共建、开源项目、掘金翻译计划等等的建设,为技术社区打造一片纯粹干净的后花园。

    搬家命令行工具

    那么作为程序员,手动搬文章显然是略 low 的

    所以写了一个简单的 python 脚本,有兴趣的同学可以使用它将 cnblogs 上面已有或者创作中的草稿转移到掘金来。

    如果有兴趣可以试试改造的更完美点,但不建议泄露自己的隐私信息

    环境配置

    脚本跑起来需要 python3 环境,所以先安装一下 python 环境

    请在 cookie.json 中补充博客园与掘金的 cookie

    使用 python3 main.py -h 查看使用说明

    作为程序员应该都了解 cookie 是啥,也知道从哪里捞出来吧

    使用方法

    还是上个获取 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 的 源码之外也有录制好的一个视频,有兴趣的同学可以下载使用或者研究研究,脚本有问题或者写的不好改进的地方也可以互相探讨下。有意见也可以随时留言反馈

    以上就是用python写个博客迁移工具的详细内容,更多关于python 博客迁移的资料请关注昆居客【www.kunjuke.com】其它相关文章!

    相关推荐:

    迷你世界工具大全用法2026

    迷你世界里面有很多的道具物品,今天给大家带来了游戏里面的最新版本的工具大全用法,还有小伙伴不清楚游戏里面的工具怎么用,那么下面就是具体的内容. 迷你世界工具大全用法 氧气面罩 属性:工具 解锁途径:无需解锁 需要材料 棉花*2 雀莺羽毛*1 合成台 工具箱 用途:使用雀莺的羽毛制成的密封性极好的面罩,是星球旅行的必备装。装备到头部可以抵御外星恶劣的缺氧环境,装备会扣除耐久度,耐久度为0时装备会消失...

    realme【真我服务】账号业务官宣完成迁移,后续品牌产品相关问题可在【OPPO 服务】咨询

    7 月 1 日消息,realme 真我官方微博今日宣布,【真我服务】账号业务将于 2026 年 7 月 1 日完成迁移,后续真我产品相关问题,可以在【OPPO 服务】账号咨询。据此前报道,真我官方在 3 月 20 日发布公告,宣布其在中国大陆地区的售后服务逐步整合至 OPPO 服务体系。自 2026 年 4 月 1 日起,realme 在中国大陆(不含港澳台)的手机、平板、电脑及 IoT 等产品全...

    谷歌关闭 GIF 搜索工具 Tenor API 对外服务,要求第三方平台另寻出路

    感谢网友 Coje_He 的线索投递! 7 月 1 日消息,谷歌宣布已于昨日(6 月 30 日)正式关闭 GIF 搜索工具 Tenor API 对外服务,未来谷歌将仅保留 Tenor API 供自家产品内部使用,过去依赖该接口提供 GIF 搜索功能的第三方平台需要寻找替代方案。公开信息显示,Tenor 成立于 2014 年,最初名为 Riffsy,早期主打移动端 GIF 搜索与分享服务。谷歌在 2...

    我的世界方块宝可梦工具合成表大全

    我的世界里面有很多的mod,模组中所有内容的合成都是不一样的,今天给大家带来了游戏里面的方块宝可梦模组工具合成图鉴分享,还有小伙伴不清楚,那么下面就是具体的内容. 我的世界方块宝可梦工具合成表大全 宝可梦图鉴(红) 合成表: 本文内容来源于互联网,如有侵权请联系删除。

    我的世界迁移账号怎么弄

    ①点击“移动我的账户”,重新登录一次账号,并进行人机验证;②完成验证之后,Mojang会开启第二重验证——邮箱验证;③代码确认无误后,便是最后一个步骤,登陆已有/注册新的微软账号,登陆完成之后,数秒之后原Mojang账号中的皮肤、披风等物品都将迁移到新账户中。 详细答案: ①点击“移动我的账户”,Mojang会让你重新登录一次账号,并进行人机验证。(顺便吐槽一下,现在的验证机制虽说是安全了,但真的...

    百度浏览器电脑版如何导入Chrome书签_百度浏览器数据迁移同步教程

    可成功将Chrome书签导入百度浏览器电脑版,方法包括:一、通过内置工具自动识别Chrome数据目录;二、导出HTML文件再导入;三、直接导入Bookmarks JSON文件,需关闭Chrome并确保百度浏览器为v12.58+版本。 如果您希望将Chrome浏览器中保存的书签迁移到百度浏览器电脑版,但发现百度浏览器无法自动识别或导入失败,则可能是由于Chrome未运行、账号未登录、数据路径变更或同...

    硬氪观察 | 苹果代工厂开造人形机器人,一场豪赌未来的产能大迁移

    作者 | 邱晓芬 编辑 | 袁斯来 在龙旗位于南昌的智能制造工厂中,两台智元精灵G2人形机器人已融入平板产线,承担起多媒体集成测试(MMIT)工站的上下料作业。 其工作节拍已接近熟练产线员工——依托腰部的三个运动自由度与全向移动底盘,机器人精准从传送带抓取生产完成的平板,送入检测机箱;待测试完毕,又能迅速取出合格产品、利落放回传送带分流。 据现场实测数据,单台机器人每小时可稳定处理约310台平板设...
    · AI · 102500

    《零基础搭建OpenClaw迁移训练环境指南》

    在实验室的仿真环境中,智能体可以高精度地完成抓取、装配、导航等一连串复杂操作,任务成功率趋近于百分之百。但当同一套模型被部署至真实物理场景时,却常因光照的细微偏移、物体表面的细微磨损,抑或执行器毫厘级的响应偏差,而引发完全不可预知的失败。这种从数字仿真到实体世界的断崖式性能滑坡,已成为制约所有通用具身智能体真正落地的核心瓶颈。openclaw并未延续传统域适应的既有路径,而是回归智能体理解世界的根...
    · AI · 92666