使用Python及Selenium自动发表文章 篇三:简书

2020-05-05
5分钟阅读时长

前文中我们已经能将文章发表在思否上了。本文是使用Python及Selenium自动发表文章系列文章的第三篇。在本文中我们将实现将文章自动发表到简书上。

本文的主要内容是使用Python及Selenium自动发表文章到简书的实现思路及方式的介绍并为读者提供参考的源代码。

实现思路

和发表到思否类似,在自动发表文章到简书时,我们的实现思路大致如下:

  1. 对Markdown文章进行处理,生成相应的配置及内容。
  2. 模拟登录目标网站,在此我们选择了使用QQ授权登录。
  3. 将文章内容写入网站的编辑器并发表。

Markdown文章的处理

此部分为各平台发表时的公共模块,仍然是通过读取配置字段及正文的方式进行处理,在此就不再赘述。想了解的读者可以阅读使用Python及Selenium自动发表文章 篇二:思否中的对应内容。

模拟登录目标网站

和前文一样,在简书的模拟登录中我依然选择了QQ授权登录。

我们首先会读取本地cookies,如果不存在本地cookies则使用QQ登录。读取cookies的代码如下所示:

try:
    with open('jianshu_cookies.json', 'r', encoding='utf-8') as f:
        cookies = json.loads(f.read())
except FileNotFoundError:
    cookies = json.loads(segmentfault_login.qq(driver, timeout))

如果不存在cookies则需通过QQ进行登录。由于QQ授权登录的网站结构相同,在此的实现思路也与前文类似。想了解的读者可以阅读使用Python及Selenium自动发表文章 篇二:思否中的对应内容。

需要说明的是,如果有对Selenium基本操作还不熟悉的读者可以阅读我之前的所写的文章使用Selenium自动化你的浏览器。此文中包含了所有后续会用到的Selenium操作。

在编辑器中写入文章

登录成功后我们即可访问写博客的界面进行书写。简书的编辑器界面逻辑较为简单,首先我们需要选择对应的文集。在这里我将简书中的文集与我的分类相对应,具体如下所示:

def __set_corpus(post, driver, timeout):
    corpora = WebDriverWait(driver, timeout).until(lambda d: d.find_elements_by_class_name('_3DM7w'))
    for c in corpora:
        corpus = c.get_attribute('title')
        if post.categories[0] == corpus:
            c.click()
            return
    driver.find_element_by_class_name('_1iZMb').click()
    new_corpus = driver.find_element_by_class_name('_1CtV4')
    pyperclip.copy(post.categories[0])
    new_corpus.clear()
    new_corpus.send_keys(Keys.CONTROL, 'v')
    driver.find_element_by_class_name('dwU8Q').click()

可以看到,在此我们获取了全部文集的标题并与文章的分类进行对比。找到对应文集则进入,没找到则会新建文集。在进入文集后,我们需要新建文章,对应操作如下所示:

new_article = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[1]/div/div/div/div[1]'))
new_article.click()
article = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[1]/div/div/div/ul/li[1]'))
article.click()

在此之中我们新建了对应文章并进入了该文章的编辑界面。之后标题的输入及正文的输入均较为简单,如下所示:

# 添加标题
title = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[2]/div/div/div/div/input'))
pyperclip.copy(post.title)
title.clear()
title.send_keys(Keys.CONTROL, 'v')
time.sleep(3)
    
# 添加正文
content = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_id('arthur-editor'))
pyperclip.copy(post.content)
content.clear()
content.send_keys(Keys.CONTROL, 'v')
time.sleep(3)

上一篇文章类似,我使用了剪贴板来输入文本。为了实现对剪贴板的操作,我在其中使用了pyperclip库进行剪贴板的处理。

没有使用send_keys()方法模拟键盘输入的原因是在我的电脑上该方法无法输入反括号(`)。由于send_keys()方法的实现方式是模拟键盘输入,因此我猜测可能是由于语言的有关问题导致的。如果读者也遇到过此类问题欢迎在评论中指出解决方法。

在输入完正文内容后,可以选择点击保存或发表。有关代码如下所示:

# 保存草稿
driver.find_element_by_class_name('fa-floppy-o').click()

# 发表文章
driver.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[2]/div/div/div/div/ul/li[1]/a').click()

在使用的过程中读者只需选取自己需要的方式即可。

实现方式

在前文中,我们已经了解了将文章发表在简书上的思路。接下来则是实现具体的代码部分。

主程序

jianshu.py为在简书上发表文章的主程序。

import json

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait

import post_reader
import jianshu_login
import jianshu_writer

base_url = 'https://jianshu.com/'
driver = webdriver.Chrome()
driver.get(base_url)
driver.delete_all_cookies()

timeout = 5

try:
    with open('jianshu_cookies.json', 'r', encoding='utf-8') as f:
        cookies = json.loads(f.read())
except FileNotFoundError:
    cookies = json.loads(jianshu_login.qq(driver, timeout))

for cookie in cookies:
    driver.add_cookie({
        'name': cookie['name'],
        'value': cookie['value'],
        'path': cookie['path'],
        'domain': cookie['domain'],
        'secure': cookie['secure']
    })
driver.get('https://jianshu.com/')

mypost = post_reader.read_file('your_post.md')
jianshu_writer.write(mypost, driver, timeout)

Markdown文章的处理

jianshu.py中我们分别调用了各模块进行文章读取,登录及文章发表等操作。在post_reader.py中的则是对文章进行读取的相关操作。

import yaml

from post import Post


def read_file(file_path):
    with open(file_path, 'r', encoding= 'UTF-8') as f:
        whole = f.read().split('---\n', 2)
        content = whole[2]
        
        try:
            config = yaml.safe_load(whole[1])
            title = config['title']
            tags = config['tags']
            categories = config['categories']
            draft = config['draft']
            return Post(title, categories, content, draft)
        except yaml.YAMLError as exc:
            print(exc)

使用QQ登录目标网站

在读取文章内容后则是使用QQ进行登录,这一部分在authorization.py中实现。

from selenium.webdriver.support.wait import WebDriverWait
 

def qq(driver, timeout):
    window_handles = driver.window_handles
    driver.switch_to_window(window_handles[-1])
 
    iframe = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_id('ptlogin_iframe'))
    driver.switch_to_frame(iframe)
 
    login = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_class_name('face'))
    login.click()

在编辑器中写入文章

最终我们只需在简书的文本编辑器中完成文章的发表即可,这一部分在jianshu_writer.py中实现。

import time

import pyperclip
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains

from post import Post

def write(post, driver, timeout):
    write_link = 'https://www.jianshu.com/writer#/'
    driver.get(write_link)

    # 选择文集
    __set_corpus(post, driver, timeout)
    time.sleep(3)

    # 新建文章
    new_article = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[1]/div/div/div/div[1]'))
    new_article.click()
    article = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[1]/div/div/div/ul/li[1]'))
    article.click()
    time.sleep(3)

    # 添加标题
    title = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[2]/div/div/div/div/input'))
    pyperclip.copy(post.title)
    title.clear()
    title.send_keys(Keys.CONTROL, 'v')
    time.sleep(3)
    
    # 添加正文
    content = WebDriverWait(driver, timeout).until(lambda d: d.find_element_by_id('arthur-editor'))
    pyperclip.copy(post.content)
    content.clear()
    content.send_keys(Keys.CONTROL, 'v')
    time.sleep(3)

    # 保存草稿
    # driver.find_element_by_class_name('fa-floppy-o').click()

    # 发表文章
    driver.find_element_by_xpath('//*[@id="root"]/div/div[2]/div[2]/div/div/div/div/ul/li[1]/a').click()

def __set_corpus(post, driver, timeout):
    corpora = WebDriverWait(driver, timeout).until(lambda d: d.find_elements_by_class_name('_3DM7w'))
    for c in corpora:
        corpus = c.get_attribute('title')
        if post.categories[0] == corpus:
            c.click()
            return
    driver.find_element_by_class_name('_1iZMb').click()
    new_corpus = driver.find_element_by_class_name('_1CtV4')
    pyperclip.copy(post.categories[0])
    new_corpus.clear()
    new_corpus.send_keys(Keys.CONTROL, 'v')
    driver.find_element_by_class_name('dwU8Q').click()

目前代码中我使用的是发布文章,因此如果读者只需保存为草稿的话,去掉保存草稿部分的注释并注释掉发布文章部分即可。

以上为部分核心代码的简单分析,全部源代码读者可在后文处下载。

后记

本文中实现了将文章自动发表到简书的相关操作。相对于思否而言,在简书上发布文章的实现更为简单直观。

同样需要说明的是,因为本文的目的是简洁明了地给读者展示发表文章的思路及过程,所以对异常处理等方面并没有进行深入地处理。在之后的文章中我们将专门针对此问题进行改进。

源代码

读者可点击此处获取本文源代码。如果本文对你有帮助的话,欢迎star我的项目并关注我!

本文首发于我的个人博客wangchucheng.com
原文链接:https://wangchucheng.com/zh/posts/python-selenium-post-3/
本博客文章除特别声明外均为原创,采用CC BY-NC-SA 4.0 许可协议进行许可。超出CC BY-NC-SA 4.0 许可协议的使用请联系作者获得授权。

Avatar

WANG Chucheng

说学逗唱样样不精的地道天津人。