🤟致敬读者

  • 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉

📘博主相关



📃文章前言

  • 🔷文章均为学习工作中整理的笔记。
  • 🔶如有错误请指正,共同学习进步。


在这里插入图片描述


博文自动发布功能实现 (平台:CSDN、语言:Python、驱动:Firefox)

1. 背景

当我在CSDN上发布文章时,流程很简单:只需确保格式正确,然后复制粘贴即可完成发布。

但如果想把同一篇文章同时发布到多个博客平台,就需要逐个平台进行操作。这让我思考:能否实现一键多平台发布呢?

虽然市面上已有一些商业软件支持批量发布到大多数博客平台,但它们都需要付费订阅。因此,我萌生了自己开发一个解决方案的想法。

虽然前路充满挑战,结果也未可知,但我决定尝试一下。于是,就有了这篇探索之旅的记录——让我们先从CSDN平台开始着手。

2. 工具

博客平台采用CSDN
编程语言选用Python
浏览器使用Firefox
自动化测试基于selenium库
以下是具体实现代码

3. 代码

3.1 库的安装

需要安装的库如下
selenium + webdriver_manager + pyperclip

pip install selenium webdriver_manager pyperclip

3.2 完整代码

完整的代码如下

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import pyperclip # 用于处理复制粘贴
import time
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
## 这种方法会自动下载并管理对应版本的驱动程序,无需手动下载和配置。
from webdriver_manager.firefox import GeckoDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 需要下载的库 selenium + webdriver_manager + pyperclip


# 函数定义
def publish_to_csdn(blog_content):

    # 使用webdriver_manager自动管理驱动
    options = webdriver.FirefoxOptions()
    # 设置默认搜索引擎为百度(可选)
    options.set_preference("browser.search.defaultenginename", "百度")
    service = webdriver.FirefoxService(GeckoDriverManager().install())
    # 备用方案,当上面的直接下载不下来时,释放下面两行代码,使用本地驱动
    # from selenium.webdriver.firefox.service import Service as FirefoxService
    # service = FirefoxService(executable_path="geckodriver.exe")

    driver = webdriver.Firefox(service=service, options=options)

    # 打开CSDN登录页面
    ## 打开后登录,然后在当前页面中执行操作
    print("-------------------> 扫码登录CSDN ------------------->")

    driver.get("https://mp.csdn.net/")

    # 如果当前页面有弹窗,则关闭
    try:
        WebDriverWait(driver, 10).until(
            EC.alert_is_present()
        ).accept()
    except:
        pass

    time.sleep(2)

    # 等待页面加载后查找"写博客"或"创作"按钮
    try:
        # 等待并点击创作按钮
        write_btn = WebDriverWait(driver, 2).until(
            EC.element_to_be_clickable((By.XPATH, '//a[contains(text(), "创作")]'))
        )
        write_btn.click()
    except:
        # 如果找不到按钮,直接访问编辑器页面
        driver.get("https://mp.csdn.net/edit")
    
    ## 等待登录扫码(5秒内扫码登录,否则报错,时间可根据需要调整加长)
    time.sleep(2)

    print("-------------------> 开始发布文章 ------------------->")

    # 1. 填写标题
    print("-------------------> 填写标题 ------------------->")
    try:
        # 等待标题输入框出现
        title_field = WebDriverWait(driver, 2).until(
            # EC.presence_of_element_located((By.XPATH, '//input[@placeholder="请输入文章标题"]'))
            EC.presence_of_element_located((By.XPATH, '//input[@placeholder="请输入文章标题(5~100个字)"]'))
        )
        title_field.clear()
        title_field.send_keys(blog_content["title"])
    except:
        # 如果主要选择器失败,尝试备用选择器
        alternative_selectors = [
            '//input[contains(@placeholder, "标题")]',
            '//*[@placeholder="请输入文章标题"]',
            '//input[@class="article-title"]'
        ]
        
        title_field = None
        for selector in alternative_selectors:
            try:
                title_field = driver.find_element(By.XPATH, selector)
                break
            except:
                continue
        
        if not title_field:
            raise Exception("无法找到标题输入框")

    print("标题已填写:", blog_content["title"])
    time.sleep(2)

    # 2. 填写内容(采用复制粘贴方式,更稳定):cite[4]
    # 先将内容复制到系统剪贴板
    pyperclip.copy(blog_content["content"])
    # 定位到内容编辑区域并点击焦点
    print("-------------------> 定位文本区域 ------------------->")
    # Fallback to original selector
    content_area = driver.find_element(By.XPATH, '//div[@class="editor"]')
    # content_area.click()
    print("-----------------> content_area: ",content_area)

    ## 模拟删除内容--------------------------------------------------------------------------
    print("------------------> 开始清空文本区域内容 ------------------->")
    # 使用JavaScript直接清空编辑器内容
    try:
        # 直接清空整个编辑器内容
        driver.execute_script("""
            var editor = document.querySelector('.editor__inner');
            if (editor) {
                editor.innerHTML = '<div class="cledit-section"></div><div></div>';
                console.log('编辑器内容已清空');
            }
        """)
        time.sleep(2)
        print("已通过JavaScript清空编辑器内容")
    except Exception as e:
        print(f"JavaScript清空内容失败: {e}")
        pass
    
    # Method 3: Keyboard operations
    # try:
        # content_area.click()
        # content_area.send_keys(Keys.CONTROL + 'a')
        # time.sleep(1)
        # content_area.send_keys(Keys.BACKSPACE)
        # time.sleep(2)
        # print("已通过键盘操作清空内容")
        # return True
    # except:
    #     pass
    time.sleep(2)
    # 点击
    print("----------------------> 模拟鼠标点击content_area <--------------------->")
    content_area.click()
    # time.sleep(3)
    # 输入文本
    print("-------------------> 输入内容 ------------------->")

    # 模拟键盘Ctrl+V粘贴-------------------------------------------------------------------
    print("----------------------> 模拟Ctrl+V粘贴 ------------------->")
    # 无效操作,这个操作不是执行粘贴操作,而是获取剪切板的内容,并不会将内容粘贴到网页的文本区域

    time.sleep(2)
    print("---------------------> 粘贴内容")
    action_chains = webdriver.ActionChains(driver)
    action_chains.key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
    print("c-v 已粘贴内容")
    # time.sleep(10)
    # 使用JavaScript直接设置内容
    driver.execute_script("arguments[0].innerHTML = arguments[1];", content_area, blog_content["content"])
    print("已通过JavaScript直接设置内容")
    time.sleep(2)


    # 3. 点击发布按钮
    print("-------------------> 点击发布按钮 ------------------->")
    publish_btn = driver.find_element(By.XPATH, '//button[contains(text(), "发布文章")]')
    publish_btn.click()
    print("已点击发布按钮")
    time.sleep(2)

    # 4. 在发布弹窗中设置标签、摘要等(可选,但推荐)
    # ... 此处可添加更多自动化设置逻辑,参考:cite[4]


    ## 标签填写
    print("-------------------> 填写标签 ------------------->")
    tag_list = blog_content["tag"]
    tag_btn = driver.find_element(By.XPATH, '//button[contains(text(), "添加文章标签")]')
    tag_btn.click()
    print("--------------------已点击标签按钮")
    for tag in tag_list:
        pyperclip.copy(tag)
        # tag_input = driver.find_element(By.XPATH, '//input[@placeholder="请输入文字搜索"]')
        tag_input = driver.find_element(By.XPATH, '//input[@placeholder="请输入文字搜索,Enter键入可添加自定义标签"]')
        tag_input.send_keys(tag)
        time.sleep(3)
        print("------------已输入标签:", tag)
        tag_input.send_keys(Keys.ENTER)
        print("------------已添加标签:", tag)
        time.sleep(3)
        # 清空当前输入框的内容
        tag_input.send_keys(Keys.CONTROL + 'a')
        # 回车清空当前输入框的内容
        tag_input.send_keys(Keys.BACKSPACE)
        time.sleep(3)

    # 关闭窗口

    # 改进的关闭标签弹窗方式
    print("---------------------> 关闭标签弹窗")
    try:
        # 方法1: 查找关闭按钮(X)并点击
        close_button = driver.find_element(By.XPATH, '//button[contains(@class, "modal") and contains(@class, "close") or @title="关闭"]')
        close_button.click()
        time.sleep(2)
        print("已通过关闭按钮关闭标签弹窗")
    except:
        try:
            # 方法2: 查找具有特定类名的关闭按钮
            close_button = driver.find_element(By.XPATH, '//div[contains(@class, "mark_selection_box")]//button[contains(@class, "close") or @aria-label="关闭"]')
            close_button.click()
            time.sleep(2)
            print("已通过类名选择器关闭标签弹窗")
        except:
            try:
                # 方法3: 使用 ESC 键关闭弹窗
                webdriver.ActionChains(driver).send_keys(Keys.ESCAPE).perform()
                time.sleep(2)   
                print("已通过ESC键关闭标签弹窗")
            except:
                try:
                    # 方法4: 点击模态框遮罩层关闭  
                    overlay = driver.find_element(By.XPATH, '//div[contains(@class, "modal-overlay") or contains(@class, "el-overlay") or contains(@class, "mask")]')
                    overlay.click()
                    print("已通过点击遮罩层关闭标签弹窗")
                except:
                    print("无法找到关闭标签弹窗的方法,将继续执行后续操作")
    time.sleep(2)

    ## 范围选择
    print("-------------------> 选择范围 ------------------->")
    # 查找并点击"仅我可见"标签
    scope_button = driver.find_element(By.XPATH, '//label[@for="private" and contains(text(), "仅我可见")]')
    scope_button.click()
    time.sleep(2)

    ## 发布文章
    print("-------------------> 发布文章 ------------------->")
    try:
        # 使用精确的XPath选择器定位发布文章按钮
        publish_btn = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.XPATH, '//button[contains(@class, "btn-b-red") and contains(text(), "发布文章")]'))
        )
        # 滚动到元素可见位置
        driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", publish_btn)
        time.sleep(1)
        
        # 点击按钮
        publish_btn.click()
        print("已成功点击发布文章按钮")
    except Exception as e:
        print("点击发布文章按钮失败:", str(e))
        # 备用方案:使用JavaScript点击
        try:
            publish_btn = driver.find_element(By.XPATH, '//button[contains(@class, "btn-b-red") and contains(text(), "发布文章")]')
            driver.execute_script("arguments[0].click();", publish_btn)
            print("已通过JavaScript成功点击发布文章按钮")
        except Exception as e2:
            print("JavaScript点击也失败:", str(e2))
    time.sleep(5)

    
    driver.quit()

if __name__ == '__main__':
    # 使用保存的cookies自动发布文章
    news_title = "自动发布CSDN文章功能测试"
    news_content = "## 自动发布CSDN文章功能测试 \r\n ## 1. 标题1 \r\n 这是一篇测试文章的文本内容 \r\n ## 2. 标题2 \r\n 这也是一篇测试文章的文本内容2 \n ## 3. 标题3 \n 这也是一篇测试文章的文本内容3 "
    # 定义博客内容字典结构
    blog_content = {
        "title": news_title,
        "content": news_content,
        "tag": ["python","csdn","selenium","firefox"],
        "scope": "self" # all, self, fans, vip
    }
    # publish_to_csdn(news_title, news_content)
    publish_to_csdn(blog_content)

4. 演示

运行该代码可以自动将博文发布到CSDN平台,具体流程如下:

  • 自动跳转至登录页面,扫码完成认证后立即进入编辑界面
  • 智能清空并填充文章标题
  • 自动清空并输入正文内容
  • 智能选择相关标签等发布信息
  • 最终完成自动发布

以下是完整操作过程的演示视频:

CSDN自动发博视频演示


在这里插入图片描述


📜文末寄语

  • 🟠关注我,解锁更多优质内容
  • 🟡技术前沿 | 实战干货 | 疑难解答,持续更新中
  • 🟢加入《全栈知识库》,与各领域开发者共创技术盛宴
  • 🔵​进入《专属社群》,技术路上结伴同行,共同成长
  • 🟣点击下方名片,获取更多精彩内容👇

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐