批量下载自己的随笔
文档说明:只记录关键地方; 2023-04-15
缘由和意义:
东西都在自己手里,平台倒闭还可以迁移
数据永远在自己手里,发什么都有自主权
可以避免人为刀俎,我为鱼肉的情况
无广告、自主可控、打造自己的品牌、高度定制化
知道为啥大家自己买硬盘了吧 (网盘行业大家有目共睹)
为啥写: 汲取了知识,同时也需要回馈,相互促进的关系
破除知识优越感 !
破除知识诅咒 !
copyleft !
下载自己随笔的脚本
都是使用markdown 写的,下载就方便了很多
办法一: 控制台注入javascript 直接就可以下载
办法二:浏览器扩展注入javascript 直接就可以下载
办法三: python3 直接抓取
办法四: python3 注入javascript
办法五: python3 浏览器扩展注入javascript
办法六: 上述办法混合使用
实操原理图
实操
准备数据源
打开地址: https://i.cnblogs.com/posts
把每页显示随笔数 设置为最大值
打开web控制台,输入如下代码
浏览器右上角 允许下载随笔索引文件
通过查看控制台: 发现还有更简单的 https://i.cnblogs.com/api/posts/list?p=1&cid=&t=1&cfg=0&search=&orderBy=&s=80&scid=
{ function createJSONFile(content, filename) { let blob = new Blob([JSON.stringify(content)], {type: "application/json"}); let url = window.URL.createObjectURL(blob); let a = document.createElement("a"); a.style.display = "none"; a.href = url; a.download = filename; a.click(); setTimeout(function () { window.URL.revokeObjectURL(url); }, 3000); } let list = [] let pattern = /^\/posts\/edit;postId=(\d+)$/ let download_url_prefix = 'https://www.cnblogs.com/jingjingxyk/p/'; document.querySelectorAll('.cnb-table table tbody tr').forEach((value, key, parent) => { let title_link = value.querySelector('.post-title-appendix') let title_content = value.querySelector('.post-title-content span') let public_date = value.querySelector('.post-title-suffix-date span') let editor_operator = value.querySelector('td:nth-child(6) a') let public_state = value.querySelector('td:nth-child(3)') let post_id = parseInt(pattern.exec(editor_operator.getAttribute('href'))[1]); let data = { "title": title_content.innerText.trim(), 'url': location.protocol + title_link.getAttribute('href'), 'public_date': public_date.getAttribute('title'), "post_id": post_id, 'public_state': public_state.innerText, 'public_state_code': public_state.innerText === '已发布' ? 1 : 0, 'download_md_link': download_url_prefix + post_id + '.md' } list.push(data) }) let current_page = document.querySelector('.cnb-pager-position .indexes .current') let current_page_number = parseInt(current_page.innerText) createJSONFile(list, "i-cnblogs-com-" + current_page_number + ".json") }
浏览器启用远程调试功能
以chromium 内核浏览器为例
chromium --remote-debugging-port=9221
准备python 运行环境
Pipfile 文件
pipenv update --pypi-mirror https://pypi.tuna.tsinghua.edu.cn/simple
[[source]] url = "https://pypi.tuna.tsinghua.edu.cn/simple" verify_ssl = true name = "pip_conf_index_global" [packages] requests = "*" playwright = "*" [dev-packages] [requires] python_version = "3.10" python_full_version = "3.10.6"
准备 python3 脚本
进入 运行环境
pipenv shell
执行
python3 main.py
import os import json import glob import optparse import datetime from playwright.async_api import async_playwright import asyncio import re ''' 下载 随笔 markdown 文档 原理: 借助浏览器 远程调试 实现 --remote-debugging-port=9221 前提: 用自己的帐号先登录,这样 跳过了登录环节 ''' def get_cnblogs_index(filename): with open(filename, mode='r', encoding='utf-8') as f: content = f.read() result = json.loads(content) return result def init(): wildcard = project_dir + '/i-cnblogs-com-*.json' data_origin_index = list() for file in glob.glob(wildcard): res = get_cnblogs_index(file) for cell in res: data_origin_index.append(cell) return data_origin_index async def get_content(element, SEMAPHORE, browser): async with SEMAPHORE: # 文件名中的空格替换为下划线 pattern = r'\s+' title = re.sub(pattern, '_', element['title']) filename = project_dir + '/cnblogs/' + title + '.md' if not os.path.isfile(filename): context = browser.contexts[0] page = await context.new_page() print(element) await page.goto(element['url']) content = await page.evaluate( ''' async (url) => { //使用 ajax 原始文本 let response = await fetch(url) let content= await response.text() return content } ''' , element['download_md_link']) print("{}".format(content)) with open(filename, "w+") as f: f.write(content) await asyncio.sleep(3) await page.close() async def run(playwright): data_origin_index = init() browser = await playwright.chromium.connect_over_cdp("http://localhost:9222") tasks = [asyncio.create_task(get_content(element, SEMAPHORE, browser)) for element in data_origin_index] result = await asyncio.gather(*tasks) await browser.close() async def main(): async with async_playwright() as playwright: await run(playwright) if __name__ == '__main__': parser = optparse.OptionParser('usage: %prog [OPTIONS]', description=__doc__) parser.add_option('--id', help='随笔 ID ', type=int, default=None) parser.add_option('--max_parallel', help='最大并发数', type=int, default=os.cpu_count()) args = parser.parse_args() # 暂不启用传递命令行参数 project_dir = os.path.abspath(os.path.dirname(__file__) + '/') if not os.path.isdir(project_dir + '/cnblogs'): os.mkdir(project_dir + '/cnblogs') start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 控制并发数 默认为3 ;不要轻易改大,改大以后,影响服务器性能,可能会惹上麻烦 SEMAPHORE = asyncio.Semaphore(3) asyncio.run(main()) end_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print("start_time: {} \nend_time: {}".format(start_time, end_time))
小结:
此方法不仅仅可以备份 博客园 随笔文件,更多应用。。。。。
其实就是各种爬虫的玩法 (甚至可以操纵自动登录)
爬虫写的好,牢饭吃到老 爬取个人隐私身份信息是违法的
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术