1.搭建环境# 安装依赖包pip install playwright schedule pyyaml markdownplaywright install chromium2.创建项目结构csdn_auto_publisher/│├── config.yaml # 配置文件存放账号文章路径├── csdn_publisher.py # 主发布脚本├── articles/ # 存放待发布文章的文件夹│ ├── 2026-06-15_文章标题1.md│ └── 2026-06-16_文章标题2.md├── cookies.json # 登录凭证自动生成└── logs/ # 日志文件夹3. 写一个YAML配置文件:# CSDN 自动发布配置csdn:cookie_file: cookies.json # 字符串editor_url: https://editor.csdn.net/md/article:source_dir: ./articles # 路径auto_set_tags: true # 布尔值tags: # 列表- Python- 后端开发schedule:publish_time: 08:00 # 字符串虽然像数字timezone: Asia/Shanghaibrowser:headless: false # 是否无头模式4.文件说明代码块#!/usr/bin/env python3# -*- coding: utf-8 -*-CSDN 博客自动发布脚本 v2.0功能每天08:00自动扫描articles文件夹读取Markdown内容并发布到CSDN5.接口调用代码块import osimport reimport sysimport timeimport jsonimport loggingimport yamlimport scheduleimport markdownfrom pathlib import Pathfrom datetime import datetimefrom playwright.sync_api import sync_playwright6.配置日志库# 日志配置 logging.basicConfig(levellogging.INFO,format%(asctime)s - %(levelname)s - %(message)s,handlers[logging.FileHandler(logs/publisher.log, encodingutf-8),logging.StreamHandler()])logger logging.getLogger(__name__)7.加载配置文件# 工具函数 def load_config():读取配置文件with open(config.yaml, r, encodingutf-8) as f:return yaml.safe_load(f)8.加载cookiedef load_cookies(context, cookie_file):加载已保存的登录凭证避免重复登录if os.path.exists(cookie_file):with open(cookie_file, r, encodingutf-8) as f:cookies json.load(f)context.add_cookies(cookies)logger.info(✅ 加载已保存的登录凭证)return Truelogger.info(⚠️ 未找到凭证文件需要手动登录)return False9.保存cookiedef save_cookies(context, cookie_file):保存登录凭证cookies context.cookies()with open(cookie_file, w, encodingutf-8) as f:json.dump(cookies, f)logger.info( 登录凭证已保存)def parse_markdown_file(file_path):解析Markdown文件提取标题和内容with open(file_path, r, encodingutf-8) as f:content f.read()# 提取标题首行 # 开头的行title_match re.search(r^#\s(.?)$, content, re.MULTILINE)if title_match:title title_match.group(1).strip()# 移除正文中的标题行content re.sub(r^#\s.?$\n?, , content, count1, flagsre.MULTILINE)else:# 如果没有#标题用文件名作标题title Path(file_path).stem# 移除日期前缀可选title re.sub(r^\d{4}-\d{2}-\d{2}_, , title)return title, content.strip()def extract_tags_from_title(title):从标题提取关键词作为标签用于CSDN标签系统# 提取技术关键词常见技术栈词汇库tech_keywords [Python, Java, Go, Rust, JavaScript, TypeScript, Vue, React,Spring, Docker, Kubernetes, MySQL, Redis, MongoDB, PostgreSQL,AI, 机器学习, 深度学习, 数据分析, 算法, 架构, 微服务, 后端, 前端]found []for kw in tech_keywords:if kw.lower() in title.lower():found.append(kw)return found[:3] # 最多返回3个标签def get_pending_articles(source_dir, published_log):获取待发布的文章列表检查是否已发布过published set()if os.path.exists(published_log):with open(published_log, r, encodingutf-8) as f:published set(line.strip() for line in f)md_files list(Path(source_dir).glob(*.md))pending []for md_file in md_files:if md_file.name not in published:title, content parse_markdown_file(md_file)pending.append({file: md_file,filename: md_file.name,title: title,content: content})return pendingdef mark_published(filename, published_log):标记文章为已发布with open(published_log, a, encodingutf-8) as f:f.write(f{filename}\n)logger.info(f 已记录: {filename})# 核心发布函数Playwright方案def publish_article(title, content, config):使用Playwright发布单篇文章到CSDNwith sync_playwright() as p:# 启动浏览器持久上下文 反检测配置browser p.chromium.launch(headlessconfig[browser][headless],args[--disable-blink-featuresAutomationControlled,--no-sandbox,--disable-dev-shm-usage])# 创建上下文模拟真实用户context browser.new_context(viewport{width: 1920, height: 1080},user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36)# 尝试加载Cookiecookie_file config[csdn][cookie_file]cookies_loaded load_cookies(context, cookie_file)page context.new_page()try:# 步骤1打开CSDN编辑器页面editor_url config[csdn][editor_url]page.goto(editor_url, timeout60000)logger.info( 已打开CSDN编辑器页面)time.sleep(3)# 步骤2如果需要登录手动完成if not cookies_loaded or login in page.url:logger.warning( 需要登录请在浏览器中扫码登录...)logger.warning(页面打开后将显示二维码登录后脚本会自动继续)# 等待跳转到编辑页面最长90秒page.wait_for_url(lambda url: editor.csdn.net/md in url, timeout90000)# 保存登录凭证save_cookies(context, cookie_file)logger.info(✅ 登录完成凭证已保存)time.sleep(3)# 步骤3填写文章标题title_selector input[placeholder*标题]page.wait_for_selector(title_selector, timeout10000)page.fill(title_selector, )page.fill(title_selector, title)logger.info(f 标题已填写: {title[:50]}...)time.sleep(1)# 步骤4填写文章正文Markdown编辑器# CSDN编辑器是基于textarea的content_selector textareapage.wait_for_selector(content_selector, timeout10000)# 如果有图片引用需要将本地图片转为base64进阶功能此处简化# 直接将Markdown内容填入page.fill(content_selector, content)logger.info(f 正文已填写 (长度: {len(content)} 字符))time.sleep(2)# 步骤5填写文章标签if config[article].get(auto_set_tags, True):tags extract_tags_from_title(title)if not tags:tags config[article].get(tags, [技术, 开发])# 点击添加标签按钮tag_input page.locator(input[placeholder*标签]).firstif tag_input.count() 0:for tag in tags:tag_input.fill(tag)time.sleep(0.5)page.keyboard.press(Enter)time.sleep(0.3)logger.info(f️ 标签已添加: {, .join(tags)})# 步骤6点击发布按钮publish_btn page.locator(button:has-text(发布文章))if publish_btn.count() 0:publish_btn page.locator(button:has-text(发布))if publish_btn.count() 0:publish_btn.first.click()logger.info( 正在提交发布...)time.sleep(5)else:logger.error(❌ 未找到发布按钮)return False# 步骤7等待发布成功检查成功提示page.wait_for_timeout(5000)if 发布成功 in page.content() or page.url.startswith(https://blog.csdn.net/):logger.info(f✅ 发布成功文章: {title})return Trueelse:logger.warning(⚠️ 未检测到成功提示请人工检查)return Falseexcept Exception as e:logger.error(f❌ 发布失败: {str(e)})return Falsefinally:browser.close()# 批量发布调度器 def run_publisher():执行发布任务扫描所有待发文章并逐一发布logger.info( * 50)logger.info(f 开始执行定时任务: {datetime.now().strftime(%Y-%m-%d %H:%M:%S)})config load_config()source_dir config[article][source_dir]published_log config[article][published_log]# 检查文章目录if not os.path.exists(source_dir):os.makedirs(source_dir)logger.info(f 创建文章目录: {source_dir})# 获取待发布文章pending get_pending_articles(source_dir, published_log)if not pending:logger.info( 没有待发布的文章)returnlogger.info(f 发现 {len(pending)} 篇待发布文章)# 逐一发布for idx, article in enumerate(pending, 1):logger.info(f\n--- [{idx}/{len(pending)}] 正在发布: {article[title]} ---)success publish_article(titlearticle[title],contentarticle[content],configconfig)if success:mark_published(article[filename], published_log)logger.info(f✨ 发布完成: {article[filename]})else:logger.error(f 发布失败: {article[filename]})# 发布间隔避免被识别为异常操作if idx len(pending):wait_time 60logger.info(f⏳ 等待 {wait_time} 秒后发布下一篇...)time.sleep(wait_time)logger.info( 全部发布任务执行完毕)def schedule_daily():配置定时任务每天08:00执行config load_config()publish_time config[schedule][publish_time]schedule.every().day.at(publish_time).do(run_publisher)logger.info(f⏰ 定时任务已设定: 每日 {publish_time} 自动发布)while True:schedule.run_pending()time.sleep(60)# 入口函数 if __name__ __main__:if not os.path.exists(logs):os.makedirs(logs)if not os.path.exists(articles):os.makedirs(articles)if len(sys.argv) 1 and sys.argv[1] --now:# 立即执行发布测试用run_publisher()else:# 启动定时任务模式schedule_daily() 配置 Windows 任务计划每日08:00准时触发1. 打开 任务计划程序 → 创建任务2. 常规· 名称CSDN_Auto_Publish_Daily· 勾选「不管用户是否登录都要运行」· 勾选「使用最高权限运行」3. 触发器· 新建 → 每天 → 开始时间 08:00:00 → 勾选「已启用」4. 操作· 新建 → 启动程序· 程序python· 添加参数D:\csdn_auto_publisher\csdn_publisher_once.py你的脚本绝对路径· 起始于D:\csdn_auto_publisher5. 条件取消所有「只有在以下条件才启动」的勾选保证即使未登录也会执行6. 设置勾选「如果任务失败按以下频率重新启动」→ 间隔 5 分钟最多 3 次
[Python]自动发布CSDN脚本
1.搭建环境# 安装依赖包pip install playwright schedule pyyaml markdownplaywright install chromium2.创建项目结构csdn_auto_publisher/│├── config.yaml # 配置文件存放账号文章路径├── csdn_publisher.py # 主发布脚本├── articles/ # 存放待发布文章的文件夹│ ├── 2026-06-15_文章标题1.md│ └── 2026-06-16_文章标题2.md├── cookies.json # 登录凭证自动生成└── logs/ # 日志文件夹3. 写一个YAML配置文件:# CSDN 自动发布配置csdn:cookie_file: cookies.json # 字符串editor_url: https://editor.csdn.net/md/article:source_dir: ./articles # 路径auto_set_tags: true # 布尔值tags: # 列表- Python- 后端开发schedule:publish_time: 08:00 # 字符串虽然像数字timezone: Asia/Shanghaibrowser:headless: false # 是否无头模式4.文件说明代码块#!/usr/bin/env python3# -*- coding: utf-8 -*-CSDN 博客自动发布脚本 v2.0功能每天08:00自动扫描articles文件夹读取Markdown内容并发布到CSDN5.接口调用代码块import osimport reimport sysimport timeimport jsonimport loggingimport yamlimport scheduleimport markdownfrom pathlib import Pathfrom datetime import datetimefrom playwright.sync_api import sync_playwright6.配置日志库# 日志配置 logging.basicConfig(levellogging.INFO,format%(asctime)s - %(levelname)s - %(message)s,handlers[logging.FileHandler(logs/publisher.log, encodingutf-8),logging.StreamHandler()])logger logging.getLogger(__name__)7.加载配置文件# 工具函数 def load_config():读取配置文件with open(config.yaml, r, encodingutf-8) as f:return yaml.safe_load(f)8.加载cookiedef load_cookies(context, cookie_file):加载已保存的登录凭证避免重复登录if os.path.exists(cookie_file):with open(cookie_file, r, encodingutf-8) as f:cookies json.load(f)context.add_cookies(cookies)logger.info(✅ 加载已保存的登录凭证)return Truelogger.info(⚠️ 未找到凭证文件需要手动登录)return False9.保存cookiedef save_cookies(context, cookie_file):保存登录凭证cookies context.cookies()with open(cookie_file, w, encodingutf-8) as f:json.dump(cookies, f)logger.info( 登录凭证已保存)def parse_markdown_file(file_path):解析Markdown文件提取标题和内容with open(file_path, r, encodingutf-8) as f:content f.read()# 提取标题首行 # 开头的行title_match re.search(r^#\s(.?)$, content, re.MULTILINE)if title_match:title title_match.group(1).strip()# 移除正文中的标题行content re.sub(r^#\s.?$\n?, , content, count1, flagsre.MULTILINE)else:# 如果没有#标题用文件名作标题title Path(file_path).stem# 移除日期前缀可选title re.sub(r^\d{4}-\d{2}-\d{2}_, , title)return title, content.strip()def extract_tags_from_title(title):从标题提取关键词作为标签用于CSDN标签系统# 提取技术关键词常见技术栈词汇库tech_keywords [Python, Java, Go, Rust, JavaScript, TypeScript, Vue, React,Spring, Docker, Kubernetes, MySQL, Redis, MongoDB, PostgreSQL,AI, 机器学习, 深度学习, 数据分析, 算法, 架构, 微服务, 后端, 前端]found []for kw in tech_keywords:if kw.lower() in title.lower():found.append(kw)return found[:3] # 最多返回3个标签def get_pending_articles(source_dir, published_log):获取待发布的文章列表检查是否已发布过published set()if os.path.exists(published_log):with open(published_log, r, encodingutf-8) as f:published set(line.strip() for line in f)md_files list(Path(source_dir).glob(*.md))pending []for md_file in md_files:if md_file.name not in published:title, content parse_markdown_file(md_file)pending.append({file: md_file,filename: md_file.name,title: title,content: content})return pendingdef mark_published(filename, published_log):标记文章为已发布with open(published_log, a, encodingutf-8) as f:f.write(f{filename}\n)logger.info(f 已记录: {filename})# 核心发布函数Playwright方案def publish_article(title, content, config):使用Playwright发布单篇文章到CSDNwith sync_playwright() as p:# 启动浏览器持久上下文 反检测配置browser p.chromium.launch(headlessconfig[browser][headless],args[--disable-blink-featuresAutomationControlled,--no-sandbox,--disable-dev-shm-usage])# 创建上下文模拟真实用户context browser.new_context(viewport{width: 1920, height: 1080},user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36)# 尝试加载Cookiecookie_file config[csdn][cookie_file]cookies_loaded load_cookies(context, cookie_file)page context.new_page()try:# 步骤1打开CSDN编辑器页面editor_url config[csdn][editor_url]page.goto(editor_url, timeout60000)logger.info( 已打开CSDN编辑器页面)time.sleep(3)# 步骤2如果需要登录手动完成if not cookies_loaded or login in page.url:logger.warning( 需要登录请在浏览器中扫码登录...)logger.warning(页面打开后将显示二维码登录后脚本会自动继续)# 等待跳转到编辑页面最长90秒page.wait_for_url(lambda url: editor.csdn.net/md in url, timeout90000)# 保存登录凭证save_cookies(context, cookie_file)logger.info(✅ 登录完成凭证已保存)time.sleep(3)# 步骤3填写文章标题title_selector input[placeholder*标题]page.wait_for_selector(title_selector, timeout10000)page.fill(title_selector, )page.fill(title_selector, title)logger.info(f 标题已填写: {title[:50]}...)time.sleep(1)# 步骤4填写文章正文Markdown编辑器# CSDN编辑器是基于textarea的content_selector textareapage.wait_for_selector(content_selector, timeout10000)# 如果有图片引用需要将本地图片转为base64进阶功能此处简化# 直接将Markdown内容填入page.fill(content_selector, content)logger.info(f 正文已填写 (长度: {len(content)} 字符))time.sleep(2)# 步骤5填写文章标签if config[article].get(auto_set_tags, True):tags extract_tags_from_title(title)if not tags:tags config[article].get(tags, [技术, 开发])# 点击添加标签按钮tag_input page.locator(input[placeholder*标签]).firstif tag_input.count() 0:for tag in tags:tag_input.fill(tag)time.sleep(0.5)page.keyboard.press(Enter)time.sleep(0.3)logger.info(f️ 标签已添加: {, .join(tags)})# 步骤6点击发布按钮publish_btn page.locator(button:has-text(发布文章))if publish_btn.count() 0:publish_btn page.locator(button:has-text(发布))if publish_btn.count() 0:publish_btn.first.click()logger.info( 正在提交发布...)time.sleep(5)else:logger.error(❌ 未找到发布按钮)return False# 步骤7等待发布成功检查成功提示page.wait_for_timeout(5000)if 发布成功 in page.content() or page.url.startswith(https://blog.csdn.net/):logger.info(f✅ 发布成功文章: {title})return Trueelse:logger.warning(⚠️ 未检测到成功提示请人工检查)return Falseexcept Exception as e:logger.error(f❌ 发布失败: {str(e)})return Falsefinally:browser.close()# 批量发布调度器 def run_publisher():执行发布任务扫描所有待发文章并逐一发布logger.info( * 50)logger.info(f 开始执行定时任务: {datetime.now().strftime(%Y-%m-%d %H:%M:%S)})config load_config()source_dir config[article][source_dir]published_log config[article][published_log]# 检查文章目录if not os.path.exists(source_dir):os.makedirs(source_dir)logger.info(f 创建文章目录: {source_dir})# 获取待发布文章pending get_pending_articles(source_dir, published_log)if not pending:logger.info( 没有待发布的文章)returnlogger.info(f 发现 {len(pending)} 篇待发布文章)# 逐一发布for idx, article in enumerate(pending, 1):logger.info(f\n--- [{idx}/{len(pending)}] 正在发布: {article[title]} ---)success publish_article(titlearticle[title],contentarticle[content],configconfig)if success:mark_published(article[filename], published_log)logger.info(f✨ 发布完成: {article[filename]})else:logger.error(f 发布失败: {article[filename]})# 发布间隔避免被识别为异常操作if idx len(pending):wait_time 60logger.info(f⏳ 等待 {wait_time} 秒后发布下一篇...)time.sleep(wait_time)logger.info( 全部发布任务执行完毕)def schedule_daily():配置定时任务每天08:00执行config load_config()publish_time config[schedule][publish_time]schedule.every().day.at(publish_time).do(run_publisher)logger.info(f⏰ 定时任务已设定: 每日 {publish_time} 自动发布)while True:schedule.run_pending()time.sleep(60)# 入口函数 if __name__ __main__:if not os.path.exists(logs):os.makedirs(logs)if not os.path.exists(articles):os.makedirs(articles)if len(sys.argv) 1 and sys.argv[1] --now:# 立即执行发布测试用run_publisher()else:# 启动定时任务模式schedule_daily() 配置 Windows 任务计划每日08:00准时触发1. 打开 任务计划程序 → 创建任务2. 常规· 名称CSDN_Auto_Publish_Daily· 勾选「不管用户是否登录都要运行」· 勾选「使用最高权限运行」3. 触发器· 新建 → 每天 → 开始时间 08:00:00 → 勾选「已启用」4. 操作· 新建 → 启动程序· 程序python· 添加参数D:\csdn_auto_publisher\csdn_publisher_once.py你的脚本绝对路径· 起始于D:\csdn_auto_publisher5. 条件取消所有「只有在以下条件才启动」的勾选保证即使未登录也会执行6. 设置勾选「如果任务失败按以下频率重新启动」→ 间隔 5 分钟最多 3 次