news 2026/6/15 3:46:56

[Python]自动发布CSDN脚本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[Python]自动发布CSDN脚本

1.搭建环境

# 安装依赖包
pip install playwright schedule pyyaml markdown
playwright install chromium

2.创建项目结构

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/Shanghai"

browser:
headless: false # 是否无头模式

4.文件说明代码块

#!/usr/bin/env python3

# -*- coding: utf-8 -*-

"""

CSDN 博客自动发布脚本 v2.0

功能:每天08:00自动扫描articles文件夹,读取Markdown内容并发布到CSDN

"""

5.接口调用代码块

import os

import re

import sys

import time

import json

import logging

import yaml

import schedule

import markdown

from pathlib import Path

from datetime import datetime

from playwright.sync_api import sync_playwright

6.配置日志库

# ========== 日志配置 ==========

logging.basicConfig(

level=logging.INFO,

format='%(asctime)s - %(levelname)s - %(message)s',

handlers=[

logging.FileHandler('logs/publisher.log', encoding='utf-8'),

logging.StreamHandler()

]

)

logger = logging.getLogger(__name__)

7.加载配置文件

# ========== 工具函数 ==========

def load_config():

"""读取配置文件"""

with open('config.yaml', 'r', encoding='utf-8') as f:

return yaml.safe_load(f)

8.加载cookie

def load_cookies(context, cookie_file):

"""加载已保存的登录凭证(避免重复登录)"""

if os.path.exists(cookie_file):

with open(cookie_file, 'r', encoding='utf-8') as f:

cookies = json.load(f)

context.add_cookies(cookies)

logger.info("✅ 加载已保存的登录凭证")

return True

logger.info("⚠️ 未找到凭证文件,需要手动登录")

return False

9.保存cookie

def save_cookies(context, cookie_file):

"""保存登录凭证"""

cookies = context.cookies()

with open(cookie_file, 'w', encoding='utf-8') as f:

json.dump(cookies, f)

logger.info("💾 登录凭证已保存")

def parse_markdown_file(file_path):

"""解析Markdown文件,提取标题和内容"""

with open(file_path, 'r', encoding='utf-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, count=1, flags=re.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', encoding='utf-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 pending

def mark_published(filename, published_log):

"""标记文章为已发布"""

with open(published_log, 'a', encoding='utf-8') as f:

f.write(f"{filename}\n")

logger.info(f"📝 已记录: {filename}")

# ========== 核心发布函数(Playwright方案)==========

def publish_article(title, content, config):

"""使用Playwright发布单篇文章到CSDN"""

with sync_playwright() as p:

# 启动浏览器(持久上下文 + 反检测配置)

browser = p.chromium.launch(

headless=config['browser']['headless'],

args=[

'--disable-blink-features=AutomationControlled',

'--no-sandbox',

'--disable-dev-shm-usage'

]

)

# 创建上下文(模拟真实用户)

context = browser.new_context(

viewport={'width': 1920, 'height': 1080},

user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'

)

# 尝试加载Cookie

cookie_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, timeout=60000)

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, timeout=90000)

# 保存登录凭证

save_cookies(context, cookie_file)

logger.info("✅ 登录完成,凭证已保存")

time.sleep(3)

# 步骤3:填写文章标题
title_selector = 'input[placeholder*="标题"]'
page.wait_for_selector(title_selector, timeout=10000)
page.fill(title_selector, "")
page.fill(title_selector, title)
logger.info(f"📌 标题已填写: {title[:50]}...")
time.sleep(1)

# 步骤4:填写文章正文(Markdown编辑器)
# CSDN编辑器是基于textarea的
content_selector = 'textarea'
page.wait_for_selector(content_selector, timeout=10000)

# 如果有图片引用,需要将本地图片转为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*="标签"]').first
if 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 True
else:
logger.warning("⚠️ 未检测到成功提示,请人工检查")
return False

except Exception as e:
logger.error(f"❌ 发布失败: {str(e)}")
return False
finally:
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("📭 没有待发布的文章")
return

logger.info(f"📚 发现 {len(pending)} 篇待发布文章")

# 逐一发布
for idx, article in enumerate(pending, 1):
logger.info(f"\n--- [{idx}/{len(pending)}] 正在发布: {article['title']} ---")

success = publish_article(
title=article['title'],
content=article['content'],
config=config
)

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 = 60
logger.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_publisher

5. 条件:取消所有「只有在以下条件才启动」的勾选(保证即使未登录也会执行)

6. 设置:勾选「如果任务失败,按以下频率重新启动」→ 间隔 5 分钟,最多 3 次

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 3:46:55

串口通信避坑指南:奇偶校验位设置错了怎么办?一个案例带你排查FPGA与上位机通信故障

串口通信校验位配置实战&#xff1a;从波形分析到故障定位的完整指南当FPGA与上位机通过UART通信时&#xff0c;校验位设置不一致导致的故障往往令开发者头疼。这种问题看似简单&#xff0c;却可能耗费数小时调试时间。本文将带您深入理解校验位工作机制&#xff0c;并通过真实…

作者头像 李华
网站建设 2026/6/15 3:41:04

在游戏主机上享受B站内容:wiliwili跨平台客户端完整指南

在游戏主机上享受B站内容&#xff1a;wiliwili跨平台客户端完整指南 【免费下载链接】wiliwili 第三方B站客户端&#xff0c;目前可以运行在PC全平台、PSVita、PS4 、Xbox 和 Nintendo Switch上 项目地址: https://gitcode.com/GitHub_Trending/wi/wiliwili 你是否曾想过…

作者头像 李华
网站建设 2026/6/15 3:30:52

解决CH32V307+FreeRTOS+LwIP联网大坑:DHCP反复插拔网线导致IP耗尽怎么办?

CH32V307FreeRTOSLwIP深度优化&#xff1a;根治DHCP频繁插拔导致的IP池耗尽问题当CH32V307开发板运行在采用软路由等特定DHCP服务器的环境中&#xff0c;工程师们常会遇到一个令人头疼的现象——反复插拔网线几次后&#xff0c;设备突然无法获取IP地址。这背后隐藏着一个容易被…

作者头像 李华