本文正在参加「Python主题月」,详情查看 活动链接
背景介绍
如下两篇文章中从登录掘金到发布文章的全过程。之所以会有两篇文章,是因为写文章的时候后面的功能还没有完成。今天的文章是对整个项目的优化以及补充。
正文开始
此项目目前已经实现自动部署、邮件提醒、开关配置等功能。
项目主页提供详细的使用文档,如果你有需要欢迎 Fork 掘金自动发布文章 。
滑块拖拽优化
效果对比
之前的文章中有提到滑块的拖拽很慢(甚至会出现超时的情况),我们直接看优化前后的对比:
- 优化前
- 优化后
优化分析
这里是利用的正太分布的曲线是一条完美的先加速后减速的图像。下面对比一下两种方式的轨迹:
- 拖拽的总距离为 100 px
- 横坐标为第几次位移,纵坐标为位移的距离
总的来前者对应的方式较慢且误差较大。后者对应的方式为先加速后减速的过程。后者应该更符合拖拽逻辑。(这里不分好坏,只看结果。如果后者有一天被检测出违规拖拽,那么依然是可以使用前者)
代码实现
完整代码见 track.py
下面是关于优化后生成轨迹的代码:
def gen_normal_track(distance):
def norm_fun(x, mu, sigma):
pdf = np.exp(-((x - mu) ** 2) / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))
return pdf
result = []
# TODO 这里的移动次数可以根据 distance 灵活配置
for i in range(-10, 10, 1):
result.append(norm_fun(i, 0, 1) * distance)
# 为了减少误差
result.append(sum(result) - distance)
return result
复制代码
优化页面等待
项目中使用很多 time.sleep()
,有没有更优雅的方式。
因为懒。再者项目本身比较简单,发布属于一次性任务,对于程序的执行时间没有严格的限制,所以就偷懒直接就忽略这些细节。至于优化的方式肯定是有的,如下代码就会等待10秒,如果10秒内找到元素则立即返回,否则会抛出 TimeoutException 异常,WebDriverWait 默认每500毫秒调用一下ExpectedCondition直到它返回成功为止。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://juejin.cn/")
try:
element = WebDriverWait(driver,10).until(
EC.presence_of_element_located((By.XPATH, '''//div[@class="sc-kkGfuU bujTgx"]'''))
)
finally:
driver.quit()
复制代码
当然有更简单的方式直接使用全局的参数配置, 默认等待时间统一设置为10秒。
driver.implicitly_wait(10) # seconds
复制代码
错误重试机制
因滑块的识别以及滑块的拖拽存在失败的概率,导致登录不成功 代码见 juejin.py
- 新增登录成功的判断
- 新增登录失败重试机制
核心代码如下:
# 登录成功的判断
JUEJIN_NICKNAME = "西红柿蛋炒饭"
juejin_avatar_alt = JUEJIN_NICKNAME + "的头像"
driver.find_element(By.XPATH, f'//img[@alt="{juejin_avatar_alt}"]')
# 重试
for retry in range(self.retry):
# ...
get_cookies()
# ...
try:
avatar = self.driver.find_element(By.XPATH, '''//img[@alt="西红柿蛋炒饭的头像"]''')
if avatar:
break
except NoSuchElementException:
pass
复制代码
新增邮件功能
完整代码见 mail.py
新增文章发布完成邮件通知功能,邮件代码如下:
# 邮箱的配置请自行设置
def send(content: str, subject: str, mail_from: str, mail_to: list):
msg_root = MIMEMultipart('related')
msg_text = MIMEText(content, 'html', 'utf-8')
msg_root.attach(msg_text)
msg_root['Subject'] = subject
msg_root['From'] = mail_from
msg_root['To'] = ";".join(mail_to)
try:
stp = smtplib.SMTP_SSL(MAIL_HOST, MAIL_PORT)
# stp.set_debuglevel(1)
stp.ehlo()
stp.login(MAIL_USER, MAIL_PASSWORD)
stp.sendmail(MAIL_ADDRESS, mail_to, msg_root.as_string())
stp.quit()
except Exception as e:
print(traceback.format_exc(e))
复制代码
需要注意的是邮箱需要开通 POP3/SMTP/IMAP 服务,本文的配置开通的是 POP3/SMTP 服务,您可以根据邮箱的配置设置邮箱帐号密码。
变量 | 描述 | 示例 |
---|---|---|
MAIL_USER | 发件人邮箱用户名 | xxx.qq.com |
MAIL_ADDRESS | 发件人邮箱地址 | xxx.qq.com |
MAIL_HOST | 发件人邮箱服务器 | smt.qq.com |
MAIL_PASSWORD | 发件人邮箱密码 | xxxxxx |
MAIL_PORT | 邮箱服务器端口 | 465 |
MAIL_TO | 收信邮箱 | xxx.qq.com |
备注:这里测试过网易邮箱以及 QQ 邮箱的配置,均可正常发送邮件。
邮件结果如下:
写在最后
- 关于滑块检测成功率较低怎么处理?
这里关于滑块检测的算法有很多中比如基于 CV2 的 边缘检测;还有基于 YOLO 的目标识别技术。这里不做展开,其中基于机器学习的目标识别的成功率能达到 99%,有兴趣的小伙伴可以自行搜索。
- 关于项目未知错误的处理方式。
可以直接查看 GitHub Action 的运行结果,该项目会打印所有异常信息。
如果你觉得我的项目对你有帮助,欢迎一键三连❤️❤️❤️。
此项目所有代码见 我的GitHub仓库 ,欢迎 star fork。